canvas 模仿抽奖转盘

摸鱼摸鱼,闲来无事就是摸鱼

搞个营销类的转盘抽奖活动

转盘抽奖

先看一下效果图

思路

做完项目Google一下发现还可以通过css 的方式去做,这边主要通过canvas的方法去绘制

先生成一个基础的canvas

<canvas id="canvas" width="600" height="600"></canvas>

首先绘制一个圆的背景(也可以通过图片来设置背景)

js 复制代码
const drawBall = (beginPointX,beginPointY,radius,startAngle,endAngle,backgroudColor) =>{
  const ctx = canvasOptions.ctx
  ctx.beginPath();
  ctx.arc(beginPointX,beginPointY,radius,startAngle,endAngle);
  ctx.fillStyle = backgroudColor;
  ctx.fill();
}
// 设置背景为图片 获取最小值 通过半径来判断 缩放值
const drawRoundImage = (beginPointX,beginPointY,radius,startAngle,endAngle,backImage) =>{
  const ctx = canvasOptions.ctx
  let temp = Math.min(backImage.width,backImage.height);
  let scale = radius / temp;
  ctx.rect(beginPointX-backImage.width*scale, beginPointX-backImage.height*scale, beginPointX+radius, beginPointY+backImage.width*scale.height*scale);
  ctx.drawImage(backImage, beginPointX-backImage.width*scale/2, beginPointY-backImage.height*scale/2,backImage.width*scale,backImage.height*scale);
}

画完背景就里面抽奖的内容了

js 复制代码
// 根据抽奖的内容列表来判断每次分配所占的角度
for (const [index,item] of ballOptions.entries()){
    let startAngle = 0;
    let endAngle = 0;
    if(index){
      startAngle = 2*Math.PI*(index/ballOptions.length);
    }
    if(index !=ballOptions.length-1){
      endAngle = 2*Math.PI*((index+1)/ballOptions.length);
    }
    await drawArc(300,300,200,startAngle+angle.value,endAngle+angle.value,item.color,item)
  }
  // 转盘里面的内容 包括图片 背景颜色 和 字体内容
const drawArc=(beginPointX,beginPointY,radius,startAngle,endAngle,backgroudColor,item)=>{
  const centerX = 300;
  const centerY = 300;
  const orbitRadius = 200
  const ctx = canvasOptions.ctx
  ctx.beginPath();
  ctx.arc(beginPointX,beginPointY,radius,startAngle,endAngle);
  if(backgroudColor){
    ctx.fillStyle = backgroudColor;
    ctx.strokeStyle ="white";
  }else{
    ctx.fillStyle = "rgba(0,0,0,0)";
  }
  ctx.fill();
  ctx.beginPath()
  ctx.moveTo(300,300)
  const startX = centerX + orbitRadius * Math.cos(startAngle);  
  const startY = centerY + orbitRadius * Math.sin(startAngle);
  const endX = centerX + orbitRadius * Math.cos(endAngle);  
  const endY = centerY + orbitRadius * Math.sin(endAngle);
  ctx.lineTo(startX,startY)
  ctx.lineTo(endX,endY);
  ctx.closePath();
  ctx.fillStyle = backgroudColor;
  ctx.strokeStyle = backgroudColor
  ctx.stroke();
  ctx.fill();
  // 保存字体样式
  ctx.save();
  ctx.translate((startX+endX)/2, (startY+endY)/2);
  // 根据圆的弧度计算文字旋转角度
  let angle = 0
  let index = ballOptions.findIndex(ite =>ite.label == item.label);
  if(index != ballOptions.length-1){
    angle = (startAngle + endAngle) / 2 + Math.PI/2;
  }else{
    angle = (startAngle + endAngle) / 2 - Math.PI/2;
  }
  // 旋转画布
  ctx.rotate(angle);
  ctx.font = '24px Arial bold';
  ctx.fillStyle = '#AA625B';
  ctx.textAlign = "center"
  ctx.fillText(item.label, 0,0);
  ctx.restore();
  if("backImage" in item){
    let img = new Image();
    img.src = item.backImage;
    img.onload=()=>{
      ctx.save();
      ctx.translate(((startX+endX)/2+300)/2, ((startY+endY)/2+300)/2);
      let angle = 0
      let index = ballOptions.findIndex(ite =>ite.label == item.label);
      if(index != ballOptions.length-1){
        angle = (startAngle + endAngle) / 2 + Math.PI/2;
      }else{
        angle = (startAngle + endAngle) / 2 - Math.PI/2;
      }
      // 旋转画布
      ctx.rotate(angle);
      drawRoundImage(0,0,60,0,Math.PI*2,img)
      ctx.restore();
    }
  }
}

