圣诞老人舔“铁”

Published on
/
/趣玩前端
<!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();

          /* jshint esnext: true */

          //santa object
          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 () {
                //left arm
                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;

                //right arm
                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);

                //right arm
                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
                );

                //right hand
                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();

                //legs
                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();

                //body
                noStroke();
                fill(215, 70, 85);
                ellipse(this.x, this.y, this.diameter, this.diameter);

                //left arm
                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
                );

                //left hand
                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();

                //head
                pushMatrix();
                translate(
                  this.x - this.diameter * 0.2,
                  this.y - this.diameter * 0.35
                );
                rotate(radians(this.offset * 0.2));

                //face and hat
                noStroke();
                fill(240, 195, 195);
                rect(
                  -this.diameter * 0.29,
                  -this.diameter * 0.2,
                  this.diameter * 0.49,
                  this.diameter * 0.5
                );
                //ear
                ellipse(
                  this.diameter * 0.19,
                  -this.diameter * 0.08,
                  this.diameter * 0.1,
                  this.diameter * 0.1
                );

                //eyebrows
                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
                );
                //eyes
                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)
                );

                //hat
                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
                );

                //beard
                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
                );

                //nose
                noStroke();
                fill(245, 130, 130);
                ellipse(
                  -this.diameter * 0.12,
                  -this.diameter * -0.03,
                  this.diameter * 0.08,
                  this.diameter * 0.08
                );

                //mouth
                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;
          })();

          //create instance of santa
          const santa = new Santa({
            x: 400,
            y: 430,
            diameter: 180,
          });

          //array for holding smoke objects
          const smoke = [];

          //initialize and set snow
          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;
          })();

          //points for the aerialel
          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,
          };

          //generate chimney image
          const chimney = (function () {
            background(0, 0, 0, 0);

            //snow at top of chimney
            stroke(255);
            strokeWeight(30);
            line(90, 485, 120, 485);
            line(155, 485, 223, 485);

            //chimney
            noStroke();
            fill(195, 70, 90);
            rect(70, 485, 170, 15);
            rect(80, 500, 150, 100);

            //bricks on chimney
            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);
                }
              }
            }

            //snow at bottom of chimney
            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);

            //main box
            noStroke();
            fill(132, 232, 135);
            rect(100, 400, 100, 100);
            //shadow
            fill(5, 5, 5, 70);
            rect(100, 415, 100, 5);

            //spots
            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);
              }
            }

            //lid
            fill(90, 209, 90);
            rect(95, 400, 110, 15);

            //cross
            fill(235, 225, 120, 180);
            rect(145, 400, 10, 100);
            rect(100, 448, 100, 10);

            //bow
            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();
          })();

          //main app
          function app() {
            let dx, dy;

            background(205, 235, 240);

            //display chimney
            image(present, 0, 0);

            //draw smoke
            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);
              }
            }

            //display chimney
            image(chimney, 0, 0);

            //santa
            santa.run();

            //tounge
            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);

            //dot on aerial
            ellipse(5 + toungeX, toungeY + 3, 20, 25);
            //stretched tounge
            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
            aerial.x2 = aerial.x2_base + santa.offset * 0.5;
            aerial.x3 = aerial.x3_base + santa.offset * 0.3;

            //draw aerial
            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
            );

            //snow
            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);
              }
            }

            //add smoke
            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>