Skip to content

Commit b03e2b8

Browse files
committed
Add rgba, rgb, hsl, hsla color support with tests and doc updates
1 parent 2516317 commit b03e2b8

25 files changed

+418
-227
lines changed

dist/granim.js

+133-48
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
/*! Granim v1.1.1 - https://sarcadass.github.io/granim.js */
2-
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
2+
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
33
'use strict';
44

55
function Granim(options) {
6-
var doesGradientUseOpacity;
76
this.getElement(options.element);
87
this.x1 = 0;
98
this.y1 = 0;
@@ -13,15 +12,15 @@ function Granim(options) {
1312
this.customDirection = options.customDirection || {};
1413
this.validateInput('direction');
1514
this.isPausedWhenNotInView = options.isPausedWhenNotInView || false;
16-
this.opacity = options.opacity;
15+
// this.opacity = options.opacity;
1716
this.states = options.states;
1817
this.stateTransitionSpeed = options.stateTransitionSpeed || 1000;
1918
this.previousTimeStamp = null;
2019
this.progress = 0;
2120
this.isPaused = false;
2221
this.isCleared = false;
2322
this.isPausedBecauseNotInView = false;
24-
this.iscurrentColorsSet = false;
23+
// this.isCurrentColorsSet = false;
2524
this.context = this.canvas.getContext('2d');
2625
this.channels = {};
2726
this.channelsIndex = 0;
@@ -46,9 +45,7 @@ function Granim(options) {
4645
blendingMode: options.image.blendingMode || false
4746
};
4847
}
49-
doesGradientUseOpacity = this.opacity.map(function(el) { return el !== 1 })
50-
.indexOf(true) !== -1;
51-
this.shouldClearCanvasOnEachFrame = !!this.image || doesGradientUseOpacity;
48+
5249
this.events = {
5350
start: new CustomEvent('granim:start'),
5451
end: new CustomEvent('granim:end'),
@@ -109,14 +106,14 @@ Granim.prototype.validateInput = require('./validateInput.js');
109106

110107
Granim.prototype.triggerError = require('./triggerError.js');
111108

109+
Granim.prototype.convertColorToRgba = require('./convertColorToRgba.js');
110+
112111
Granim.prototype.prepareImage = require('./prepareImage.js');
113112

114113
Granim.prototype.eventPolyfill = require('./eventPolyfill.js');
115114

116115
Granim.prototype.colorDiff = require('./colorDiff.js');
117116

118-
Granim.prototype.hexToRgb = require('./hexToRgb.js');
119-
120117
Granim.prototype.setDirection = require('./setDirection.js');
121118

122119
Granim.prototype.setColors = require('./setColors.js');
@@ -155,7 +152,7 @@ Granim.prototype.changeState = require('./changeState.js');
155152

156153
module.exports = Granim;
157154

158-
},{"./animateColors.js":2,"./changeBlendingMode.js":3,"./changeDirection.js":4,"./changeState.js":5,"./clear.js":6,"./colorDiff.js":7,"./destroy.js":8,"./eventPolyfill.js":9,"./getCurrentColors.js":10,"./getDimensions.js":11,"./getElement.js":12,"./getLightness.js":13,"./hexToRgb.js":14,"./makeGradient.js":15,"./onResize.js":16,"./onScroll.js":17,"./pause.js":18,"./pauseWhenNotInView.js":19,"./play.js":20,"./prepareImage.js":21,"./refreshColors.js":22,"./setColors.js":23,"./setDirection.js":24,"./setSizeAttributes.js":25,"./triggerError.js":26,"./validateInput.js":27}],2:[function(require,module,exports){
155+
},{"./animateColors.js":2,"./changeBlendingMode.js":3,"./changeDirection.js":4,"./changeState.js":5,"./clear.js":6,"./colorDiff.js":7,"./convertColorToRgba.js":8,"./destroy.js":9,"./eventPolyfill.js":10,"./getCurrentColors.js":11,"./getDimensions.js":12,"./getElement.js":13,"./getLightness.js":14,"./makeGradient.js":15,"./onResize.js":16,"./onScroll.js":17,"./pause.js":18,"./pauseWhenNotInView.js":19,"./play.js":20,"./prepareImage.js":21,"./refreshColors.js":22,"./setColors.js":23,"./setDirection.js":24,"./setSizeAttributes.js":25,"./triggerError.js":26,"./validateInput.js":27}],2:[function(require,module,exports){
159156
'use strict';
160157

161158
module.exports = function(timestamp) {
@@ -290,7 +287,7 @@ module.exports = function(state) {
290287
// Compute the gradient diff between the last frame gradient
291288
// and the first one of the new state
292289
this.states[state].gradients[0].forEach(function(color, i, arr) {
293-
nextColors = _this.hexToRgb(_this.states[state].gradients[0][i]);
290+
nextColors = _this.convertColorToRgba(_this.states[state].gradients[0][i]);
294291
colorDiff = _this.colorDiff(_this.activeColors[i], nextColors);
295292
_this.activeColorDiff.push(colorDiff);
296293
});
@@ -321,14 +318,115 @@ module.exports = function(colorA, colorB) {
321318
var i;
322319
var colorDiff = [];
323320

324-
for (i = 0; i < 3; i++) {
321+
for (i = 0; i < 4; i++) {
325322
colorDiff.push(colorB[i] - colorA[i])
326323
}
327324

328325
return colorDiff;
329326
};
330327

331328
},{}],8:[function(require,module,exports){
329+
'use strict'
330+
331+
var regex = {
332+
hexa: /^#(?:[0-9a-fA-F]{3}){1,2}$/,
333+
rgba: /^rgba\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3}), ?(.?\d{1,3})\)$/,
334+
rgb: /^rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\)$/,
335+
hsla: /^hsla\((\d{1,3}), ?(\d{1,3})%, ?(\d{1,3})%, ?(.?\d{1,3})\)$/,
336+
hsl: /^hsl\((\d{1,3}), ?(\d{1,3})%, ?(\d{1,3})%\)$/
337+
}, match;
338+
339+
module.exports = function(color) {
340+
switch(identifyColorType(color)) {
341+
default:
342+
this.triggerError('colorType');
343+
344+
case 'hexa':
345+
return hexToRgba(color);
346+
347+
case 'rgba':
348+
return [
349+
parseInt(match[1], 10),
350+
parseInt(match[2], 10),
351+
parseInt(match[3], 10),
352+
parseFloat(match[4], 10)
353+
];
354+
355+
case 'rgb':
356+
return [
357+
parseInt(match[1], 10),
358+
parseInt(match[2], 10),
359+
parseInt(match[3], 10),
360+
1
361+
];
362+
363+
case 'hsla':
364+
return hslaToRgb(
365+
parseInt(match[1], 10) / 360,
366+
parseInt(match[2], 10) / 100,
367+
parseInt(match[3], 10) / 100,
368+
parseFloat(match[4], 10)
369+
);
370+
371+
case 'hsl':
372+
return hslaToRgb(
373+
parseInt(match[1], 10) / 360,
374+
parseInt(match[2], 10) / 100,
375+
parseInt(match[3], 10) / 100,
376+
1
377+
);
378+
}
379+
}
380+
381+
function identifyColorType(color) {
382+
var colorTypes = Object.keys(regex);
383+
var i = 0;
384+
for (i; i < colorTypes.length; i++) {
385+
match = regex[colorTypes[i]].exec(color);
386+
if (match) return colorTypes[i];
387+
}
388+
return false;
389+
}
390+
391+
function hexToRgba(hex) {
392+
// Expand shorthand form (e.g. '03F') to full form (e.g. '0033FF')
393+
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
394+
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
395+
return r + r + g + g + b + b;
396+
});
397+
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
398+
return result ? [
399+
parseInt(result[1], 16),
400+
parseInt(result[2], 16),
401+
parseInt(result[3], 16),
402+
1
403+
] : null;
404+
}
405+
406+
function hue2rgb(p, q, t) {
407+
if (t < 0) t += 1;
408+
if (t > 1) t -= 1;
409+
if (t < 1 / 6) return p + (q - p) * 6 * t;
410+
if (t < 1 / 2) return q;
411+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
412+
return p;
413+
}
414+
415+
function hslaToRgb(h, s, l, a) {
416+
var r, g, b, q, p;
417+
if (s === 0) {
418+
r = g = b = l; // achromatic
419+
} else {
420+
q = l < 0.5 ? l * (1 + s) : l + s - l * s;
421+
p = 2 * l - q;
422+
r = hue2rgb(p, q, h + 1/3);
423+
g = hue2rgb(p, q, h);
424+
b = hue2rgb(p, q, h - 1/3);
425+
}
426+
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a];
427+
}
428+
429+
},{}],9:[function(require,module,exports){
332430
'use strict';
333431

334432
module.exports = function() {
@@ -337,7 +435,7 @@ module.exports = function() {
337435
this.clear();
338436
};
339437

340-
},{}],9:[function(require,module,exports){
438+
},{}],10:[function(require,module,exports){
341439
'use strict';
342440

343441
module.exports = function() {
@@ -355,7 +453,7 @@ module.exports = function() {
355453
window.CustomEvent = CustomEvent;
356454
};
357455

358-
},{}],10:[function(require,module,exports){
456+
},{}],11:[function(require,module,exports){
359457
'use strict';
360458

361459
module.exports = function() {
@@ -371,15 +469,15 @@ module.exports = function() {
371469
return currentColors;
372470
};
373471

374-
},{}],11:[function(require,module,exports){
472+
},{}],12:[function(require,module,exports){
375473
'use strict';
376474

377475
module.exports = function() {
378476
this.x1 = this.canvas.offsetWidth;
379477
this.y1 = this.canvas.offsetHeight;
380478
};
381479

382-
},{}],12:[function(require,module,exports){
480+
},{}],13:[function(require,module,exports){
383481
'use strict';
384482

385483
module.exports = function(element) {
@@ -398,7 +496,7 @@ module.exports = function(element) {
398496
}
399497
};
400498

401-
},{}],13:[function(require,module,exports){
499+
},{}],14:[function(require,module,exports){
402500
'use strict';
403501

404502
module.exports = function() {
@@ -427,33 +525,14 @@ module.exports = function() {
427525
return lightnessAverage >= 128 ? 'light' : 'dark';
428526
};
429527

430-
},{}],14:[function(require,module,exports){
431-
'use strict';
432-
433-
module.exports = function(hex) {
434-
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
435-
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
436-
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
437-
return r + r + g + g + b + b;
438-
});
439-
440-
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
441-
return result ? [
442-
parseInt(result[1], 16),
443-
parseInt(result[2], 16),
444-
parseInt(result[3], 16)
445-
] : null;
446-
};
447-
448528
},{}],15:[function(require,module,exports){
449529
'use strict';
450530

451531
module.exports = function() {
452532
var i, colorPosition;
453533
var gradient = this.setDirection();
454534
var elToSetClassOnClass = document.querySelector(this.elToSetClassOn).classList;
455-
456-
if (this.shouldClearCanvasOnEachFrame) this.context.clearRect(0, 0, this.x1, this.y1);
535+
this.context.clearRect(0, 0, this.x1, this.y1);
457536

458537
if (this.image) {
459538
this.context.drawImage(
@@ -473,7 +552,7 @@ module.exports = function() {
473552
this.currentColors[i][0] + ', ' +
474553
this.currentColors[i][1] + ', ' +
475554
this.currentColors[i][2] + ', ' +
476-
this.opacity[i] + ')'
555+
this.currentColors[i][3] + ')'
477556
);
478557
}
479558

@@ -688,9 +767,15 @@ module.exports = function(progressPercent) {
688767
// Loop through each colors of the active gradient
689768
for (i = 0; i < this.activeColors.length; i++) {
690769

691-
// Generate RGB colors
692-
for (j = 0; j < 3; j++) {
770+
// Generate RGBA colors
771+
for (j = 0; j < 4; j++) {
772+
// If color value [0-255] round to the integer,
773+
// Else f opacity [0-1] round to 2 decimals
693774
activeChannel = _this.activeColors[i][j] +
775+
(j !== 3
776+
? Math.ceil(_this.activeColorDiff[i][j] / 100 * progressPercent)
777+
: Math.round((_this.activeColorDiff[i][j] / 100 * progressPercent) * 100) / 100
778+
)
694779
Math.ceil(_this.activeColorDiff[i][j] / 100 * progressPercent);
695780

696781
// Prevent colors values from going < 0 & > 255
@@ -729,16 +814,16 @@ module.exports = function() {
729814

730815
// Go on each gradient of the current state
731816
this.states[this.activeState].gradients[this.channelsIndex].forEach(function(color, i) {
732-
// Push the hex color converted to rgb on the channel and the active color properties
733-
var rgbColor = _this.hexToRgb(color);
817+
// Push the hex color converted to rgba on the channel and the active color properties
818+
var rgbaColor = _this.convertColorToRgba(color);
734819
var activeChannel = _this.channels[_this.activeState];
735820

736-
activeChannel[_this.channelsIndex].colors.push(rgbColor);
737-
_this.activeColors.push(rgbColor);
821+
activeChannel[_this.channelsIndex].colors.push(rgbaColor);
822+
_this.activeColors.push(rgbaColor);
738823

739824
// If it's the first channel to be set, set the currentColors
740-
if (!_this.iscurrentColorsSet) {
741-
_this.currentColors.push(_this.hexToRgb(color));
825+
if (!_this.isCurrentColorsSet) {
826+
_this.currentColors.push(_this.convertColorToRgba(color));
742827
}
743828

744829
// If it's the last gradient, compute the color diff between the last gradient and the first one,
@@ -749,7 +834,7 @@ module.exports = function() {
749834
activeChannel[0].colors[i]
750835
);
751836
} else {
752-
nextColors = _this.hexToRgb(_this.states[_this.activeState].gradients[_this.channelsIndex + 1][i]);
837+
nextColors = _this.convertColorToRgba(_this.states[_this.activeState].gradients[_this.channelsIndex + 1][i]);
753838
colorDiff = _this.colorDiff(
754839
activeChannel[_this.channelsIndex].colors[i], nextColors
755840
);
@@ -760,7 +845,7 @@ module.exports = function() {
760845
});
761846

762847
this.activetransitionSpeed = this.states[this.activeState].transitionSpeed || 5000;
763-
this.iscurrentColorsSet = true;
848+
this.isCurrentColorsSet = true;
764849
};
765850

766851
},{}],24:[function(require,module,exports){

dist/granim.min.js

+5-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/api-v2.0.0.html

+10-11
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,16 @@
3434
y1: '322px'
3535
}</code></pre><strong>This property is required</strong> when the property<span class="snippet">direction</span> is set to<span class="snippet">custom</span>.<br>For more details, checkout the documentation of the <a class="link" href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createLinearGradient" target="_blank">createLinearGradient </a>function.</td></tr><tr><td>isPausedWhenNotInView</td><td>false</td><td>Boolean</td><td>Does the animation stop when it's not in window view?<br>
3636
(Despite this parameter, the animation always pause
37-
when changing tab).</td></tr><tr><td>scrollDebounceThreshold <span class="version">v1.1.0</span></td><td>300</td><td>Int</td><td>What is the scroll <a class="link" href="https://css-tricks.com/debouncing-throttling-explained-examples/" target="_blank">debounce</a> threshold (in ms)?<br><strong>Only useful if </strong><span class="snippet">isPausedWhenNotInView</span> is set to <span class="snippet">true</span>.</td></tr><tr><td>opacity <span class="required">(required)</span></td><td>/</td><td>Array of Int</td><td>You can adapt the opacity of each color of the gradient.<br>
38-
if you have two colors <span class="snippet">[1, .5]</span>,
39-
or three <span class="snippet">[1, .5, 1]</span>...</td></tr><tr><td>stateTransitionSpeed</td><td>1000</td><td>Int</td><td>Duration of the animation when changing state.</td></tr><tr><td>defaultStateName</td><td>'default-state'</td><td>String</td><td>The name of the default state.</td></tr><tr><td>states <span class="required">(required)</span></td><td>/</td><td>Object</td><td>Object containing all the states, see more info below.</td></tr><tr><td>image <span class="version">v1.1.0</span></td><td>/</td><td>Object</td><td>Object containing image options, see more info below.</td></tr></tbody></table></div><div class="container bloc"><h2>options.states</h2><p>All the options available to customize the states and the different gradients.<br>
37+
when changing tab).</td></tr><tr><td>scrollDebounceThreshold <span class="version">v1.1.0</span></td><td>300</td><td>Int</td><td>What is the scroll <a class="link" href="https://css-tricks.com/debouncing-throttling-explained-examples/" target="_blank">debounce</a> threshold (in ms)?<br><strong>Only useful if </strong><span class="snippet">isPausedWhenNotInView</span> is set to <span class="snippet">true</span>.</td></tr><tr><td>stateTransitionSpeed</td><td>1000</td><td>Int</td><td>Duration of the animation when changing state.</td></tr><tr><td>defaultStateName</td><td>'default-state'</td><td>String</td><td>The name of the default state.</td></tr><tr><td>states <span class="required">(required)</span></td><td>/</td><td>Object</td><td>Object containing all the states, see more info below.</td></tr><tr><td>image <span class="version">v1.1.0</span></td><td>/</td><td>Object</td><td>Object containing image options, see more info below.</td></tr></tbody></table></div><div class="container bloc"><h2>options.states</h2><p>All the options available to customize the states and the different gradients.<br>
4038
Create a state with the name you want then add these parameters.<br>
41-
By default, the name of your first state is <span class="snippet">'default-state'</span>, you can change it with <span class="snippet">options.defaultStateName</span>.</p><table class="api-list"><thead><tr><th>Property</th><th>Default</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>gradients <span class="required">(required)</span></td><td>/</td><td>Array of Arrays</td><td>The different gradients with the different colors
42-
e.g. <span class="snippet">[ ['#FFF', '#000'],
43-
['#000', '#FFF'] ]</span>.<br>
44-
The array can contain only one gradient.</td></tr><tr><td>transitionSpeed</td><td>5000</td><td>Int</td><td>Transition duration between each gradient.</td></tr><tr><td>loop</td><td>true</td><td>Boolean</td><td>When the animation has arrived to the last gradient, does it repeat?</td></tr></tbody></table></div><div class="container bloc" id="options-image"><h2>options.image <span class="version">v1.1.0</span></h2><p>All the options available to customize the image.<br>
39+
By default, the name of your first state is <span class="snippet">'default-state'</span>, you can change it with <span class="snippet">options.defaultStateName</span>.</p><table class="api-list"><thead><tr><th>Property</th><th>Default</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>gradients <span class="required">(required)</span></td><td>/</td><td>Array of Arrays</td><td>The different gradients with the different colors, e.g.<pre><code class="language-js">[
40+
['rgba(255, 153, 102, .33)', '#ff5e62'],
41+
['hsla(144, 100%, 47%, .75)', 'hsl(210, 96%, 46%)'],
42+
['rgb(225, 238, 195)', '#f05053'],
43+
]</code></pre>The colors type accepted are: <span class="snippet">hexadecimal</span>,
44+
<span class="snippet">rgb</span>, <span class="snippet">rgba</span>,
45+
<span class="snippet">hsl</span> and <span class="snippet">hsla</span>.
46+
All the gradients should contain the same number of colors.</td></tr><tr><td>transitionSpeed</td><td>5000</td><td>Int</td><td>Transition duration between each gradient.</td></tr><tr><td>loop</td><td>true</td><td>Boolean</td><td>When the animation has arrived to the last gradient, does it repeat?</td></tr></tbody></table></div><div class="container bloc" id="options-image"><h2>options.image <span class="version">v1.1.0</span></h2><p>All the options available to customize the image.<br>
4547
The blending Mode works only if you set an image and gradients.</p><table class="api-list"><thead><tr><th>Property</th><th>Default</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>source <span class="required">(required)</span></td><td>/</td><td>String</td><td>The source of your image,
4648
e.g. <span class="snippet">'img/800x200.jpg'</span>.</td></tr><tr><td>position</td><td>['center', 'center']</td><td>Array</td><td>The position of your image in the canvas, represented as an <span class="snippet">[x, y]</span> array.<br>
4749
Possible values for <span class="snippet">x</span>:<ul><li><span class="snippet">'left'</span></li><li><span class="snippet">'center'</span></li><li><span class="snippet">'right'</span></li></ul>Possible values for <span class="snippet">y</span>:<ul><li><span class="snippet">'top'</span></li><li><span class="snippet">'center'</span></li><li><span class="snippet">'bottom'</span></li></ul></td></tr><tr><td>stretchMode</td><td>/</td><td>Array</td><td>Does the image have to stretch ? This option is useful for liquid/responsive design.
@@ -88,7 +90,6 @@
8890
direction: 'diagonal',
8991
isPausedWhenNotInView: false,
9092
scrollDebounceThreshold: 300,
91-
opacity: [1, 1],
9293
stateTransitionSpeed: 1000,
9394
image : {
9495
source: '../assets/img/bg-forest.jpg',
@@ -126,15 +127,13 @@
126127
}
127128
);</code></pre><p>You can use more than 2 colors for the gradients,
128129
but cannot have different length of colors in the
129-
different states. you will also have to
130-
<strong>adapt the opacity</strong>.</p><pre><code class="language-js">var granimInstance = new Granim({
130+
different states.</p><pre><code class="language-js">var granimInstance = new Granim({
131131
element: '',
132132
name: 'granim',
133133
elToSetClassOn: 'body',
134134
direction: 'diagonal',
135135
isPausedWhenNotInView: false,
136136
scrollDebounceThreshold: 300,
137-
opacity: [1, 1, .5, 1],
138137
stateTransitionSpeed: 1000,
139138
states : {
140139
"default-state": {

0 commit comments

Comments
 (0)