<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>一个有趣的动态背景</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<canvas id="canvas"></canvas>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.0/gsap.min.js'></script><script src="./script.js"></script>
</body>
</html>
console.clear();
console.log('');
const config = {
src: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/open-peeps-sheet.png',
rows: 15,
cols: 7 };
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];
const resetPeep = ({ stage, peep }) => {
const direction = Math.random() > 0.5 ? 1 : -1;
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];
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();
}}
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();
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) {
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();
}
html, body {
height: 100%;
}
body {
margin: 0;
}
#canvas {
width: 100%;
height: 100%;
}