js实现红包雨功能(canvas,react,ts),包括图片不规则旋转、大小、转速、掉落速度控制、屏幕最大红包数量控制等功能

介绍

  1. 本文功能由canvas实现红包雨功能(index.tsx
  2. 本文为react的ts版。如有其他版本需求可评论区
  3. 观赏地址,需过墙
ts 复制代码
import React, { Component } from 'react';
// import './index.css';
import moneyx from '@/assets/images/RedEnvelopeRain/ball1.png';
import money1 from '@/assets/images/RedEnvelopeRain/money1.webp';
import money2 from '@/assets/images/pointCon/机器人.png';
import money3 from '@/assets/images/pointCon/无人机.png';
import {
  loadImage,
  positionX,
  randomRange,
} from './utils';
type minMax = [number, number];
const config = {
  // speed value area
  speedLimit: [1, 4] as minMax,
  // display quantity
  density: [5, 15] as minMax,
  imgInfo: {
    // image width
    w: 50,
    // image height
    h: 50,
    // image rotate value area
    randomLimit: [0.5, 1] as minMax,
  },
  // image number max show
  numberMaxLimit: 200,
};
const { speedLimit, imgInfo, numberMaxLimit, density } = config;

export default class CanvasCom extends Component {
  // ratio = null;
  ctx: any = null;

  wid = 0;
  hei = 0;
  packedArr: ReturnType<typeof this.createPack>[] = [];
  // display quantity
  density = {
    min: density[0],
    max: density[1],
  };

  clearTime: any = null;
  img: any = [];
  loadingImageArr = [money1, moneyx, money2, money3]

  componentDidMount() {
    this.loadingImg();
  }
  loadingImg() {
    Promise.all(this.loadingImageArr.map(v => loadImage(v))).then((res) => {
      this.img = res;
      this.wid = window.innerWidth;
      this.hei = window.innerHeight;
      this.initCanvas();
      this.start();
    });
  }
  getPixelRatio = (context: any) => {
    const backingStore =
      context.backingStorePixelRatio ||
      context.webkitBackingStorePixelRatio ||
      context.mozBackingStorePixelRatio ||
      context.msBackingStorePixelRatio ||
      context.oBackingStorePixelRatio ||
      context.backingStorePixelRatio ||
      1;
    return (window.devicePixelRatio || 1) / backingStore;
  };
  initCanvas() {
    const canvas: HTMLCanvasElement | any = document.getElementById('canvas');
    canvas.width = this.wid;
    canvas.height = this.hei;
    if (canvas.getContext) {
      // 判断是否有此方法,如果有才能进入
      this.ctx = canvas.getContext('2d');
      // this.ratio = this.getPixelRatio(this.ctx);
    }
  }
  createPack() {
    const imgRandom = randomRange(...imgInfo.randomLimit);
    return {
      x: positionX(),
      y: 0,
      img: this.img[Math.floor(randomRange(0, this.loadingImageArr.length ))],
      // rotate
      rotate: randomRange(-45, 45),
      direction: Math.random(),
      speed: randomRange(...speedLimit),
      // rotate speed
      round: 0,
      roundSpeed: randomRange(1, 2),
      imgInfo: {
        w: imgInfo.w * imgRandom,
        h: imgInfo.h * imgRandom,
      },
    };
  }
  pushPackArr = () => {
    const { max, min } = this.density;
    const random = Math.floor(Math.random() * (max - min) + min);
    this.packedArr.push(
      ...new Array(random).fill('').map(() => this.createPack())
    );
    this.clearTime = setTimeout(() => {
      if (this.packedArr.length > numberMaxLimit)
        return clearTimeout(this.clearTime);
      this.pushPackArr();
    }, 300);
  };
  drawPacked = () => {
    this.ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
    this.packedArr.forEach((item, index) => {
      const r = ((item.rotate + item.round) * Math.PI) / 180;
      const temp = item.y - item.x * Math.tan(r);
      const top = temp * Math.cos(r);
      const left = item.x / Math.cos(r) + temp * Math.sin(r);
      this.ctx.save();
      this.ctx.rotate(r);
      this.ctx.drawImage(
        item.img,
        left - item.imgInfo.w / 2,
        top - item.imgInfo.h / 2,
        item.imgInfo.w,
        item.imgInfo.h
      );
      this.ctx.restore();
      if (item.direction < 0.5) {
        item.round -= item.roundSpeed;
      } else {
        item.round += item.roundSpeed;
      }
      if (item.y + item?.speed <= window.innerHeight) {
        item.y = item.y + item?.speed || 0;
      } else {
        // init
        item.y = 0 + item?.speed || 0;
        item.x = positionX();
        Object.assign(item, this.createPack());
      }
    });
    window.requestAnimationFrame(this.drawPacked);
  };

  start = () => {
    this.pushPackArr();
    this.drawPacked();
  };

  render() {
    return (
      <div style={{ position: 'absolute', left: 0, top: 0, background: '#000' }}>
        <canvas id="canvas"></canvas>
      </div>
    );
  }
}
  1. utils/index.ts
ts 复制代码
export const loadImage = (url: string) => {
  if (!window?.Image) return null;
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.src = url;
    img.onload = () => {
      resolve(img);
    };
    img.onerror = () => {
      reject(new Error('load image error!'));
    };
  });
};
export const positionX = () => Math.random() * window.innerWidth;
export const randomRange = (start: number, end: number) => {
  const random = (end - start) * Math.random() + start;
  const number = 1;
  const arr = new Array(number).fill('').map((v, i) => random / (i + 1));
  return arr[Math.floor(Math.random() * number)];
};

效果图


相关推荐
Rockson1 分钟前
使用Ruby接入实时行情API教程
javascript·python
前端小巷子1 小时前
Web开发中的文件上传
前端·javascript·面试
上单带刀不带妹2 小时前
手写 Vue 中虚拟 DOM 到真实 DOM 的完整过程
开发语言·前端·javascript·vue.js·前端框架
前端风云志2 小时前
typescript结构化类型应用两例
javascript
gnip3 小时前
总结一期正则表达式
javascript·正则表达式
爱分享的程序员3 小时前
前端面试专栏-算法篇:18. 查找算法(二分查找、哈希查找)
前端·javascript·node.js
翻滚吧键盘4 小时前
vue 条件渲染(v-if v-else-if v-else v-show)
前端·javascript·vue.js
你这个年龄怎么睡得着的4 小时前
为什么 JavaScript 中 'str' 不是对象,却能调用方法?
前端·javascript·面试
南屿im4 小时前
JavaScript 手写实现防抖与节流:优化高频事件处理的利器
前端·javascript
西陵4 小时前
Nx带来极致的前端开发体验——借助CDD&TDD开发提效
前端·javascript·架构