Skip to content

点击五角星区域触发事件,五角星区域外不触发怎么实现?

最近遇到一个小问题,大屏首页出现了异形的点击按钮,几个六边形按钮组合在一起的场景。

但是在点击的时候会出现问题,因为DOM是个矩形,所以在点击区域外也会触发事件。

简单研究了一下,可以通过三种方案实现特殊区域的点击方法。

最后一种比较邪门!

SVG

通过SVG创建图形,将点击事件绑定在 polygon 元素上,实现特定区域的点击。

html
<div @click="handleArea">
    <svg width="200" height="200" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <polygon
                points="50,5 61,35 95,35 67,57 78,90 50,70 22,90 33,57 5,35 39,35"
                fill="orange" @click.stop="handleInArea" />
    </svg>
</div>

clip-path

通过CSS的 clip-path 属性对可视区域进行裁切,实现特定区域的点击。

html
<div @click="handleArea">
    <div class="star" @click.stop="handleInArea"></div>
</div>
css
.star {
    width: 200px;
    height: 200px;
    background-color: orange;
    clip-path: polygon(
        50% 5%,
        61% 35%,
        95% 35%,
        67% 57%,
        78% 90%,
        50% 70%,
        22% 90%,
        33% 57%,
        5% 35%,
        39% 35%
    );
}

RGBA判断

通过将页面转化为 canvas,获取点击区域像素点的 RGBA 色值的方式,判断点击区域是否为特定区域。

html
<div @click="handleGetRGB">
    <div class="start2">
        <div class="star-container">
            <div class="star-arm"></div>
            <div class="star-arm"></div>
            <div class="star-arm"></div>
            <div class="star-arm"></div>
            <div class="star-arm"></div>
        </div>
    </div>
    <div style="margin-top: 20px">Js</div>
</div>
css
.start2 {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 240px;
    margin-top: -60px;
}

.star-container {
    position: relative;
    width: 200px;
    height: 200px;
}

.star-arm {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
    border-left: 30px solid transparent;   /* 加宽角的宽度 */
    border-right: 30px solid transparent;
    border-bottom: 100px solid orange;     /* 增加高度,控制五角星的纵向尺寸 */
    transform-origin: 50% 100%;            /* 设置旋转中心在底部 */
}

/* Rotate each arm to form a star */
.star-arm:nth-child(1) { transform: translate(-50%, -50%) rotate(0deg); }
.star-arm:nth-child(2) { transform: translate(-50%, -50%) rotate(72deg); }
.star-arm:nth-child(3) { transform: translate(-50%, -50%) rotate(144deg); }
.star-arm:nth-child(4) { transform: translate(-50%, -50%) rotate(216deg); }
.star-arm:nth-child(5) { transform: translate(-50%, -50%) rotate(288deg); }
js
handleGetRGB(event) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // 获取点击位置在视口中的坐标
    const clickX = event.clientX;
    const clickY = event.clientY;

    // 获取视口和文档尺寸
    const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    const viewportWidth = window.innerWidth;
    const viewportHeight = window.innerHeight;

    // 设置 canvas 尺寸与文档一致(而非视口)
    canvas.width = document.documentElement.scrollWidth;
    canvas.height = document.documentElement.scrollHeight;

    html2canvas(document.body).then(canvasSnapshot => {
        // 创建一个临时 canvas 用于缩放计算
        const tempCanvas = document.createElement('canvas');
        const tempCtx = tempCanvas.getContext('2d');

        // 设置 tempCanvas 尺寸为截图 canvas 的大小
        tempCanvas.width = canvasSnapshot.width;
        tempCanvas.height = canvasSnapshot.height;

        // 绘制 html2canvas 的结果到 tempCanvas
        tempCtx.drawImage(canvasSnapshot, 0, 0);

        // 计算点击点在完整页面中的绝对位置
        const fullPageWidth = canvas.width;
        const fullPageHeight = canvas.height;

        // 视口比例 -> 页面实际比例
        const scaleX = tempCanvas.width / fullPageWidth;
        const scaleY = tempCanvas.height / fullPageHeight;

        const pixelX = Math.floor(clickX * scaleX);
        const pixelY = Math.floor(clickY * scaleY);

        // 获取像素数据
        try {
            const pixel = tempCtx.getImageData(pixelX, pixelY, 1, 1).data;
            if(pixel[0] === 204 && pixel[1] === 204 && pixel[2] === 204 && pixel[3] === 255) {
                console.log('点击区域外!');
            }else if(pixel[0] === 255 && pixel[1] === 165 && pixel[2] === 0 && pixel[3] === 255) {
                console.log('点击区域内!');
            }
        } catch (e) {
            console.error('无法获取像素数据:', e);
        }
    });
}

总结

