微信小程序实现简版点赞动画

发布于:2025-07-01 ⋅ 阅读:(25) ⋅ 点赞:(0)

这是第二次写canvas,基于微信小程序文档demo进行改写

demo效果为方块横向来回循环移动

我想做的是直播间那种点赞效果,竖向曲线移动、方块换成图片、点击添加绘制元素

第一阶段实现竖向曲线移动、点击添加绘制元素;下一阶段讲方块替换为图片

import { formatDate, getRandomInt } from "../../utils/util";

// components/stars/stars.ts
Component({
  lifetimes: {
    attached() {
      this.createSelectorQuery().select("#myCanvas2").fields({
        node: true,
        size: true
      }).exec(res => this.init(res));
    },
  },

  /**
   * 组件的属性列表
   */
  properties: {

  },

  /**
   * 组件的初始数据
   */
  data: {
    click: [{ startTime: Date.now(), img: '', fd: getRandomInt(-3, 3), c: getRandomInt(100, 200) }
    ]
  },

  /**
   * 组件的方法列表
   */
  methods: {
    handleAdd() {
      this.data.click.push({ startTime: Date.now(), img: '', fd: getRandomInt(-3, 3), c: getRandomInt(0, 200) })
    },
    init(res) {
      const width = res[0].width
      const height = res[0].height
      // 设置画布宽高
      const canvas = res[0].node
      const ctx = canvas.getContext('2d')
      canvas.width = width
      canvas.height = height
      
      // 帧渲染回调
      const draw = () => {
        const w = 40
        const time = Date.now()
        this.data.click = this.data.click.map((item, index) => {
          // 计算经过的时间
          const elapsed = time - item.startTime
          // 计算动画位置
          const n = Math.floor(elapsed / 3000)
          const m = elapsed % 3000
          const dy = m / 1500
          const bl = 1 - dy / 3
          const y = height - (height) * dy / 2
          // -2 随机生成 dy如何确定呢
          const x = item.fd * Math.sin(dy * Math.PI / 4) * (width - w * bl) / 2 + (width - w * bl) / 2
          return { ...item, x, y, bl, w, n }
        })
        this.data.click = this.data.click.filter(item => {
          if (item.y < 5) {
            return false
          }
          return true
        })
        this.render(ctx, width, height, this.data.click)
        // 注册下一帧渲染
        canvas.requestAnimationFrame(draw)
      }
      draw()
    },
    render(ctx, width, height, click) {
      ctx.clearRect(0, 0, width, height)
      // 渲染
      for (let i = 0; i < click.length; i++) {
        if (!this.data.click[i]) continue;
        const { x, y, bl, w, c } = this.data.click[i]
        ctx.fillStyle = 'rgba(' + c + ', 0, 0,' + bl + ')';
        ctx.fillRect(x, y, w * bl, w * bl);
      }
    }

  }
})

 替换图片

import { formatDate, getRandomInt } from "../../utils/util";
import { defaultIcon } from '../../pages/clockIn/defaultImg'
let imgsList: any = []

// components/stars/stars.ts
Component({
  lifetimes: {
    attached() {
      this.createSelectorQuery().select("#myCanvas2").fields({
        node: true,
        size: true
      }).exec(res => this.init(res));
    },
  },
  /**
   * 组件的属性列表
   */
  properties: {

  },
  /**
   * 组件的初始数据
   */
  data: {
    click: [{ startTime: Date.now(), img: '', fd: getRandomInt(-3, 3), c: getRandomInt(100, 200) }
    ]
  },
  /**
   * 组件的方法列表
   */
  methods: {
    handleAdd() {
      this.data.click.push({ startTime: Date.now(), img: imgsList[getRandomInt(0, imgsList.length - 1)], fd: getRandomInt(-3, 3), c: getRandomInt(0, 200) })
    },
    init(res) {
      const width = res[0].width
      const height = res[0].height
      // 设置画布宽高
      const canvas = res[0].node
      const ctx = canvas.getContext('2d')
      canvas.width = width
      canvas.height = height
      // 加载图片资源
      let imgKeys = Object.keys(defaultIcon)
      for (let k of imgKeys) {
        const image = canvas.createImage()
        image.onload = () => {
          imgsList.push(image)
          // ctx.drawImage(image, 0, 0, 40,40)
          console.log(imgsList.length)
        }
        image.src = defaultIcon[k]
      }
      // 帧渲染回调
      const draw = () => {
        const w = 40
        const time = Date.now()
        this.data.click = this.data.click.map((item, index) => {
          // 计算经过的时间
          const elapsed = time - item.startTime
          // 计算动画位置
          const n = Math.floor(elapsed / 3000)
          const m = elapsed % 3000
          const dy = m / 1500
          const bl = (1 - dy / 3) * 2
          const y = height - (height) * dy / 2
          // -2 随机生成 dy如何确定呢
          const x = item.fd * Math.sin(dy * Math.PI / 4) * (width - w / bl) / 2 + (width - w / bl) / 2
          return { ...item, x, y, bl, w, n }
        })
        this.data.click = this.data.click.filter(item => {
          if (item.y < 5) {
            return false
          }
          return true
        })
        this.render(ctx, width, height, this.data.click)
        // 注册下一帧渲染
        canvas.requestAnimationFrame(draw)
      }
      draw()
    },
    render(ctx, width, height, click) {
      ctx.clearRect(0, 0, width, height)
      // 渲染
      for (let i = 0; i < click.length; i++) {
        if (!this.data.click[i]) continue;
        const { x, y, bl, w, c, img } = this.data.click[i]
        
        if (img) {
          ctx.drawImage(img, x, y, w / bl, w / bl)
          ctx.globalAlpha = bl
        }
      }
     
    },

  }
})
<!--components/stars/stars.wxml-->
<view class="conponent-stars flex-cloumn-center">
  <canvas type="2d" id="myCanvas2" style="width: 150px;height: 200px;"></canvas>
  <view class="add" bind:tap="handleAdd"></view>
</view>

接下来的问题是canvas绘制的图片有锯齿,找了下解决办法:清晰多了

const dpr = wx.getSystemInfoSync().pixelRatio;
      canvas.width = width * dpr;
      canvas.height = height * dpr;
      ctx.scale(dpr, dpr);

由于找不到透明背景的切图,为了美观想给图片画个圆

目前只能加个边框,裁剪还不能多个同时裁剪

// 渲染
      for (let i = 0; i < click.length; i++) {
        if (!this.data.click[i]) continue;
        const { x, y, bl, w, c, img } = this.data.click[i]
        // ctx.fillStyle = 'rgba(' + c + ', 0, 0,' + bl + ')';
        // ctx.fillRect(x, y, w / (3 * bl), w / (3 * bl));
        if (img) {
          ctx.drawImage(img, x, y, w / bl, w / bl)
          ctx.globalAlpha = bl
          ctx.beginPath()
          ctx.arc(x + 0.5 * w / bl, y + 0.5 * w / bl, 20 / bl, 0, 2 * Math.PI)
          ctx.stroke()
          ctx.closePath()
          // ctx.clip()
        }
      }

点击效果可以查看小程序打卡模块


网站公告

今日签到

点亮在社区的每一天
去签到