|
/** |
|
* lessons → lerpArray → animate between colors |
|
* — |
|
* |
|
* Animate between two HSL colors using a cubic easing function |
|
* and perceptual color space (CIELAB). |
|
* |
|
* You can read about color interpolation here: |
|
* https://howaboutanorange.com/blog/2011/08/10/color_interpolation/ |
|
* |
|
* You can visualize the cubic bezier easing function using this tool: |
|
* http://cubic-bezier.com/?#.85,0,.15,1 |
|
*/ |
|
|
|
const canvasSketch = require('canvas-sketch'); |
|
const { lerpArray } = require('canvas-sketch-util/math'); |
|
const colorSpace = require('color-space'); |
|
const BezierEasing = require('bezier-easing'); |
|
|
|
const settings = { |
|
dimensions: [ 512, 512 ], |
|
animate: true, |
|
duration: 3 |
|
}; |
|
|
|
const rect = (context, x, y, width, height, color) => { |
|
context.fillStyle = color; |
|
context.fillRect(x, y, width, height); |
|
}; |
|
|
|
const circle = (context, x, y, radius, color, lineWidth) => { |
|
context.strokeStyle = context.fillStyle = color; |
|
context.beginPath(); |
|
context.arc(x, y, radius, 0, Math.PI * 2, false); |
|
context.lineWidth = lineWidth; |
|
if (lineWidth != null) context.stroke(); |
|
else context.fill(); |
|
}; |
|
|
|
const progress = (context, time, width, height, margin = 0) => { |
|
context.fillStyle = 'white'; |
|
context.fillRect( |
|
margin * 2, |
|
height - margin * 2, |
|
(width - margin * 4) * time, |
|
4 |
|
); |
|
}; |
|
|
|
const sketch = ({ width, height }) => { |
|
const margin = 25; |
|
|
|
// A sharp in-out easing to snap forward without staying |
|
// in the in-between frames for too long. |
|
const ease = new BezierEasing(0.85, 0, 0.15, 1.0); |
|
|
|
return props => { |
|
// Destructure a few props we need |
|
const { context, width, height, playhead } = props; |
|
|
|
// Fill off-white background |
|
rect(context, 0, 0, width, height, 'hsl(0, 0%, 98%)'); |
|
|
|
// Fill color foreground with padding |
|
rect(context, margin, margin, width - margin * 2, height - margin * 2, '#e5b5b5'); |
|
|
|
// Draw this scene |
|
draw(props); |
|
|
|
// Also draw a timeline at the bottom |
|
progress(context, playhead, width, height, margin); |
|
}; |
|
|
|
function draw ({ context, width, height, playhead, deltaTime }) { |
|
const radius = 80; |
|
|
|
// Get our 0..1 range value |
|
let t = playhead; |
|
|
|
// Ease the value so we spend less time with in-between colors |
|
t = ease(t); |
|
|
|
// Choose a start and end color, let's use HSL here for fun |
|
const color0HSL = [ 0, 50, 50 ]; |
|
const color1HSL = [ 200, 30, 40 ]; |
|
|
|
// Convert both colors to a perceptual color space |
|
// Can also try others like 'xyz', 'hsl', 'hsluv', 'lch' |
|
// In some cases this will help reduce in-between hue/saturation shifts |
|
const space = 'lab'; |
|
const color0Conv = colorSpace.hsl[space](color0HSL); |
|
const color1Conv = colorSpace.hsl[space](color1HSL); |
|
|
|
// Interpolate within this new color space from A to B color |
|
const colorConv = lerpArray(color0Conv, color1Conv, t); |
|
|
|
// Now convert to regular RGB for our canvas |
|
const [ R, G, B ] = colorSpace[space].rgb(colorConv); |
|
|
|
// Create a CSS color string with R, G, B components |
|
const color = `rgb(${R.toFixed(6)}, ${G.toFixed(6)}, ${B.toFixed(6)})`; |
|
|
|
// Draw circle with this color |
|
circle(context, width / 2, height / 2, radius, color); |
|
} |
|
}; |
|
|
|
canvasSketch(sketch, settings); |
Linear interpolation is a really handy function for creative coding, animations, game dev, and generative art.