Hey, remember crowds?
Made with the wonderful open peeps by Pablo Stanley
A Pen by Szenia Zadvornykh on CodePen.
Hey, remember crowds?
Made with the wonderful open peeps by Pablo Stanley
A Pen by Szenia Zadvornykh on CodePen.
<canvas id="canvas"></canvas> |
console.clear() | |
console.log('lsakdfalskjdflnksd') | |
const config = { | |
src: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/open-peeps-sheet.png', | |
rows: 15, | |
cols: 7 | |
} | |
// UTILS | |
const randomRange = (min, max) => min + Math.random() * (max - min) | |
const randomIndex = (array) => randomRange(0, array.length) | 0 | |
const removeFromArray = (array, i) => array.splice(i, 1)[0] | |
const removeItemFromArray = (array, item) => removeFromArray(array, array.indexOf(item)) | |
const removeRandomFromArray = (array) => removeFromArray(array, randomIndex(array)) | |
const getRandomFromArray = (array) => ( | |
array[randomIndex(array) | 0] | |
) | |
// TWEEN FACTORIES | |
const resetPeep = ({ stage, peep }) => { | |
const direction = Math.random() > 0.5 ? 1 : -1 | |
// using an ease function to skew random to lower values to help hide that peeps have no legs | |
const offsetY = 100 - 250 * gsap.parseEase('power2.in')(Math.random()) | |
const startY = stage.height - peep.height + offsetY | |
let startX | |
let endX | |
if (direction === 1) { | |
startX = -peep.width | |
endX = stage.width | |
peep.scaleX = 1 | |
} else { | |
startX = stage.width + peep.width | |
endX = 0 | |
peep.scaleX = -1 | |
} | |
peep.x = startX | |
peep.y = startY | |
peep.anchorY = startY | |
return { | |
startX, | |
startY, | |
endX | |
} | |
} | |
const normalWalk = ({ peep, props }) => { | |
const { | |
startX, | |
startY, | |
endX | |
} = props | |
const xDuration = 10 | |
const yDuration = 0.25 | |
const tl = gsap.timeline() | |
tl.timeScale(randomRange(0.5, 1.5)) | |
tl.to(peep, { | |
duration: xDuration, | |
x: endX, | |
ease: 'none' | |
}, 0) | |
tl.to(peep, { | |
duration: yDuration, | |
repeat: xDuration / yDuration, | |
yoyo: true, | |
y: startY - 10 | |
}, 0) | |
return tl | |
} | |
const walks = [ | |
normalWalk, | |
] | |
// CLASSES | |
class Peep { | |
constructor({ | |
image, | |
rect, | |
}) { | |
this.image = image | |
this.setRect(rect) | |
this.x = 0 | |
this.y = 0 | |
this.anchorY = 0 | |
this.scaleX = 1 | |
this.walk = null | |
} | |
setRect (rect) { | |
this.rect = rect | |
this.width = rect[2] | |
this.height = rect[3] | |
this.drawArgs = [ | |
this.image, | |
...rect, | |
0, 0, this.width, this.height | |
] | |
} | |
render (ctx) { | |
ctx.save() | |
ctx.translate(this.x, this.y) | |
ctx.scale(this.scaleX, 1) | |
ctx.drawImage(...this.drawArgs) | |
ctx.restore() | |
} | |
} | |
// MAIN | |
const img = document.createElement('img') | |
img.onload = init | |
img.src = config.src | |
const canvas = document.querySelector('#canvas') | |
const ctx = canvas.getContext('2d') | |
const stage = { | |
width: 0, | |
height: 0, | |
} | |
const allPeeps = [] | |
const availablePeeps = [] | |
const crowd = [] | |
function init () { | |
createPeeps() | |
// resize also (re)populates the stage | |
resize() | |
gsap.ticker.add(render) | |
window.addEventListener('resize', resize) | |
} | |
function createPeeps () { | |
const { | |
rows, | |
cols | |
} = config | |
const { | |
naturalWidth: width, | |
naturalHeight: height | |
} = img | |
const total = rows * cols | |
const rectWidth = width / rows | |
const rectHeight = height / cols | |
for (let i = 0; i < total; i++) { | |
allPeeps.push(new Peep({ | |
image: img, | |
rect: [ | |
(i % rows) * rectWidth, | |
(i / rows | 0) * rectHeight, | |
rectWidth, | |
rectHeight, | |
] | |
})) | |
} | |
} | |
function resize () { | |
stage.width = canvas.clientWidth | |
stage.height = canvas.clientHeight | |
canvas.width = stage.width * devicePixelRatio | |
canvas.height = stage.height * devicePixelRatio | |
crowd.forEach((peep) => { | |
peep.walk.kill() | |
}) | |
crowd.length = 0 | |
availablePeeps.length = 0 | |
availablePeeps.push(...allPeeps) | |
initCrowd() | |
} | |
function initCrowd () { | |
while (availablePeeps.length) { | |
// setting random tween progress spreads the peeps out | |
addPeepToCrowd().walk.progress(Math.random()) | |
} | |
} | |
function addPeepToCrowd () { | |
const peep = removeRandomFromArray(availablePeeps) | |
const walk = getRandomFromArray(walks)({ | |
peep, | |
props: resetPeep({ | |
peep, | |
stage, | |
}) | |
}).eventCallback('onComplete', () => { | |
removePeepFromCrowd(peep) | |
addPeepToCrowd() | |
}) | |
peep.walk = walk | |
crowd.push(peep) | |
crowd.sort((a, b) => a.anchorY - b.anchorY) | |
return peep | |
} | |
function removePeepFromCrowd (peep) { | |
removeItemFromArray(crowd, peep) | |
availablePeeps.push(peep) | |
} | |
function render () { | |
canvas.width = canvas.width | |
ctx.save() | |
ctx.scale(devicePixelRatio, devicePixelRatio) | |
crowd.forEach((peep) => { | |
peep.render(ctx) | |
}) | |
ctx.restore() | |
} |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js"></script> |
html, body { | |
height: 100%; | |
} | |
body { | |
margin: 0; | |
} | |
#canvas { | |
width: 100%; | |
height: 100%; | |
} |