Vue3 & TypeScript组件:刮奖区
2023-05-30 本文已影响0人
牛会骑自行车
效果图
说明:组件区域只有遮罩那一块,其他内容放页面里;需要先给页面设置一个放刮层的容器,组件在里面会自动撑开。
思路:
- 页面正常显示。先设置一个放刮层的容器
- 组件中也先设置一个容器,容器宽度默认100%,高度也设置为100%
- onMounted时获取容器宽高,赋值给canvas
- 设置画布灰色蒙层
- canvas上设置touchstart和touchmove事件,通过touch事件我们可以获取到当前下手的位置
点:
- canvas:https://www.runoob.com/html/html5-canvas.html
- 在其中的arc中,x和y指的是当前落点到canvas边界的距离
组件代码:
<script lang='ts' setup>
const { proxy }: any = getCurrentInstance();
const emit = defineEmits(['exceed']);
// #region init
type PropsType = {
// 刮奖区遮罩颜色
background?: string,
// 画笔粗细
lineWidth?: number,
// 面积设置:大于该值算刮开
winPercintage?: string
}
const props = withDefaults(defineProps<PropsType>(), {
background: '#C0C0C0',
lineWidth: 8
})
const context = ref(null);
const refScratchPrizeDynamic = ref('');
const canvas = reactive({
w: 0,
h: 0
})
const canvasOffset = reactive({
x: 0,
y: 0
})
const refScratchPrizeContainer = ref(null);
const initCanvas = () => {
// 容器信息
const containerO = refScratchPrizeContainer.value;
const {clientWidth, clientHeight} = containerO;
// 画布宽高撑满容器
canvas.w = clientWidth;
canvas.h = clientHeight;
// 设置灰色蒙层
nextTick(() => {
const contextEl = proxy.$refs[refScratchPrizeDynamic.value];
const {x, y} = contextEl.getBoundingClientRect();
canvasOffset.x = x;
canvasOffset.y = y;
const { clientWidth, clientHeight } = contextEl;
context.value = contextEl.getContext("2d");
context.value.beginPath();
context.value.fillStyle = props.background;
context.value.fillRect(0, 0, clientWidth, clientHeight);//设置画布大小
context.value.globalCompositeOperation = "destination-out"; //橡皮擦模式
})
}
onMounted(() => {
refScratchPrizeDynamic.value = proxy.$utils.getUuid(4, { letter: true });
initCanvas();
})
// #endregion
// #region main
const start = (e: TouchEvent) => {
const { pageX: x, pageY: y } = e.targetTouches[0];
handleScratch(x, y);
}
const count = shallowRef(0);
const move = (e: TouchEvent) => {
const { pageX: x, pageY: y } = e.targetTouches[0];
handleScratch(x, y);
if (props.winPercintage) {
// 计算刮开面积
const canvasInfo = context.value.getImageData(0, 0, canvas.w, canvas.h).data;
const percentage = getUsedArea(canvasInfo);
if (percentage * 100 >= parseInt(props.winPercintage)) {
if(count.value >= 1) return;
emit('exceed', percentage * 100 + '%');
count.value ++;
}
}
}
const handleScratch = (x: number, y: number) => {
const contextO = context.value;
contextO.beginPath();
// 小知识:arc中的x、y是以canvas画布为基准
contextO.arc(x - canvasOffset.x, y - canvasOffset.y, props.lineWidth, 0, Math.PI * 2);
contextO.fill();
}
const getUsedArea = (arr: Uint8ClampedArray) => {
let used = 0;
let notUsed = 0;
arr.forEach((item, index) => {
if (index % 4 === 3) {
item === 0 && used++
item === 255 && notUsed++
}
})
return used / (notUsed + used);
}
// #endregion
</script>
<template>
<div class="scratch-prize" ref="refScratchPrizeContainer">
<canvas :ref="refScratchPrizeDynamic" :width="canvas.w" :height="canvas.h" @touchmove="move" @touchstart="start">
您的浏览器不支持刮奖操作
</canvas>
</div>
</template>
<style lang="scss" scoped>
.scratch-prize {
height: 100%;
}
</style>
使用页面代码:
<script lang='ts' setup>
const exceed = () => {
console.log('超出设置百分比')
}
</script>
<template>
<div class="scratch-view">
<img src="https://img2.baidu.com/it/u=3375776510,3085752136&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1685638800&t=cb4d9fcc048cd4f41227737a607b4098" alt="" class="scratch-view-image">
<div class="scratch-view-wrap">
<scratch-prize winPercintage="80%" @exceed="exceed"></scratch-prize>
</div>
</div>
</template>
<style lang="scss" scoped>
.scratch-view {
position: relative;
&-wrap {
width: 150px;
height: 50px;
border-radius: 6px;
position: absolute;
top: 150px;
left: 50%;
transform: translateX(-50%);
}
&-image {
width: 100%;
}
}
</style>