最后就是转盘的动画内容了 包括旋转角速度的变化 以及一些后门的逻辑

js 复制代码
 if(isAnimal.value){
    speed.value += 0.0005;
    angle.value += speed.value;
    // console.log(angle.value);
    animateFlag.value = requestAnimationFrame(animate);
  }
  if(speed.value > 0.32){
    stopAnimate();
  }
  
const stopAnimate = () =>{
  // 计算减缓速度 设置减缓的时间
  // 设置一个延时暂停然后
  // 后门计算说谁谁必中
  if(mustIndex.value){
    if(!startAngle.value){
      mustIndex.value = ballOptions.length-mustIndex.value-2
    }
    startAngle.value = ((mustIndex.value)*Math.PI*2)/ballOptions.length; // 指针应该停的位置
    endAngle.value = ((mustIndex.value+1)*Math.PI*2)/ballOptions.length; // 指针应该挺的位置
    let startAngle1 = startAngle.value;
    let endAngle1  = endAngle.value
    let stopAngle = (startAngle1+endAngle1)/2; // 暂停的中间值
    let baseAngle = angle.value % (2*Math.PI) // 当前的旋转的所在的位置
    // console.log(stopAngle);
    downSpeed.value = stopAngle<baseAngle?(2*Math.PI-baseAngle+stopAngle)/1000:(stopAngle-baseAngle)/1000;
    subSpeed();
  }else{
    // console.log(startAngle.value);
    // console.log("6");
    subSpeed();
  }
}

const subSpeed=()=>{
  speed.value -= downSpeed.value;
  if(mustIndex.value){
    if(!(angle.value % (2*Math.PI)>startAngle.value
        &&
      angle.value % (2*Math.PI)<endAngle.value)){
      speedFlag.value = requestAnimationFrame(subSpeed);
    }else if(speed.value<0){
      // console.log("6");
      cancelAnimationFrame(animateFlag.value)
      cancelAnimationFrame(speedFlag.value)
    }
    else{
      // angle.value = startAngle.value;
      cancelAnimationFrame(animateFlag.value)
      cancelAnimationFrame(speedFlag.value)
    }
  }else{
    if(speed.value<0){
      cancelAnimationFrame(animateFlag.value)
      cancelAnimationFrame(speedFlag.value)
      // console.log("6");
    }else{
      speedFlag.value = requestAnimationFrame(subSpeed);
    }
  }
}

const startAnimate = ()=>{
  isAnimal.value = true
  animate();
  speed.value = 0.1
}

具体代码在 github.com/chenchuchun... 感觉是想复杂也写复杂了 damn

相关推荐
文刀竹肃21 小时前
DVWA -SQL Injection-通关教程-完结
前端·数据库·sql·安全·网络安全·oracle
LYFlied21 小时前
【每日算法】LeetCode 84. 柱状图中最大的矩形
前端·算法·leetcode·面试·职场和发展
Bigger21 小时前
Tauri(21)——窗口缩放后的”失焦惊魂”,游戏控制权丢失了
前端·macos·app
Bigger1 天前
Tauri (20)——为什么 NSPanel 窗口不能用官方 API 全屏?
前端·macos·app
bug总结1 天前
前端开发中为什么要使用 URL().origin 提取接口根地址
开发语言·前端·javascript·vue.js·html
一招定胜负1 天前
网络爬虫(第三部)
前端·javascript·爬虫
Shaneyxs1 天前
从 0 到 1 实现CloudBase云开发 + 低代码全栈开发活动管理小程序(13)
前端
半山烟雨半山青1 天前
微信内容emoji表情包编辑器 + vue3 + ts + WrchatEmogi Editor
前端·javascript·vue.js
码途潇潇1 天前
Vue 事件机制全面解析:原生事件、自定义事件与 DOM 冒泡完全讲透
前端·javascript
zmirror1 天前
Monorepo 在 Docker 中的构建方案方案
前端