<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>圣诞老人舔“铁”</title>
<style>
* {
margin: 0;
box-sizing: border-box;
overflow: hidden;
}
body {
background: #cdebf0;
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
body canvas {
box-shadow: 0.2em 0.2em 2em #0004;
border: none;
outline: none;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="https://cdn.jsdelivr.net/processing.js/1.4.8/processing.min.js"></script>
<script>
var sketchProc = function (processingInstance) {
with (processingInstance) {
size(600, 600);
frameRate(60);
smooth();
const Santa = (function () {
const _Santa = function (args) {
this.x = 400;
this.y = 430;
this.diameter = 180;
this.tounge = {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
x3: 0,
y3: 0,
x4: 0,
y4: 0,
};
this.arms = {
left: {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
x3: 0,
y3: 0,
x4: 0,
y4: 0,
},
right: {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
x3: 0,
y3: 0,
x4: 0,
y4: 0,
},
};
this.eyes = 0;
this.offset = 0;
this.timer = 0;
this.state = 'base';
this.setup();
};
_Santa.prototype = {
setup: function () {
this.arms.left.x1 = this.x + this.diameter * 0.18;
this.arms.left.y1 = this.y - this.diameter * 0.25;
this.arms.left.x2 = this.x + this.diameter * 0.18;
this.arms.left.y2 = this.y + this.diameter * 0.0;
this.arms.left.x3 = this.x + this.diameter * 0.18;
this.arms.left.y3 = this.y + this.diameter * 0.3;
this.arms.left.x4 = this.x + this.diameter * 0.18;
this.arms.left.y4 = this.y + this.diameter * 0.55;
this.arms.right.x1 = this.x - this.diameter * 0.37;
this.arms.right.y1 = this.y - this.diameter * 0.25;
this.arms.right.x2 = this.x - this.diameter * 0.37;
this.arms.right.y2 = this.y + this.diameter * 0.0;
this.arms.right.x3 = this.x - this.diameter * 0.37;
this.arms.right.y3 = this.y + this.diameter * 0.3;
this.arms.right.x4 = this.x - this.diameter * 0.37;
this.arms.right.y4 = this.y + this.diameter * 0.55;
},
update: function () {
switch (this.state) {
case 'pull':
this.timer++;
this.offset = constrain(this.offset + 0.5, 0, 80);
this.eyes = constrain(this.eyes + 0.1, 0, 20);
if (this.offset === 80) {
this.state = 'base';
this.timer = 0;
}
break;
case 'base':
this.timer++;
this.offset = lerp(this.offset, 0, 0.1);
this.eyes = lerp(this.eyes, 0, 0.1);
if (this.timer === 180) {
this.state = 'pull';
this.timer = 0;
}
break;
}
},
draw: function () {
pushMatrix();
translate(this.offset, 0);
stroke(195, 70, 90);
strokeWeight(40);
bezier(
this.arms.right.x1,
this.arms.right.y1,
this.arms.right.x2 + this.offset,
this.arms.right.y2,
this.arms.right.x3 + this.offset,
this.arms.right.y3,
this.arms.right.x4,
this.arms.right.y4 - this.offset * 0.3
);
noStroke();
fill(50);
ellipse(
this.arms.right.x4,
this.arms.right.y4 - this.offset * 0.3,
40,
40
);
fill(255);
pushMatrix();
translate(
bezierPoint(
this.arms.right.x1,
this.arms.right.x2 + this.offset,
this.arms.right.x3 + this.offset,
this.arms.right.x4,
0.9
),
bezierPoint(
this.arms.right.y1,
this.arms.right.y2,
this.arms.right.y3,
this.arms.right.y4 - this.offset * 0.3,
0.9
)
);
let dx = bezierTangent(
this.arms.right.x1,
this.arms.right.x2 + this.offset,
this.arms.right.x3 + this.offset,
this.arms.right.x4,
0.9
);
let dy = bezierTangent(
this.arms.right.y1,
this.arms.right.y2,
this.arms.right.y3,
this.arms.right.y4 - this.offset * 0.3,
0.9
);
rotate(radians(90) + atan2(dy, dx));
rectMode(CENTER);
rect(0, 0, 42, 20);
rectMode(CORNER);
popMatrix();
pushMatrix();
translate(-this.offset, 0);
noFill();
stroke(195, 70, 90);
strokeWeight(20);
bezier(
this.x - this.diameter * 0.15 + this.offset,
this.y + this.diameter * 0.35,
this.x - this.diameter * 0.15,
this.y + this.diameter * 0.6,
this.x - this.diameter * 0.15,
this.y + this.diameter * 0.8,
this.x - this.diameter * 0.15,
this.y + this.diameter * 1
);
bezier(
this.x + this.diameter * 0.15 + this.offset,
this.y + this.diameter * 0.35,
this.x + this.diameter * 0.15 + this.offset * 0.2,
this.y + this.diameter * 0.6,
this.x + this.diameter * 0.15 + this.offset * 0.2,
this.y + this.diameter * 0.8,
this.x + this.diameter * 0.15 + this.offset * 0.4,
this.y + this.diameter * 1
);
popMatrix();
noStroke();
fill(215, 70, 85);
ellipse(this.x, this.y, this.diameter, this.diameter);
stroke(195, 70, 90);
strokeWeight(40);
bezier(
this.arms.left.x1,
this.arms.left.y1,
this.arms.left.x2 + this.offset,
this.arms.left.y2,
this.arms.left.x3 + this.offset,
this.arms.left.y3,
this.arms.left.x4,
this.arms.left.y4 - this.offset * 0.3
);
noStroke();
fill(50);
ellipse(
this.arms.left.x4,
this.arms.left.y4 - this.offset * 0.3,
40,
40
);
fill(255);
pushMatrix();
translate(
bezierPoint(
this.arms.left.x1,
this.arms.left.x2 + this.offset,
this.arms.left.x3 + this.offset,
this.arms.left.x4,
0.9
),
bezierPoint(
this.arms.left.y1,
this.arms.left.y2,
this.arms.left.y3,
this.arms.left.y4 - this.offset * 0.3,
0.9
)
);
dx = bezierTangent(
this.arms.left.x1,
this.arms.left.x2 + this.offset,
this.arms.left.x3 + this.offset,
this.arms.left.x4,
0.9
);
dy = bezierTangent(
this.arms.left.y1,
this.arms.left.y2,
this.arms.left.y3,
this.arms.left.y4 - this.offset * 0.3,
0.9
);
rotate(radians(90) + atan2(dy, dx));
rectMode(CENTER);
rect(0, 0, 42, 20);
rectMode(CORNER);
popMatrix();
pushMatrix();
translate(
this.x - this.diameter * 0.2,
this.y - this.diameter * 0.35
);
rotate(radians(this.offset * 0.2));
noStroke();
fill(240, 195, 195);
rect(
-this.diameter * 0.29,
-this.diameter * 0.2,
this.diameter * 0.49,
this.diameter * 0.5
);
ellipse(
this.diameter * 0.19,
-this.diameter * 0.08,
this.diameter * 0.1,
this.diameter * 0.1
);
stroke(255);
strokeWeight(3);
line(
-this.diameter * 0.23,
-this.diameter * 0.11,
-this.diameter * 0.18,
-this.diameter * 0.11 + this.eyes * 0.2
);
line(
-this.diameter * 0.05,
-this.diameter * 0.11 + this.eyes * 0.2,
-this.diameter * -0.0,
-this.diameter * 0.11
);
noStroke();
fill(40);
ellipse(
-this.diameter * 0.2,
-this.diameter * 0.05,
this.diameter * 0.04,
this.diameter * 0.04 -
constrain(this.eyes, 0, this.diameter * 0.03)
);
ellipse(
-this.diameter * 0.03,
-this.diameter * 0.05,
this.diameter * 0.04,
this.diameter * 0.04 -
constrain(this.eyes, 0, this.diameter * 0.03)
);
noStroke();
fill(255);
ellipse(
this.diameter * 0.19,
-this.diameter * 0.45,
this.diameter * 0.18,
this.diameter * 0.18
);
fill(215, 70, 85);
arc(
-this.diameter * 0.08,
-this.diameter * 0.25,
this.diameter * 0.4,
this.diameter * 0.4,
radians(180),
radians(360)
);
rect(
-this.diameter * 0.08,
-this.diameter * 0.45,
this.diameter * 0.28,
this.diameter * 0.22,
0,
10,
0,
0
);
stroke(255);
strokeWeight(20);
line(
-this.diameter * 0.28,
-this.diameter * 0.22,
this.diameter * 0.19,
-this.diameter * 0.22
);
noStroke();
fill(255);
rect(
-this.diameter * 0.21,
this.diameter * 0.05,
this.diameter * 0.24,
this.diameter * 0.2,
this.diameter * 1
);
ellipse(
-this.diameter * 0.08,
this.diameter * 0.4,
this.diameter * 0.59,
this.diameter * 0.59
);
stroke(255);
strokeWeight(18);
line(
this.diameter * 0.16,
this.diameter * 0.01,
this.diameter * 0.16,
this.diameter * 0.38
);
stroke(240, 195, 195);
strokeWeight(17);
line(
this.diameter * 0.065,
this.diameter * 0.1,
this.diameter * 0.065,
this.diameter * 0.145
);
noStroke();
fill(245, 130, 130);
ellipse(
-this.diameter * 0.12,
-this.diameter * -0.03,
this.diameter * 0.08,
this.diameter * 0.08
);
noStroke();
fill(40);
rect(
-this.diameter * 0.17,
this.diameter * 0.11,
this.diameter * 0.15,
this.diameter * 0.09 + this.offset * 0.1,
7,
7,
0,
0
);
arc(
-this.diameter * 0.095,
this.diameter * 0.195 + this.offset * 0.1,
this.diameter * 0.145,
this.diameter * 0.08,
0,
radians(180)
);
fill(215, 70, 85);
rect(
-this.diameter * 0.105,
this.diameter * 0.124 + this.offset * 0.015,
this.diameter * 0.085,
this.diameter * 0.1,
5,
5,
15,
5
);
popMatrix();
popMatrix();
},
run: function () {
this.update();
this.draw();
},
};
return _Santa;
})();
const santa = new Santa({
x: 400,
y: 430,
diameter: 180,
});
const smoke = [];
const snow = (function () {
const arr = [];
for (let i = 0; i < 50; i++) {
arr.push({
x: random(width),
y: random(height),
diameter: random(3, 8),
vx: random(-0.3, 0.3),
vy: random(0.5, 1),
});
}
return arr;
})();
const aerial = {
x1: 270,
y1: 600,
x2: 270,
y2: 450,
x3: 270,
y3: 250,
x4: 270,
y4: 60,
x2_base: 270,
x3_base: 270,
x2_off: 0,
x3_off: 0,
};
const chimney = (function () {
background(0, 0, 0, 0);
stroke(255);
strokeWeight(30);
line(90, 485, 120, 485);
line(155, 485, 223, 485);
noStroke();
fill(195, 70, 90);
rect(70, 485, 170, 15);
rect(80, 500, 150, 100);
fill(215, 70, 85);
for (let x = 0; x < 4; x++) {
for (let y = 0; y < 5; y++) {
if (y % 2 === 0) {
rect(83 + x * 37.5, 500 + y * 20, 32, 15);
} else if (x < 3) {
rect(83 + 37.5 / 2 + x * 37.5, 500 + y * 20, 32, 15);
} else {
rect(83, 500 + y * 20, 14, 15);
rect(83 + 18 + x * 37.5, 500 + y * 20, 14, 15);
}
}
}
stroke(255);
strokeWeight(30);
line(55, 600, 65, 600);
line(90, 600, 140, 600);
line(210, 600, 250, 600);
return get();
})();
const present = (function () {
background(0, 0, 0, 0);
pushMatrix();
translate(150, 450);
rotate(radians(10));
translate(-145, -450);
noStroke();
fill(132, 232, 135);
rect(100, 400, 100, 100);
fill(5, 5, 5, 70);
rect(100, 415, 100, 5);
fill(89, 189, 92, 150);
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 4; j++) {
ellipse(110 + i * 20, 430 + j * 20, 10, 10);
}
}
fill(90, 209, 90);
rect(95, 400, 110, 15);
fill(235, 225, 120, 180);
rect(145, 400, 10, 100);
rect(100, 448, 100, 10);
stroke(212, 202, 111);
strokeWeight(1);
fill(235, 225, 120, 250);
triangle(150, 400, 120, 390, 130, 380);
triangle(150, 400, 180, 390, 170, 380);
ellipse(150, 395, 13, 10);
popMatrix();
return get();
})();
function app() {
let dx, dy;
background(205, 235, 240);
image(present, 0, 0);
for (let i = smoke.length - 1; i >= 0; i--) {
const puff = smoke[i];
fill(255, puff.opacity);
rect(puff.x, puff.y, puff.w, puff.h, 10);
puff.x += puff.vx;
puff.y += puff.vy;
puff.w = constrain(puff.w * 0.997, 0, puff.w);
puff.opacity = constrain(puff.opacity - 0.75, 0, 255);
if (puff.opacity === 0) {
smoke.splice(i, 1);
}
}
image(chimney, 0, 0);
santa.run();
noStroke();
fill(215, 70, 85);
let toungeX = bezierPoint(
aerial.x1,
aerial.x2,
aerial.x3,
aerial.x4,
0.415
);
let toungeY = bezierPoint(
aerial.y1,
aerial.y2,
aerial.y3,
aerial.y4,
0.415
);
let mouthX = santa.x - santa.diameter * 0.315 + santa.offset;
let mouthY = santa.y - santa.diameter * 0.185;
let diff = abs(mouthX - toungeX);
ellipse(5 + toungeX, toungeY + 3, 20, 25);
noFill();
stroke(215, 70, 85);
strokeWeight(17);
bezier(
toungeX + 5,
toungeY,
toungeX + diff * 0.33,
toungeY +
santa.diameter * 0.15 -
constrain(santa.offset, 0, santa.diameter * 0.15),
mouthX - diff * 0.33,
mouthY +
santa.diameter * 0.15 -
constrain(santa.offset, 0, santa.diameter * 0.15),
mouthX,
mouthY
);
aerial.x2 = aerial.x2_base + santa.offset * 0.5;
aerial.x3 = aerial.x3_base + santa.offset * 0.3;
noStroke();
fill(255);
arc(270, 600, 60, 60, radians(180), radians(360));
noFill();
stroke(255);
strokeWeight(14);
bezier(
aerial.x1,
aerial.y1,
aerial.x2,
aerial.y2,
aerial.x3,
aerial.y3,
aerial.x4,
aerial.y4
);
pushMatrix();
translate(
bezierPoint(aerial.x1, aerial.x2, aerial.x3, aerial.x4, 0.98),
bezierPoint(aerial.y1, aerial.y2, aerial.y3, aerial.y4, 0.98)
);
dx = bezierTangent(
aerial.x1,
aerial.x2,
aerial.x3,
aerial.x4,
0.98
);
dy = bezierTangent(
aerial.y1,
aerial.y2,
aerial.y3,
aerial.y4,
0.98
);
rotate(radians(90) + atan2(dy, dx));
noStroke();
fill(255);
ellipse(0, 0, 34, 15);
stroke(255);
strokeWeight(5);
line(-50, 0, 50, 0);
popMatrix();
pushMatrix();
translate(
bezierPoint(aerial.x1, aerial.x2, aerial.x3, aerial.x4, 0.9),
bezierPoint(aerial.y1, aerial.y2, aerial.y3, aerial.y4, 0.9)
);
dx = bezierTangent(aerial.x1, aerial.x2, aerial.x3, aerial.x4, 0.9);
dy = bezierTangent(aerial.y1, aerial.y2, aerial.y3, aerial.y4, 0.9);
rotate(radians(90) + atan2(dy, dx));
noStroke();
fill(255);
ellipse(0, 0, 34, 15);
stroke(255);
strokeWeight(5);
line(-150, 0, 150, 0);
popMatrix();
pushMatrix();
translate(
bezierPoint(aerial.x1, aerial.x2, aerial.x3, aerial.x4, 0.8),
bezierPoint(aerial.y1, aerial.y2, aerial.y3, aerial.y4, 0.8)
);
dx = bezierTangent(aerial.x1, aerial.x2, aerial.x3, aerial.x4, 0.8);
dy = bezierTangent(aerial.y1, aerial.y2, aerial.y3, aerial.y4, 0.8);
rotate(radians(90) + atan2(dy, dx));
noStroke();
fill(255);
ellipse(0, 0, 34, 15);
stroke(255);
strokeWeight(5);
line(-120, 0, 120, 0);
popMatrix();
pushMatrix();
translate(
bezierPoint(aerial.x1, aerial.x2, aerial.x3, aerial.x4, 0.75),
bezierPoint(aerial.y1, aerial.y2, aerial.y3, aerial.y4, 0.75)
);
dx = bezierTangent(
aerial.x1,
aerial.x2,
aerial.x3,
aerial.x4,
0.75
);
dy = bezierTangent(
aerial.y1,
aerial.y2,
aerial.y3,
aerial.y4,
0.75
);
rotate(radians(90) + atan2(dy, dx));
noStroke();
fill(255);
ellipse(0, 0, 34, 15);
stroke(255);
strokeWeight(5);
line(-120, 0, 120, 0);
popMatrix();
noStroke();
fill(255);
ellipse(
bezierPoint(aerial.x1, aerial.x2, aerial.x3, aerial.x4, 0.3),
bezierPoint(aerial.y1, aerial.y2, aerial.y3, aerial.y4, 0.3),
20,
20
);
ellipse(
bezierPoint(aerial.x1, aerial.x2, aerial.x3, aerial.x4, 0.6),
bezierPoint(aerial.y1, aerial.y2, aerial.y3, aerial.y4, 0.6),
20,
20
);
noStroke();
fill(255);
for (let i = snow.length - 1; i >= 0; i--) {
const flake = snow[i];
ellipse(flake.x, flake.y, flake.diameter, flake.diameter);
flake.x += flake.vx;
flake.y += flake.vy;
if (flake.y - flake.diameter > height) {
flake.x = random(width);
flake.y = -10;
flake.diameter = random(3, 8);
flake.vx = random(-0.3, 0.3);
flake.y = random(0.5, 1);
}
}
if (frameCount % 20 === 0) {
const diameter = random(30, 50);
smoke.push({
x: random(100, 170),
y: 485,
vx: random(-1, 0.5),
vy: random(-1, -0.5),
w: diameter,
h: diameter * random(0.4, 0.6),
opacity: random(200, 250) | 0,
});
}
}
draw = function () {
app();
};
}
};
var canvas = document.getElementById('canvas');
var processingInstance = new Processing(canvas, sketchProc);
</script>
</body>
</html>