Vue+Canvas开发技巧分享:打造一个炫酷的图片刮刮乐效果!
用摸鱼的时间做了一个图片刮刮乐
案例分析
我们要实现一个简单的图片刮刮乐。
具体来说,我们要先创建一个画布 (canvas), 然后在其中绘制一张图片作为底图,并覆盖一张图片作为涂层。
当鼠标在涂层上移动时,会绘制圆形裁剪区域,并将该区域清空,从而展示出底部的图片。当鼠标抬起或移出画布时,程序会计算已经刮出的面积,并根据阈值决定是否重新绘制底部全图。
具体实现
1、创建画布
首先我们要定义一个 div ,里面有一个 img 和一个 canvas 元素。div 元素用于作为容器,img 元素为底部的图片。
canvas 元素则用于绘制涂层和处理用户的交互行为。
<template>
<div class="container" id="container">
<!-- 顶部图片 -->
<canvas
id="canvas"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseout="handleMouseOut"
/>
<!-- 底部图片 -->
<img :src="baseImg">
</div>
</template>
<style scoped>
.container {
position: relative;
margin: 0 auto;
}
canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
</style>
2、创建钩子和引入图片
接下来导入 vue 中的 onMounted、reactive 和 defineProps 方法,用于组件的生命周期钩子和响应式数据管理。
通过 defineProps 定义了两个组件属性:topImg 和 baseImg,分别表示底部图片和涂层图片的地址,并设置了默认值。
<script setup>
import { onMounted, reactive,defineProps, ref } from 'vue';
const props = defineProps({
topImg: { // 顶部图片
type:String,
default:'01.jpg'
},
baseImg: { // 底部图片
type:String,
default:'02.jpg'
},
})
</script>
使用 reactive 方法创建了一个响应式数据对象 data,其中包含了一些状态变量和配置参数,用于记录用户操作和控制程序的行为。
const data = reactive({
isMouseDown: false, // 当前鼠标是否按下
lastLoc: { x: 0, y: 0 }, // 上一次鼠标位置
curLoc: { x: 0, y: 0 }, // 当前鼠标位置
canvasWidth: 0, // canvas 的宽
canvasHeight: 0, // canvas 的高
threshold: 0.65 // 添加一个阈值属性
});
3、绘制 canvas 底图
在组件挂载后,会获取到 canvas 元素并设置其宽度和高度,然后获取到画布上下文(Context),并调用 drawCover 方法绘制图片。
let ctx;
onMounted(() => {
const divElement = document.getElementById('container');
// 获取div的宽度
let width = divElement.getBoundingClientRect().width
// 计算 baseImg 图片在容器中的宽高比例
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = props.baseImg;
img.onload = function () {
data.canvasWidth = width; // 容器中的宽度
data.canvasHeight = width/(img.width/img.height); // 容器中的高度
initialize()
};
});
// 设置 canvas 的宽高,并将其传递给 drawCover 函数,然后让其绘制图片。
const initialize =()=> {
const canvas = document.getElementById('canvas')
canvas.width = data.canvasWidth;
canvas.height = data.canvasHeight;
ctx = canvas.getContext('2d');
drawCover(props.topImg);
}
// 绘制图片
const drawCover = (imgSrc)=> {
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = imgSrc;
img.onload = function () {
ctx.drawImage(img, 0, 0, data.canvasWidth, data.canvasHeight);
};
}
4、监听鼠标事件
添加鼠标事件 mousedown、mousemove、mouseup、mouseout
const handleMouseDown = (e)=> {
e.preventDefault();
data.isMouseDown = true;
data.lastLoc = windowToCanvas(e.clientX, e.clientY);
}
const handleMouseMove = (e)=> {
e.preventDefault();
if (data.isMouseDown) {
data.curLoc = windowToCanvas(e.clientX, e.clientY);
// 绘制圆形裁剪区域
ctx.save();
ctx.beginPath();
ctx.arc(data.curLoc.x, data.curLoc.y, 20, 0, Math.PI * 2, false);
ctx.clip();
// 清空画布
ctx.clearRect(0, 0, data.canvasWidth, data.canvasHeight);
ctx.restore();
}
}
const handleMouseUp = (e)=> {
e.preventDefault();
data.isMouseDown = false;
// 计算已经刮出的面积
const imageData = ctx.getImageData(0, 0, data.canvasWidth, data.canvasHeight);
const pixels = imageData.data;
let count = 0;
for (let i = 0; i < pixels.length; i += 4) {
if (pixels[i + 3] === 0) {
count++;
}
}
const area = count / (data.canvasWidth * data.canvasHeight);
// 如果被刮出的面积大于阈值,重新绘制底部全图
if (area > data.threshold) {
ctx.clearRect(0, 0, data.canvasWidth, data.canvasHeight);
}
}
const handleMouseOut = (e)=> {
e.preventDefault();
if (data.isMouseDown) {
data.isMouseDown = false;
}
}
const windowToCanvas = (x, y)=> {
const canvas = document.getElementById('canvas')
const bbox = canvas.getBoundingClientRect();
return {
x: Math.round(x - bbox.left),
y: Math.round(y - bbox.top)
};
}
5、查看效果
