自己通过canvas+vue(vue不是必备的可以)实现的一个抽奖转盘,重点在实现逻辑,所以样式丑绝。
基本效果图,中间指针可以替换为图片
数据格式
转盘的分块由传入的数组长度确定,分为4,6,8块还是能看的
{
id: 1, // 奖品标识,可按需设置
content: "谢谢参与", // 奖品文本
prize: "111", //奖品内容
probability: 0.75, // 中奖概率,也可以不设,由后台决定是否中奖及中了什么奖
img: "https://img.stringon.com/dd4cffa524bf46f790520a46fefd1726-S375",// 奖品图片,按需设置
},
{
id: 2,
content: "现金红包2元",
prize: "222",
probability: 0.05,
img: "https://img.stringon.com/c0f57be115e541fd90b8df5ef163110b-S375",
},
{
id: 3,
content: "现金红包10元",
prize: "333",
probability: 0.02,
img: "https://img.stringon.com/c0f57be115e541fd90b8df5ef163110b-S375",
},
{
id: 4,
content: "现金红包3元",
prize: "444",
probability: 0.04,
img: "https://img.stringon.com/c0f57be115e541fd90b8df5ef163110b-S375",
},
html部分
第一个canvas绘制大转盘
第二个canvas绘制中间转盘,可替换为图片(用drawImage绘制)
<div class="turn-box">
<canvas id="chart" ref="turnBox" height="400" width="600">
你的浏览器不支持HTML5 canvas
</canvas>
<canvas id="centerChart" ref="centerChart" height="400" width="600">
你的浏览器不支持HTML5 canvas
</canvas>
</div>
css部分
css部分比较简单,主要用来定位两个canvas之间的位置
.turn-box {
position: relative;
}
canvas {
position: absolute;
left: 0;
right: 0;
margin: auto;
top: 0;
}
#centerChart {
top: 50px;
}
js部分
重点的总是留在最后,下面是实现代码,注释我会尽量写的清楚一点
// data初始化
data(){
return{
canvas: null,
ctx: null,
width: 262, // 宽
height: 262, // 高
count: 12,//抽奖次数,也是从后台请求
running: null,//是否正在抽奖
turnInfo:[] //奖品信息,一般从后台请求
}
},
computed: {
//为了方便比较,这里是中奖区域,如果由后台返回中奖信息,那这里就是不需要的
randList() {
let start = 0;
return this.turnInfo.map((item) => {
start = item.probability + start;
return Number(start.toFixed(2));
});
},
},
mounted() {
// 获得canvas上下文
this.canvas = document.getElementById("chart");
if (this.canvas && this.canvas.getContext) {
this.ctx = this.canvas.getContext("2d");
}
this.drawCharts(); //绘制
const that = this;
// 点击抽奖
this.canvas.onclick = async function () {
if (that.count === 0) return; //没有抽奖次数处理逻辑
if (that.running) return; //正在抽奖中处理逻辑
const rand = that.getPrize();//获取中奖信息
that.$refs.turnBox.style.transform = "unset";
that.$refs.turnBox.style.transition = "all 0s";//先将之前旋转状态重置
await setTimeout(() => {
let deg = 1800 + 360 - (360 / that.turnInfo.length) * rand;//计算旋转角度,1800(360*5)为多旋转的角度
that.$refs.turnBox.style.transform = `rotate(${deg}deg)`;
that.$refs.turnBox.style.transition = "all 3s ease-in-out";//开始旋转
});
console.log(that.turnInfo[rand].content);//输出中奖信息
that.running = setTimeout(() => {
clearTimeout(that.running);
that.running = null;正在抽奖状态重置
that.count -= 1;//中奖次数减一
}, 3000);//延迟时间为转盘旋转动画时间
};
},
methods:{
// 图表初始化
initChart() {
// 这里是对高清屏幕的处理,
// 方法:先将canvas的width 和height设置成本来的两倍
// 然后将style.height 和 style.width设置成本来的宽高
// 这样相当于把两倍的东西缩放到原来的 1/2,这样在高清屏幕上 一个像素的位置就可以有两个像素的值
// 这样需要注意的是所有的宽高间距,文字大小等都得设置成原来的两倍才可以。
this.canvas.width = this.width * 2;
this.canvas.height = this.height * 2;
this.canvas.style.height = `${this.height}px`;
this.canvas.style.width = `${this.width}px`;
this.ctx.translate(0.5, 0.5); // 当只绘制1像素的线的时候,坐标点需要偏移,这样才能画出1像素实线
},
//绘制大转盘
drawTurnBox() {
let num = this.turnInfo.length;
this.ctx.font = "bold 18px Microsoft YaHei";
for (let i = 0; i < num; i++) {
//根据奖品参数 绘制扇形填充颜色,按需设置
// ctx.fillStyle = this.turnInfo.colors[i];
//开始绘制扇形
this.ctx.save();
this.ctx.beginPath();
this.ctx.translate(this.width, this.width);
// // 从(0, 0)坐标开始定义一条新的子路径
this.ctx.moveTo(0, 0);
this.ctx.rotate(((360 / num) * (i + 1) * Math.PI) / 180);
//arc(x,y,r,起始角,结束角,绘制方向) 方法创建弧/曲线(用于创建圆或部分圆)
this.ctx.arc(0, 0, this.width, 0, (2 * Math.PI) / num, false);
this.ctx.arc(0, 0, this.width / 2 - 50, (2 * Math.PI) / num, 0, true);
if ((i + 1) % 2 == 0) {
this.ctx.fillStyle = "#ffb820";
} else {
this.ctx.fillStyle = "#ffcb3f";
}
// this.ctx.lineWidth = 0.5;
this.ctx.strokeStyle = "transparent";
this.ctx.stroke();
this.ctx.fill();
this.ctx.restore();
}
},
// 绘制文本
drawText() {
let num = this.turnInfo.length;
this.ctx.font = "24px Arial";
let arc = Math.PI / (num / 2);
for (let i = 0; i < num; i++) {
let angle = 0 + i * arc;
this.ctx.save();
//奖品默认字体颜色
// this.ctx.fillStyle = "#fff";
let text = this.turnInfo[i].content;
this.ctx.translate(
this.width + Math.cos(angle + arc / 2) * (this.width - 40),
this.width + Math.sin(angle + arc / 2) * (this.width - 40)
);
this.ctx.rotate(angle + arc / 2 + Math.PI / 2);
//由于设计的转盘色块是交错的,所以这样可以实现相邻奖品区域字体颜色不同
// if (i % 2 == 0) {
// this.ctx.fillStyle = "#fff";
// }
//将字体绘制在对应坐标
this.ctx.fillText(text, -this.ctx.measureText(text).width / 2, 20);
//设置字体
// this.ctx.font = " 14px Microsoft YaHei";
//把当前画布返回(插入)到上一个save()状态之前
this.ctx.restore();
//绘制奖品图片
if (this.turnInfo[i].img) {
let img = new Image();
img.src = this.turnInfo[i].img;
img.onload=()=>{
this.ctx.save();
this.ctx.translate(
this.width + Math.cos(angle + arc / 2) * this.width,
this.width + Math.sin(angle + arc / 2) * this.width
);
this.ctx.rotate(angle + arc / 2);
this.ctx.drawImage(img, -this.width / 2 + 10, -50, 42, 94);
this.ctx.restore();
}
}
}
},
// 绘制中间圆盘
drawCenter() {
const canvas = document.getElementById("centerChart");
canvas.style.top = `${63}px`;// 位置自己定啦
let ctx = null;
if (canvas && canvas.getContext) {
ctx = canvas.getContext("2d");
}
let arc = Math.PI / (this.turnInfo.length / 2);
canvas.width = this.width;
canvas.height = this.height;
canvas.style.height = `${this.height / 2}px`;
canvas.style.width = `${this.width / 2}px`;
ctx.translate(0.5, 0.5);
ctx.save();
ctx.fillStyle = "#ffcb3f";
ctx.beginPath();
ctx.translate(this.width / 2, this.width / 2);
ctx.rotate(arc / this.turnInfo.length + Math.PI / 180);
//这一部分可以通过drawImage直接绘制成图片,保留中心点设置与旋转角度即可,其他的都随意
ctx.moveTo(0, 40);
ctx.lineTo(this.width / 2, 40); //绘制指针
ctx.arc(0, 0, this.width / 2 - 40, 2 * Math.PI, 0, true);//绘制圆
ctx.lineWidth = 1;
ctx.strokeStyle = "rgba(0,0,0,0.45)";
ctx.stroke();
ctx.fill();
ctx.restore();
},
// 开始绘制
drawCharts() {
this.drawCenter(); //绘制中心圆
this.initChart(); // 图表初始化
this.drawTurnBox(); // 绘制转盘
this.drawText(); // 绘制文字
},
//获取中将信息,中奖信息由后台返回会比较安全,这里只需要返回中奖的索引就可以
getPrize() {
let rand = Number(Math.random().toFixed(2));//中奖随机数
let prizeIndex = -1;//中奖索引
let index = 0;
while (prizeIndex === -1 && index < this.randList.length) {
prizeIndex = this.randList[index] >= rand ? index : -1;
index++;
}
return prizeIndex;
},
}
写在最后
以上就是我实现抽奖大转盘的全部代码,写得比较急,代码粗糙,样式我自己都不忍直视,如果我记得并且有时间的话,我还会进行优化的。
项目地址:抽奖转盘demo
ps:推荐一下我自己写的二维码生成器,有输入字符生成和上传.txt文件生成,主要是希望有人使用后给我提出一些修改意见:qrcode-generator