timg.jpg
起源:为项目的403,404,500等页面添加背景,使页面更加生动有趣,下面开始讲流程吧。

技术栈:react + canvas + html + css

HTML 部份

  1. <div className={bg.pageBg}>
  2. <canvas ref={this.canvas}></canvas>
  3. </div>

CSS 部份

.pageBg{
  margin: 0;
  padding:0;
  height: cale(100vh - 40px);
  position: absolute;
  z-index: -1;
  cursor:none;
  overflow: hidden;
  background: #ffffff;
}

JS 部份

  • 定义属性和初始值

    //画布属性
    let canvas="",
      ctx="",
      //鼠标移动的属性
      mouseMoving= false,
      mouseMoveChecked= "",
      mouseX= 0,
      mouseY= 0,
      //存放线条
      dots= [],
      minDotsCo= 2,
      maxDotsCu= 50,
      //存放圆圈
      points= [],
      initPoints= 80;
    
  • 新建两个构造函数分别用于承载 线条 和 圆圈

    • 圆圈 ```jsx class Points{ constructor(id, x, y) { this.id = id; this.x = x; this.y = y; this.r = Math.floor(Math.random() 5) + 1; let opacity = (Math.floor(Math.random() 10) + 1) / 10 / 2; this.color = “rgba(192,192,192,” + opacity + “)”; } draw = ()=>{ ctx.fillStyle = this.color; ctx.shadowBlur = this.r 2; ctx.beginPath(); ctx.arc(this.x, this.y, this.r, 0, 2 Math.PI, false); ctx.closePath(); ctx.fill(); }; move = ()=>{ this.y -= .15; if (this.y <= -10) this.y = canvas.height + 10; this.draw(); }

}


   - 线条
```jsx
class Dot{
  constructor(id, x, y) {
    this.id = id;
    this.x = x;
    this.y = y;
    this.r = Math.floor(Math.random() *5) + 1;
    this.speed = .5;
    this.opacity = .5;
    //255,105,180
    this.color = "rgba(220,220,220," + this.opacity + ")";
    this.linkColor =  "rgba(220,220,220," + this.opacity/4 + ")";
    this.dir = Math.floor(Math.random() *140) + 200;
  }
  draw = ()=>{
    ctx.fillStyle = this.color;
    ctx.shadowBlur = this.r * 2;
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
    ctx.closePath();
    ctx.fill();
  }
  link = ()=>{
    if(this.id ==0) return;
    let pd1 = getPreDot(this.id, 1),
      pd2 = getPreDot(this.id, 2),
      pd3 = getPreDot(this.id, 3);
    if (!pd1) return;
    ctx.strokeStyle = this.linkColor;
    ctx.moveTo(pd1.x, pd1.y);
    ctx.beginPath();
    ctx.lineTo(this.x, this.y);
    if(pd2 !=false) ctx.lineTo(pd2.x, pd2.y);
    if(pd3 !=false) ctx.lineTo(pd3.x, pd3.y);

    ctx.stroke();
    ctx.closePath();

  }
  move = ()=>{

    this.opacity -= 0.005;
    if(this.opacity <= 0){
      this.die();
      return;
    }
    this.color = "rgba(220,220,220," + this.opacity +")";
    this.linkColor = "rgba(220,220,220," + this.opacity/4 + ")";
    this.x = this.x + Math.cos(this.dir * (Math.PI / 180)) * this.speed;
    this.y = this.y + Math.sin( this.dir* (Math.PI / 180)) * this.speed;

    this.draw();
    this.link();
  }
  die = ()=>{
    dots[this.id] = null;
    delete dots[this.id]
  }
}
function getPreDot(id, step1){
  if (id == 0 || id - step1 < 0) return false;
  if (typeof dots[id - step1] != "undefined") return dots[id - step1];
  else return false;
}

以上为圆圈和线条的构造函数,也可以把他们看做一个圆圈和一个线条的生命周期,当透明度opacity <=0是会触发die周期函数,不过这里我们想要圆圈一直存在则就不需要die周期函数,在鼠标移动期间会产生线条,然后逐渐消失。

在react的挂载周期开始初始化

记得在componentWillMount时移除监听事件

  componentDidMount() {
    canvas = this.canvas.current; //document.getElementById("canvas");
    ctx = canvas.getContext("2d");
    this.resize();
    this.init();
    window.addEventListener("resize", this.resize);
    window.addEventListener("mousemove", this.onmousemove);
  }
  resize = ()=>{
    canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
    canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
  };

  init = ()=>{
   let  width = canvas.width,
        height =  canvas.height;

    ctx.strokeStyle = "#FFFFFF";
    ctx.shadowColor = "#FFFFFF";
    for (let i = 0; i < initPoints; i++) {
      points[i] = new Points(i, Math.floor(Math.random() * width),Math.floor(Math.random() * height));
    }
    ctx.shadowBlur = 0;
    this.animate();

  }

执行动画的方法写在animate里面,requestAnimationFrame请求动画帧

  animate= ()=>{
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    for (let i in points) {
      points[i].move();
    }
    for (let i in dots) {
      dots[i].move();
    }
    this.drawIfMouseMoving();
    requestAnimationFrame(this.animate);
  };

drawIfMouseMoving 该方法用于在鼠标移动计算鼠标的位移差

  drawIfMouseMoving = ()=>{
    if(!mouseMoving) return false;
    if(dots.length ==0){
      dots[0] = new Dot(0, mouseX, mouseY);
      dots[0].draw();
      return false;
    }
    let preDot = getPreDot(dots.length, 1),
      prevX = preDot.x,
      prevY = preDot.y,
      diffX = Math.abs(prevX - mouseX),
      diffY = Math.abs(prevY - mouseY);

    if(diffX < minDotsCo || diffY < minDotsCo) return false;
    let xChange = Math.random() > .5 ? -1 :1;
    xChange = xChange * Math.floor(Math.random() * maxDotsCu) + 1;
    let yChange = Math.random() > .5 ? -1 : 1;
    yChange = yChange * Math.floor(Math.random() * maxDotsCu) + 1;
    dots[dots.length] = new Dot(dots.length, mouseX + xChange, mouseY + yChange);
    dots[dots.length - 1].draw();
    dots[dots.length - 1].link();
  };