2 483 Codepen

Перпендикулярные линии на canvas

Анимированные перпендикулярные линии на канвасе с привязкой к движению курсора.


HTML

<canvas class="js-draw"></canvas>

SCSS

html,
body {
  margin: 0;
  padding: 0;
}

body {
  width: 100vw;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
JS
Дополнительная библиотека
//cdnjs.cloudflare.com/ajax/libs/tinycolor/1.4.1/tinycolor.min.js
и сам скрипт, также включающий библиотеку для анимаций (anime.js)
import anime from '//unpkg.com/animejs@3.0.1/lib/anime.es.js';

// https://ptsjs.org/
const TAU = Math.PI * 2;

const canvas = document.querySelector('.js-draw');

class Stage {
  constructor(canvas, width, height) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');

    this.width = width;
    this.height = height;

    this.options = {
      rotation: Math.atan2(width, height) + Math.PI / 2,
      backgroundColor: '#011627',
      lineColor: '#FDFFFC',
      pointColor1: '#41EAD4',
      pointColor2: '#F71735',
    };

    this.points = [];

    this.line = { from: { x: 0, y: height * 0.5 }, to: { x: width, y: height * 0.5 } };

    this.animation = null;
  }

  get rotation() {
    return this.options.rotation;
  }

  get width() {
    return this.canvas.width;
  }

  get height() {
    return this.canvas.height;
  }

  get hypo() {
    return Math.hypot(this.width, this.height);
  }

  get widthHalf() {
    return this.width * 0.5;
  }

  get heightHalf() {
    return this.height * 0.5;
  }

  set width(w) {
    this.canvas.width = w;
  }

  set height(h) {
    this.canvas.height = h;
  }

  init() {
    this.generate();

    this.canvas.addEventListener('mousemove', e => this.onmousemove(e));
    this.canvas.addEventListener('mouseenter', e => this.onMoueEnter(e));
    this.canvas.addEventListener('mouseleave', e => this.onMouseLeave(e));
  }

  generate() {
    this.points = new Array(100).fill().map((_, i) => {
      const r = (this.heightHalf * 0.5) + Math.random() * (this.heightHalf / 2);
      const c = i % 2 === 0 ? this.options.pointColor1 : this.options.pointColor2;

      const point = {
        r,
        a: Math.random() * TAU,
        s: 0.0005 + (Math.random() * 0.0008),
        c,
      };

      return point;
    });
  }

  setSize(w, h) {
    this.width = w;
    this.height = h;
  }

  onmousemove(e) {
    const x = e.clientX - e.target.offsetLeft;
    const y = e.clientY - e.target.offsetTop;

    this.options.rotation = Math.atan2(this.heightHalf - y, this.widthHalf - x);
  }

  onMoueEnter() {
    if (this.animation) {
      this.animation.pause();
    }

    this.animation = null;
  }

  onMouseLeave() {
    this.animate();
  }

  animate() {
    const diff = -Math.PI + (Math.random() * TAU);
    const angle = this.rotation + diff;

    this.animation = anime({
      targets: this.options,
      duration: 3000,
      delay: 3000,
      rotation: angle,
      easing: 'easeInOutSine',
      complete: () => {
        this.animate();
      },
    });
  }

  drawLine(from, to, color, width = 1) {
    this.ctx.strokeStyle = color;

    this.ctx.beginPath();
    this.ctx.lineWidth = width;
    this.ctx.moveTo(from.x, from.y);
    this.ctx.lineTo(to.x, to.y);
    this.ctx.stroke();
    this.ctx.closePath();
  }

  drawPoint(point) {
    const { from, to } = this.line;
    const wh = this.canvas.width * 0.5;
    const hh = this.canvas.height * 0.5;

    point.a += point.s;
    point.x = wh + (Math.cos(point.a) * point.r);
    point.y = hh + (Math.sin(point.a) * point.r);

    const denominator = Math.hypot(to.x - from.x, to.y - from.y);
    const numerator = ((to.y - from.y) * point.x) - ((to.x - from.x) * point.y) + (to.x * from.y) - (to.y * from.x);
    const distance = numerator / denominator;

    const pointAngle = Math.atan2(to.y - from.y, to.x - from.x) + (Math.PI / 2);
    const pointRadius = 0.5 + Math.abs(distance / this.heightHalf) * 3;
    const lineWidth = 0.5 + (Math.abs(distance / this.heightHalf) - 0.5);

    const toX = point.x + (Math.cos(pointAngle) * distance);
    const toY = point.y + (Math.sin(pointAngle) * distance);

    this.ctx.save();
    this.ctx.globalAlpha = point.o;
    this.drawLine(point, { x: toX, y: toY }, this.options.lineColor, lineWidth);
    this.ctx.restore();

    this.ctx.beginPath();
    this.ctx.fillStyle = point.c;
    this.ctx.arc(point.x, point.y, pointRadius, 0, TAU);
    this.ctx.fill();
    this.ctx.closePath();
  };

  updateSeparator() {
    this.line.from.x = this.widthHalf + (Math.cos(this.rotation) * this.width);
    this.line.from.y = this.heightHalf + (Math.sin(this.rotation) * this.width);
    this.line.to.x = this.widthHalf + (Math.cos(this.rotation + Math.PI) * this.width);
    this.line.to.y = this.heightHalf + (Math.sin(this.rotation + Math.PI) * this.width);
  }

  run() {
    this.ctx.globalCompositeOperation = 'source-over';
    this.ctx.fillStyle = this.options.backgroundColor;
    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

    this.updateSeparator();

    this.points.forEach((p, i) => this.drawPoint(p, i));

    this.ctx.save();
    this.ctx.translate(this.widthHalf, this.heightHalf);
    this.ctx.rotate(this.rotation);
    this.ctx.globalCompositeOperation = 'difference';
    this.ctx.fillStyle = this.options.lineColor;
    this.ctx.fillRect(-this.hypo / 2, 0, this.hypo, this.hypo);
    this.ctx.restore();

    const circleRadius = 10;
    this.ctx.beginPath();
    this.ctx.fillStyle = '#fce9d5';
    this.ctx.arc(this.widthHalf, this.heightHalf, circleRadius, 0, TAU);
    this.ctx.fill();
    this.ctx.closePath();

    const circleRadiusInner = circleRadius * 0.5;
    this.ctx.beginPath();
    this.ctx.fillStyle = this.options.backgroundColor;
    this.ctx.arc(this.widthHalf, this.heightHalf, circleRadiusInner, 0, TAU);
    this.ctx.fill();
    this.ctx.closePath();

    requestAnimationFrame(() => this.run());
  }
}

const stage = new Stage(canvas, window.innerWidth, window.innerHeight);

stage.init();
stage.run();


window.addEventListener('resize', () => {
  stage.setSize(window.innerWidth, window.innerHeight);
  stage.generate();
});

Комментарии

  • Facebook
  • Вконтакте

Похожие статьи