Vue3 & TypeScript组件:刮奖区

2023-05-30  本文已影响0人  牛会骑自行车
效果图

说明:组件区域只有遮罩那一块,其他内容放页面里;需要先给页面设置一个放刮层的容器,组件在里面会自动撑开。

思路:

点:

组件代码:

<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>

tada~一个刮奖组件就完成啦

上一篇 下一篇

猜你喜欢

热点阅读