九宫格抽奖,大转盘抽奖,滚动抽奖,刮刮卡抽奖(四)

做了各种各样的营销抽奖活动,有九宫格抽奖、大转盘抽奖、滚动抽奖、刮刮卡抽奖,来总结一波~

九宫格抽奖
大转盘抽奖
滚动抽奖
刮刮卡抽奖

刮刮卡抽奖

我们先来看下最终实现的效果,刮开涂层,看是否中奖:

思路

将奖品图片转换为canvas,上面再添加一个灰色的涂层,鼠标移动时慢慢擦掉灰色的涂层。那要怎么擦掉这个灰色的涂层呢?我们需要借助到canvas的一个属性:globalCompositeOperation

globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上。

源图像 = 您打算放置到画布上的绘图。

目标图像 = 您已经放置在画布上的绘图。

该属性有一个属性值 destination-out:在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。看起来是有点懵逼的,我们用实际例子来看下:

静态页面:

bash 复制代码
<div id="app">
  <canvas width="500" height="200" id="canvas"></canvas>
</div>

vue实现:

ini 复制代码
<script>
  const { createApp, reactive, onMounted, toRefs } = Vue;
  createApp({
    setup() {
      onMounted(() => {
        const canvas = document.getElementById("canvas");
        const ctx = canvas.getContext("2d");
        ctx.beginPath()
        ctx.fillStyle = 'green'
        ctx.arc(100, 100, 100, 0, Math.PI * 2, true);
        ctx.fill()
        ctx.closePath()
        ctx.globalCompositeOperation = 'destination-out';
        ctx.beginPath()
        ctx.arc(200, 100, 100, 0, Math.PI * 2, true);
        ctx.fill()
        ctx.closePath()
      });
    },
  }).mount("#app");
</script>

可以看到两个圆形相交的地方变透明了:

由上面的例子可知,只要设置该属性,然后以鼠标按下的点为圆心绘制圆形,就可以一点点擦去灰色的涂层。

实现

静态页面:

ini 复制代码
<div id="app">
  <canvas width="400" height="200"></canvas>
</div>

vue实现:

我们需要先向后台请求接口获取奖品,然后将其绘制成 canvas 的背景图片,再绘制一个灰色的长方形将其遮住,最后再监听鼠标的按下、移动、抬起事件,计算鼠标按下点的位置,已该位置为中心绘制圆形。

ini 复制代码
<script>
    const { createApp, reactive, toRefs, onMounted, nextTick } = Vue
    createApp({
        setup() {
            const state = reactive({
                lotteryImg: null, // 奖品图片
                mousedown: false, // 鼠标是否按下
                ctx: null, // canvas的上下文对象
            })

            onMounted(() => {
                nextTick(() => {
                    state.canvas = document.querySelector('canvas');
                    state.lotteryImg = new Image();
                    state.lotteryImg.src = './prize.jpg';
                    // 监听图片的加载完成事件
                    state.lotteryImg.addEventListener('load', handleImgLoaded)
                    // 移动端触摸事件
                    state.canvas.addEventListener('touchstart', eventDown);
                    state.canvas.addEventListener('touchend', eventUp);
                    state.canvas.addEventListener('touchmove', eventMove);
                    // 网页版触摸事件
                    state.canvas.addEventListener('mousedown', eventDown);
                    state.canvas.addEventListener('mouseup', eventUp);
                    state.canvas.addEventListener('mousemove', eventMove);
                })
            })

            function handleImgLoaded(e) {
                // 绘制奖品图片
                state.canvas.style.backgroundImage = 'url(' + state.lotteryImg.src + ')';
                state.canvas.style.backgroundSize="cover"
                // 绘制灰色涂层(源图像)
                state.ctx = state.canvas.getContext('2d');
                state.ctx.fillStyle = 'gray';
                state.ctx.fillRect(0, 0, 400, 200)
                state.ctx.globalCompositeOperation = 'destination-out';
            }
            
            // 鼠标按下
            function eventDown(e) {
                e.preventDefault();
                state.mousedown = true;
            }

            // 鼠标抬起
            function eventUp(e) {
                e.preventDefault();
                state.mousedown = false;
            }

            // 鼠标移动
            function eventMove(e) {
                if (state.mousedown) {
                    // changedTouches:涉及当前(引发)事件的触摸点列表
                    if (e.changedTouches) {
                        e = e.changedTouches[e.changedTouches.length - 1];
                    }
                    /*
                        e.pageX,e.pageY坐标相对于整个渲染页面的左上角(包括滚动隐藏距离)
                        也可以采用 e.clientX + (document.body.scrollLeft || document.documentElement.scrollTop)
                        e.clientX,e.clientY设置或获取鼠标指针位置相对于浏览器窗口(内容区域的左上角)的坐标,与浏览器是否有滚动条无关
                    */
                    /*
                        offsetLeft:距离上一级定位元素左侧的距离
                        offsetTop:距离上一级定位元素上侧的距离
                    */
                    const x = e.pageX - state.canvas.offsetLeft
                    const y = e.pageY - state.canvas.offsetTop
                    
                    // 绘制圆形(目标图像)进行擦除
                    state.ctx.beginPath()
                    state.ctx.arc(x, y, 20, 0, Math.PI * 2);
                    state.ctx.fill()
                }
            }

            return {
                ...toRefs(state)
            }
        }
    }).mount('#app')
</script>

参考文章

1、学习 canvas 的 globalCompositeOperation 做出的神奇效果

相关推荐
念念不忘 必有回响4 分钟前
nginx前端部署与Vite环境变量配置指南
前端·nginx·vite
JIngJaneIL32 分钟前
篮球论坛|基于SprinBoot+vue的篮球论坛系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·篮球论坛系统
程序猿阿伟2 小时前
《首屏加载优化手册:Vue3+Element Plus项目提速的技术细节》
前端·javascript·vue.js
麦麦大数据2 小时前
D030知识图谱科研文献论文推荐系统vue+django+Neo4j的知识图谱|论文本文相似度推荐|协同过滤
vue.js·爬虫·django·知识图谱·科研·论文文献·相似度推荐
fruge4 小时前
Vue Pinia 状态管理实战指南
前端·vue.js·ubuntu
绝无仅有4 小时前
某教育大厂面试题解析:MySQL索引、Redis缓存、Dubbo负载均衡等
vue.js·后端·面试
sean4 小时前
开发一个自己的 claude code
前端·后端·ai编程
用户21411832636024 小时前
dify案例分享-用 Dify 一键生成教学动画 HTML!AI 助力,3 分钟搞定专业级课件
前端
没逛够5 小时前
Vue 自适应高度表格
javascript·vue.js·elementui
太过平凡的小蚂蚁6 小时前
Kotlin 协程中常见的异步返回与控制方式(速览)
开发语言·前端·kotlin