A little infinite spinner made in pure canvas.
A Pen by Sylvain Reucherand on CodePen.
const easeInOutQuart = function (t, b, c, d) { | |
if ((t/=d/2) < 1) return c/2*t*t*t*t + b; | |
return -c/2 * ((t-=2)*t*t*t - 2) + b; | |
} | |
class Wheel { | |
constructor (s) { | |
this.canvas = document.createElement('canvas') | |
this.context = this.canvas.getContext('2d') | |
this.width = s | |
this.height = s | |
this.radius = s / 2 | |
this.started = 0 | |
this.elapsed = 0 | |
this.duration = 1500 | |
} | |
init () { | |
this.canvas.width = this.width | |
this.canvas.height = this.height | |
this.canvas.style.width = this.width | |
this.canvas.style.height = this.height | |
this.started = Date.now() | |
} | |
update (dt) { | |
this.elapsed += dt | |
this.progress = this.elapsed / this.duration | |
this.progress = (this.progress > 1 && 1) || this.progress | |
} | |
render () { | |
let progress = easeInOutQuart(this.progress, 0, 1, 1) | |
this.context.clearRect(0, 0, this.width, this.height) | |
let offsetX = this.width / 2 | |
let offsetY = this.height / 2 | |
let radius = Math.max(0, progress * this.radius) | |
let angle = progress * Math.PI * 2 | |
let x = Math.cos(angle - Math.PI) * (this.radius - radius) + offsetX | |
let y = Math.sin(angle - Math.PI) * (this.radius - radius) + offsetY | |
this.context.fillStyle = '#af3063' | |
this.context.strokeStyle = '#af3063' | |
this.context.save() | |
this.context.beginPath() | |
this.context.arc(offsetX, offsetY, this.radius, 0, 2 * Math.PI) | |
this.context.fill() | |
this.context.globalCompositeOperation = 'source-in' | |
let x2 = Math.cos(-angle) * radius | |
let y2 = Math.sin(-angle) * radius | |
this.context.beginPath() | |
this.context.arc(offsetX + x2, offsetY - y2, this.radius, angle, angle + Math.PI) | |
this.context.lineWidth = radius * 2 | |
this.context.stroke() | |
this.context.restore() | |
this.context.beginPath() | |
this.context.arc(x, y, radius, 0, 2 * Math.PI) | |
this.context.fill() | |
} | |
} | |
class Spinner { | |
constructor (s) { | |
this.canvas = document.createElement('canvas') | |
this.context = this.canvas.getContext('2d') | |
this.width = s | |
this.height = s | |
this.wheels = [] | |
this.update = this.update.bind(this) | |
document.body.appendChild(this.canvas) | |
this.time = 0 | |
} | |
init () { | |
this.canvas.width = this.width | |
this.canvas.height = this.height | |
this.canvas.style.width = this.width | |
this.canvas.style.height = this.height | |
this.time = Date.now() | |
} | |
start () { | |
this.tick() | |
this.update() | |
} | |
tick () { | |
let wheel = new Wheel(this.width) | |
wheel.init() | |
this.wheels.push(wheel) | |
} | |
update () { | |
window.requestAnimationFrame(this.update) | |
let now = Date.now() | |
let dt = now - this.time | |
if (this.wheels.slice(-1)[0].progress === 1) { | |
if (this.wheels.length + 1 > 4) { | |
this.wheels = [] | |
} | |
this.tick() | |
} | |
for (let i=0, wheel; i<this.wheels.length; i++) { | |
wheel = this.wheels[i] | |
wheel.update(dt) | |
} | |
this.time = now | |
this.render() | |
} | |
render () { | |
this.context.clearRect(0, 0, this.width, this.height) | |
this.context.globalCompositeOperation = 'source-over' | |
for (let i=0, wheel; i<this.wheels.length; i++) { | |
wheel = this.wheels[i] | |
wheel.render() | |
this.context.save() | |
if (!!(i % 2)) { | |
this.context.globalCompositeOperation = 'destination-out' | |
} | |
this.context.drawImage(wheel.canvas, 0, 0, this.width, this.height) | |
this.context.restore() | |
} | |
this.context.globalCompositeOperation = 'destination-in' | |
this.context.beginPath() | |
this.context.arc(this.width / 2, this.height / 2, this.width / 2 - 3, 0, 2 * Math.PI) | |
this.context.fill() | |
} | |
} | |
let spinner = new Spinner(160) | |
spinner.init() | |
spinner.start() |
<script src="http://facebook.github.io/rebound-js/rebound.js"></script> |
A little infinite spinner made in pure canvas.
A Pen by Sylvain Reucherand on CodePen.
canvas { | |
left: 50%; | |
margin-left: -80px; | |
margin-top: -80px; | |
position: fixed; | |
top: 50%; | |
} |