在生产环境中,建议通过SVG方案来实现特定区域的点击,最后一种方案其实有点儿“邪门”,不太推荐使用。(面试可以扯扯)

完整代码:

js
<template>
    <div class="wrap">
        <div @click="handleArea">
            <svg width="200" height="200" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
                <polygon
                    points="50,5 61,35 95,35 67,57 78,90 50,70 22,90 33,57 5,35 39,35"
                    fill="orange" @click.stop="handleInArea" />
            </svg>
            <div>SVG</div>
        </div>
        <div @click="handleArea">
            <div class="star" @click.stop="handleInArea"></div>
            <div>clip-path</div>
        </div>
        <div @click="handleGetRGB">
            <div class="start2">
                <div class="star-container">
                    <div class="star-arm"></div>
                    <div class="star-arm"></div>
                    <div class="star-arm"></div>
                    <div class="star-arm"></div>
                    <div class="star-arm"></div>
                </div>
            </div>
            <div style="margin-top: 20px">Js</div>
        </div>
    </div>
</template>

<script>
import html2canvas from "html2canvas";
export default {
    name: 'AreaClick',
    data() {
        return {
            msg: 'Welcome to Your Vue.js App'
        }
    },
    methods: {
        handleArea() {
            console.log('点击区域外!');
        },
        handleInArea() {
            console.log('点击区域内!');
        },
        handleGetRGB(event) {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');

            // 获取点击位置在视口中的坐标
            const clickX = event.clientX;
            const clickY = event.clientY;

            // 获取视口和文档尺寸
            const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
            const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
            const viewportWidth = window.innerWidth;
            const viewportHeight = window.innerHeight;

            // 设置 canvas 尺寸与文档一致(而非视口)
            canvas.width = document.documentElement.scrollWidth;
            canvas.height = document.documentElement.scrollHeight;

            html2canvas(document.body).then(canvasSnapshot => {
                // 创建一个临时 canvas 用于缩放计算
                const tempCanvas = document.createElement('canvas');
                const tempCtx = tempCanvas.getContext('2d');

                // 设置 tempCanvas 尺寸为截图 canvas 的大小
                tempCanvas.width = canvasSnapshot.width;
                tempCanvas.height = canvasSnapshot.height;

                // 绘制 html2canvas 的结果到 tempCanvas
                tempCtx.drawImage(canvasSnapshot, 0, 0);

                // 计算点击点在完整页面中的绝对位置
                const fullPageWidth = canvas.width;
                const fullPageHeight = canvas.height;

                // 视口比例 -> 页面实际比例
                const scaleX = tempCanvas.width / fullPageWidth;
                const scaleY = tempCanvas.height / fullPageHeight;

                const pixelX = Math.floor(clickX * scaleX);
                const pixelY = Math.floor(clickY * scaleY);

                // 获取像素数据
                try {
                    const pixel = tempCtx.getImageData(pixelX, pixelY, 1, 1).data;
                    if(pixel[0] === 204 && pixel[1] === 204 && pixel[2] === 204 && pixel[3] === 255) {
                        console.log('点击区域外!');
                    }else if(pixel[0] === 255 && pixel[1] === 165 && pixel[2] === 0 && pixel[3] === 255) {
                        console.log('点击区域内!');
                    }
                } catch (e) {
                    console.error('无法获取像素数据:', e);
                }
            });
        }
    }
}
</script>

<style scoped>
.wrap {
    width: 800px;
    height: 240px;
    background: #ccc;
    display: flex;
    justify-content: space-between;
    text-align: center;
}

.star {
    width: 200px;
    height: 200px;
    background-color: orange;
    clip-path: polygon(
        50% 5%,
        61% 35%,
        95% 35%,
        67% 57%,
        78% 90%,
        50% 70%,
        22% 90%,
        33% 57%,
        5% 35%,
        39% 35%
    );
}
.start2 {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 240px;
    margin-top: -60px;
}

.star-container {
    position: relative;
    width: 200px;
    height: 200px;
}

.star-arm {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
    border-left: 30px solid transparent;   /* 加宽角的宽度 */
    border-right: 30px solid transparent;
    border-bottom: 100px solid orange;     /* 增加高度,控制五角星的纵向尺寸 */
    transform-origin: 50% 100%;            /* 设置旋转中心在底部 */
}

/* Rotate each arm to form a star */
.star-arm:nth-child(1) { transform: translate(-50%, -50%) rotate(0deg); }
.star-arm:nth-child(2) { transform: translate(-50%, -50%) rotate(72deg); }
.star-arm:nth-child(3) { transform: translate(-50%, -50%) rotate(144deg); }
.star-arm:nth-child(4) { transform: translate(-50%, -50%) rotate(216deg); }
.star-arm:nth-child(5) { transform: translate(-50%, -50%) rotate(288deg); }
</style>