起因:
这个模块样式和功能反复改,太tm烦人了。
只想说一句 产品傻逼
解决方案:
之前在开源程序交流群看到有人做过热区点击,所以也想自己试试。
一开始在小程序上面获取元素信息还费了点事,ref 还获取不到元素,问了下 AI 解决了。
-
先获点击的坐标相当于这个元素的位置
-
定义四个小区域范围
-
判断点击坐标是否在区域范围内,由于是小程序还要涉及单位换算,然后执行指定方法。
代码:
template
给元素设置个背景图片,样式代码就省略了。
<view class="mod" id="mod" @click="modClick($event)"> </view>
data里面定义数据
主要是相对于点击元素的 x,y 坐标,以及自身的宽高。
modList: [
{
id: 1,
name: "收藏",
info: {
x: 50,
y: 20,
width: 108,
height: 108,
},
},
{
id: 2,
name: "积分",
info: {
x: 218,
y: 20,
width: 108,
height: 108,
},
},
{
id: 3,
name: "客服",
info: {
x: 386,
y: 20,
width: 108,
height: 108,
},
},
{
id: 4,
name: "红包卡卷",
info: {
x: 554,
y: 20,
width: 108,
height: 108,
},
},
],
methods
modClick(e) {
// 安全地获取第一个触摸点的坐标
const touch =
(e.touches && e.touches[0]) ||
(e.changedTouches && e.changedTouches[0]);
if (!touch) {
console.error("No touch data available");
return;
}
const x = touch.pageX;
const y = touch.pageY;
// 使用 uni-app 的选择器查询对象
const query = uni.createSelectorQuery().in(this);
query
.select("#mod")
.boundingClientRect((rect) => {
if (!rect) {
console.error("Element size could not be retrieved");
return;
}
const relativeX = x - rect.left;
const relativeY = y - rect.top;
let clickedInside = false;
let clickedItemName = "";
// 遍历 modList 检查点击位置
this.modList.forEach((item) => {
// 确保 item.info 包含必要数据
if (
item.info &&
item.info.x &&
item.info.y &&
item.info.width &&
item.info.height
) {
const isInside = isClickInsideSmallArea(item.info, {
clickX: relativeX,
clickY: relativeY,
});
if (isInside) {
clickedInside = true;
clickedItemName = item.name;
return; // 找到匹配项,停止遍历
}
} else {
console.warn(`Incomplete info data for item id: ${item.id}`);
}
});
console.log("Clicked inside a small area:", clickedInside);
if (clickedInside) {
console.log("Clicked on:", clickedItemName);
switch (clickedItemName) {
case "收藏":
this.handleCell(1);
break;
case "积分":
break;
case "客服":
break;
case "红包卡卷":
break;
default:
break;
}
}
})
.exec();
},
utils.js
/**
* 将 rpx 转换为实际的 px
* @param {number} rpx - 需要转换的 rpx 值
* @param {number} screenWidth - 设备屏幕宽度,单位为px。如果未提供,则使用默认值。
* @returns {number} 转换后的 px 值
*/
export function convertRpxToPx(rpx, screenWidth) {
// 校验screenWidth是否为数字
if (typeof screenWidth !== "number") {
throw new TypeError("screenWidth must be a number");
}
// 边界条件处理
if (rpx <= 0 || screenWidth <= 0) {
throw new Error("rpx and screenWidth values must be greater than 0");
}
// 默认屏幕宽度,对应 iPhone6 的屏幕宽度
const defaultScreenWidth = 375;
// 使用提供的屏幕宽度,若无则使用默认值
screenWidth = screenWidth || defaultScreenWidth;
// 根据屏幕宽度转换 rpx 为 px
const px = rpx * (screenWidth / 750);
return px;
}
/**
* 获取窗口信息并转换设计稿单位到实际显示单位的函数
* @param {number} rpxValue - 需要转换的 rpx 值
* @returns {Promise<number>} 转换后的 px 值的 Promise
*/
export function convertDesignToActual(rpxValue) {
const windowInfo = wx.getWindowInfo();
const actualScreenWidth = windowInfo.windowWidth;
const pxValue = convertRpxToPx(rpxValue, actualScreenWidth);
// 移除不必要的日志输出,或者改为可配置的日志级别控制
// console.log(`${rpxValue}rpx 等于 ${pxValue.toFixed(2)}px`);
return pxValue;
}
/**
* 判断点击位置是否在小区域内
* @param {Object} smallArea - 小区域的坐标和尺寸,单位为rpx
* @param {number} clickX - 点击位置的x坐标,单位为px
* @param {number} clickY - 点击位置的y坐标,单位为px
* @returns {boolean} - 如果点击位置在小区域内返回true,否则返回false
*/
export function isClickInsideSmallArea(smallArea, { clickX, clickY }) {
// 将小区域的坐标和尺寸从rpx转换为px
const {
x: smallAreaRpxX,
y: smallAreaRpxY,
width: smallAreaRpxWidth,
height: smallAreaRpxHeight,
} = smallArea;
const smallAreaX = convertDesignToActual(smallAreaRpxX);
const smallAreaY = convertDesignToActual(smallAreaRpxY);
const smallAreaWidth = convertDesignToActual(smallAreaRpxWidth);
const smallAreaHeight = convertDesignToActual(smallAreaRpxHeight);
// 计算点击位置是否在小区域内
const isInsideX =
clickX >= smallAreaX && clickX <= smallAreaX + smallAreaWidth;
const isInsideY =
clickY >= smallAreaY && clickY <= smallAreaY + smallAreaHeight;
// 如果点击位置在小区域的水平和垂直范围内,则返回 true
return isInsideX && isInsideY;
}