<canvas id='c'></canvas>
body { margin: 0px; }
var canvas = document.getElementById('c');
var c = canvas.getContext('2d');
var width = window.innerWidth;
var height = window.innerHeight;
var mousePressed = false;
var mx, my;
var cx, cy;
var angle = -90 * 2;
var angle_t = 90;
var FOV = 512;
var DIST = 512;
var FLAKES = 768;
var prjPoints = [];
var snow = [];
canvas.width = width;
canvas.height = height;
cx = width / 2;
cy = 3 * height / 4;
var radius = height / 6;
for (var i = 0; i < FLAKES; i++) {
snow.push({
x: Math.random() - 0.5,
y: Math.random() * height - height,
z: Math.random() - 0.5,
radius: (Math.random() + 0.2) * 8
});
}
window.addEventListener('mousedown', function(e) {
mousePressed = true;
mx = e.pageX;
});
window.addEventListener('mouseup', function() {
mousePressed = false;
});
window.addEventListener('mousemove', function(e) {
if (mousePressed) {
angle_t += (mx - e.pageX) * 4;
if (angle_t >= 360) {
angle_t -= 360;
angle -= 360;
}
if (angle_t <= -180) {
angle_t += 360;
angle += 360;
}
mx = e.pageX;
}
});
function render(ts) {
c.fillStyle = '#28a';
c.fillRect(0, 0, width, height);
angle += (angle_t - angle) / 16;
var positions = [];
var loopY = cy;
var loopR = radius;
for (var i = 0; i < 3; i++) {
var newRadius = loopR * 0.8;
positions.push(cx + loopR);
positions.push(loopY);
positions.push(loopR);
loopY -= (loopR + newRadius) * 0.75;
loopR = newRadius;
}
computeArms(positions);
animateSnow();
drawSnow(true);
drawFloor();
drawSnowmanArms(true);
if ((angle >= 0 && angle < 180) || (angle > 360 && angle < 540)) {
drawSnowmanBody(positions);
drawSnowmanEyes(positions);
drawSnowmanNose(positions);
} else {
drawSnowmanEyes(positions);
drawSnowmanNose(positions);
drawSnowmanBody(positions);
}
drawSnowmanArms(false);
drawSnowmanScarf(positions);
drawSnow(false);
requestAnimationFrame(render);
}
function hexColor(c) {
if(c == 10) return 'a';
if(c == 11) return 'b';
if(c == 12) return 'c';
if(c == 13) return 'd';
if(c == 14) return 'e';
if(c == 15) return 'f';
return c;
}
function drawSnowmanScarf(positions) {
c.save();
c.globalAlpha = 0.94;
var angleDispl = angle * Math.PI/180.0;
var yoff = positions[positions.length - 3 + 1] + positions[positions.length - 3 + 2];
yoff += positions[positions.length - 6 + 1] - positions[positions.length - 6 + 2];
yoff /= 2;
c.translate(width/2, yoff);
var r = positions[positions.length - 3 + 2] * 1.2;
var SCARF_HEIGHT = 10 * positions[positions.length - 3 + 2] * 0.06;;
var HSEGMENTS = 20;
var VSEGMENTS = 5;
var h = SCARF_HEIGHT / VSEGMENTS;
for (var i = 0; i < VSEGMENTS; i++) {
var y0 = (i * h - VSEGMENTS * h * 0.5) | 0;
var y1 = ((i + 1) * h - VSEGMENTS * h * 0.5 + 2) | 0;
for (var j = 0; j < HSEGMENTS; j++) {
var angle0 = (j * ((2 * Math.PI) / HSEGMENTS) + angleDispl) % (2 * Math.PI);
var angle1 = ((j + 1) * ((2 * Math.PI) / HSEGMENTS) + angleDispl) % (2 * Math.PI);
var x0 = r * Math.sin(angle0);
var z0 = r * Math.cos(angle0);
var x1 = r * Math.sin(angle1);
var z1 = r * Math.cos(angle1);
var xp0 = (FOV * x0 / (z0 + DIST)) |0;
var xp1 = (FOV * x1 / (z1 + DIST)) |0;
var yp0 = (FOV * y0 / (z0 + DIST)) |0;
var yp1 = (FOV * y1 / (z1 + DIST)) |0;
var yl = (i + 0.5)/(VSEGMENTS * 0.5);
if (yl > 1) yl = 1 - (yl - 1);
var zl = Math.abs(((z0 + z1) * 0.5) / r);
var alpha = yl * 0.3 + zl * 0.7;
c.beginPath();
c.moveTo(xp0, yp0);
c.lineTo(xp1, yp0);
c.lineTo(xp1, yp1);
c.lineTo(xp0, yp1);
c.lineTo(xp0, yp1);
if(angle0 > Math.PI/2 && angle0 < Math.PI + Math.PI/2) {
var colR = hexColor((14 * alpha) | 0);
var colG = hexColor((14 * alpha) | 0);
var colB = hexColor((2 * alpha) | 0);
var fillColor = '#' + colR + '' + colG + '' + colB;
c.fillStyle = fillColor;
c.fill();
c.lineWidth = 4;
if (i == 0) {
c.beginPath();
c.moveTo(xp0, yp0);
c.lineTo(xp1, yp0);
c.stroke();
} else if (i == VSEGMENTS - 1) {
c.beginPath();
c.moveTo(xp0, yp1);
c.lineTo(xp1, yp1);
c.stroke();
}
c.lineWidth = 1;
} else {
// c.stroke();
}
}
}
c.restore();
}
function computeArms(positions) {
var i = positions.length - 6;
var points = [];
//left
points.push(positions[i + 2]);
points.push(positions[i + 1]) ;
points.push(0);
points.push(positions[i + 2] * 2);
points.push(positions[i + 1] * 0.9);
points.push(positions[i + 2] * 0.5);
points.push(positions[i + 2] * 3);
points.push(positions[i + 1] * 1.2);
points.push(0);
points.push(positions[i + 2] * 3.3);
points.push(positions[i + 1] * 1.3);
points.push(-positions[i + 2] * 0.2);
points.push(positions[i + 2] * 2.75);
points.push(positions[i + 1] * 1.3);
points.push(-positions[i + 2] * 0.2);
//right
points.push(-positions[i + 2]);
points.push(positions[i + 1]);
points.push(0);
points.push(-positions[i + 2] * 2);
points.push(positions[i + 1] * 0.9);
points.push(positions[i + 2] * 0.5);
points.push(-positions[i + 2] * 3);
points.push(positions[i + 1] * 1.2);
points.push(0);
points.push(-positions[i + 2] * 3.3);
points.push(positions[i + 1] * 1.3);
points.push(0);
points.push(-positions[i + 2] * 2.75);
points.push(positions[i + 1] * 1.3);
points.push(0);
var rotPoints = [];
var cos = Math.cos(Math.PI * angle / 180.0);
var sin = Math.sin(Math.PI * angle / 180.0);
for(var j = 0; j < points.length; j += 3) {
rotPoints[j + 0] = points[j + 0] * sin - points[j + 2] * cos;
rotPoints[j + 1] = points[j + 1];
rotPoints[j + 2] = points[j + 0] * cos + points[j + 2] * sin;
}
for(var j = 0; j < rotPoints.length/3; j++) {
prjPoints[j * 2 + 0] = width/2 + FOV * rotPoints[j * 3 + 0] / (DIST + rotPoints[j * 3 + 2]);
prjPoints[j * 2 + 1] = points[j * 3 + 1];
}
}
function drawSnowmanArms(before) {
c.lineWidth = 5;
c.beginPath();
i = 0;
if((!before && angle >= 90 && angle <= 270) || before) {
c.moveTo(prjPoints[i ], prjPoints[i + 1]); i += 2;
c.bezierCurveTo(prjPoints[i ], prjPoints[i + 1], prjPoints[i + 2], prjPoints[i + 3], prjPoints[i + 4], prjPoints[i + 5]);
i+=2;
c.moveTo(prjPoints[i ], prjPoints[i + 1]); i += 4;
c.lineTo(prjPoints[i ], prjPoints[i + 1]); i += 2;
} else {
i += 10;
}
if((!before && angle >= 270) || (!before && angle <= 90) || before) {
c.moveTo(prjPoints[i ], prjPoints[i + 1]); i += 2;
c.bezierCurveTo(prjPoints[i ], prjPoints[i + 1], prjPoints[i + 2], prjPoints[i + 3], prjPoints[i + 4], prjPoints[i + 5]);
i += 2;
c.moveTo(prjPoints[i ], prjPoints[i + 1]); i += 4;
c.lineTo(prjPoints[i ], prjPoints[i + 1]); i += 2;
}
c.stroke();
c.lineWidth = 1;
}
function drawSnowmanEyes(positions) {
var i = positions.length - 3;
var r = positions[i + 2] * 0.1;
var s0 = Math.sin(Math.PI * (angle + 15) / 180.0);
var c0 = Math.cos(Math.PI * (angle + 15) / 180.0);
var s1 = Math.sin(Math.PI * (angle - 15) / 180.0);
var c1 = Math.cos(Math.PI * (angle - 15) / 180.0);
var z0 = positions[i + 2] * s0;
var x0 = positions[i + 2] * c0;
var z1 = positions[i + 2] * s1;
var x1 = positions[i + 2] * c1;
var px1 = FOV * x0 / (DIST + z0);
var px2 = FOV * x1 / (DIST + z1);
c.fillStyle = '#000';
c.beginPath();
c.arc(px1 + cx, positions[i + 1] - r, r, 0, 360);
c.arc(px2 + cx, positions[i + 1] - r, r, 0, 360);
c.fill();
}
function drawSnowmanNose(positions) {
var i = positions.length - 3;
var r = positions[i + 2] * 0.1;
var h = positions[i + 2] * 0.1;
var s0 = Math.sin(Math.PI * angle / 180.0);
var c0 = Math.cos(Math.PI * angle / 180.0);
var z0 = positions[i + 2] * s0;
var x0 = positions[i + 2] * c0;
var z1 = positions[i + 2] * 2 * s0;
var x1 = positions[i + 2] * 2 * c0;
var px0 = FOV * x0 / (DIST + z0);
var px1 = FOV * x1 / (DIST + z1);
c.lineWidth = r * 2 * 0.8;
c.strokeStyle = '#f40';
c.beginPath();
c.moveTo(positions[i + 0] - positions[i + 2] + px0, positions[i + 1] + r * 2);
c.lineTo(positions[i + 0] - positions[i + 2] + px1, positions[i + 1] + r * 2);
c.stroke();
c.fillStyle = '#f40';
c.beginPath();
c.arc(positions[i + 0] - positions[i + 2] + px0, positions[i + 1] + r * 2, r * 0.8, 0, 360);
c.arc(positions[i + 0] - positions[i + 2] + px1, positions[i + 1] + r * 2, r * 0.8, 0, 360);
c.fill();
c.strokeStyle = '#000';
c.lineWidth = 1;
}
function drawFloor() {
c.save();
c.scale(1, 0.2);
c.fillStyle = '#ddd';
c.beginPath();
c.arc(cx, (cy + radius) * 5, radius * 3, 0, 360);
c.fill();
c.restore();
}
function animateSnow() {
var cos = Math.cos(Math.PI * (-angle) / 180.0);
var sin = Math.sin(Math.PI * (-angle) / 180.0);
for (var i = 0; i < FLAKES; i++) {
snow[i].x += Math.random() * 0.006 - 0.003;
snow[i].y += Math.random() * 1.6;
snow[i].z += Math.random() * 0.006 - 0.003;
if (snow[i].y > height) {
snow[i] = {
x: Math.random() - 0.5,
y: 0,
z: Math.random() - 0.5,
radius: (Math.random() + 0.2) * 8
};
}
}
for (var i = 0; i < FLAKES; i++) {
snow[i].rx = snow[i].x * sin - snow[i].z * cos;
snow[i].rz = snow[i].x * cos + snow[i].z * sin;
}
for (var i = 0; i < FLAKES; i++) {
snow[i].prx = FOV * snow[i].rx / (DIST + snow[i].rz);
}
}
function drawSnow(before) {
c.fillStyle = '#ddee';
c.beginPath();
for (var i = 0; i < FLAKES; i++) {
var x = snow[i].prx * radius * 5 + cx;
var y = snow[i].y;
if (before && snow[i].rz <= 0) {
c.moveTo(x, y);
c.arc(x, y, snow[i].radius, 0, 360);
} else if (!before && snow[i].rz > 0) {
c.moveTo(x, y);
c.arc(x, y, snow[i].radius, 0, 360);
}
}
c.fill();
}
function drawSnowmanBody(positions) {
c.fillStyle = '#000';
c.beginPath();
for (var i = 0; i < 3; i++) {
c.moveTo(positions[i * 3], positions[i * 3 + 1]);
c.arc(cx, positions[i * 3 + 1], positions[i * 3 + 2] * 1.05, 0, 360, false);
}
c.fill();
c.fillStyle = '#fff';
c.beginPath();
for (var i = 0; i < 3; i++) {
c.moveTo(positions[i * 3], positions[i * 3 + 1]);
c.arc(cx, positions[i * 3 + 1], positions[i * 3 + 2], 0, 360, false);
}
c.fill();
}
requestAnimationFrame(render);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.