1 说明

  基本思路是把图片划分为多个区域,点击图片的时候获取到坐标,判断是否在指定的区域内

2 划分图片区域

2.1 采集图片坐标的工具

2.1.1  网址

    image

 

2.2.2 说明

  这个网址可以上传图片,然后用多个点画区域,获取到这些点的坐标

 

2.2.3 示例

1)第一步:上传图片

  image

 

2)第二部:在下方去操作,框出想要的形状

  下面这里一条代表框出一个区域

  shape:要框出的区域的形状  rect矩形  poly多边形  circle圆形

  image

 

    如下图,选择的多边形,把左边这个人框出来了

  

image

 

 3)第三步,查看点的坐标

  点击 show me the code,可以看到点位的坐标coords,每两个数字是一组,分别是x和y坐标,如下面第一组就是x:157,y:137。这个坐标是按照你取点的顺序排列的。

  另外,这个坐标系的原点是图片的左上角,横向是x轴,纵向是y轴。坐标大小是按照图片的分辨率来的

  现在我们就获取到了一组坐标,可以形成一个多边形区域-也就是我们想要的区域

  image

 

3 编码

3.1 json

{
  "navigationBarTitleText": "不规则区域点击示例"
}

 

3.2 wxss

/* pages/index/index.wxss */
.page-container {
  padding: 20rpx;
  background-color: #f5f5f5;
  min-height: 100vh;
}

.page-title {
  display: block;
  font-size: 36rpx;
  font-weight: bold;
  margin-bottom: 20rpx;
  color: #333;
}

/* 图片容器:相对定位,确保Canvas能叠在图片上 */
.img-container {
  position: relative;
  width: 100%; /* 容器宽度占满屏幕,图片自适应 */
}

/* 目标图片:宽度自适应,避免拉伸 */
.target-img {
  width: 100%;
  border-radius: 8rpx;
  vertical-align: top; /* 消除图片底部空白 */
}

/* Canvas:绝对定位,不拦截点击事件 */
.border-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%; /* 与图片宽度一致 */
  height: 100%; /* 与图片高度一致 */
  pointer-events: none; /* 关键:允许点击穿透到图片 */
}

.result {
  margin-top: 30rpx;
  padding: 20rpx;
  background-color: #fff;
  border-radius: 8rpx;
  font-size: 32rpx;
  color: #666;
}

 

3.3 wxml

<!-- pages/index/index.wxml -->
<view class="page-container">
  <!-- 图片容器:bindtouchstart绑定事件,确保能触发 -->
  <view class="img-container" bindtouchstart="handleTouch">
    <!-- 目标图片:mode="widthFix"保持宽高比 -->
    <image 
      class="target-img" 
      src="{{ imgSrc }}" 
      mode="widthFix" 
      bindload="handleImgLoad"
    ></image>
  </view>

  <!-- 点击结果展示,为了方便的看到点击效果 -->
  <view class="result" wx:if="{{ selectedArea }}">
    点击区域:{{ selectedArea }}
  </view>
</view>

 

3.4 js

3.4.1 数据

1)imgSrc换成自己的图片路径

2)originSize 图片的原始大小(分辨率)

3)clickAreas:区域的数据的数组。下面是在图片上框出了两个区域。

  points:区域的坐标数组

 

3.4.2 图片缩放-坐标也需要缩放

  图片在放到小程序后,大概率是会变更大小的,所以需要做一下坐标缩放适应

  在初始化函数handleImgLoad中,获取到了实际的图片大小,根据实际图片大小和原图大小计算出缩放比例,最后把points里面的坐标也缩放,存放到adaptedAreas 中,后面实际使用的是缩放后的坐标

 

3.4.3 点击图片事件 

函数handleTouch
获取到的pageX和pageY是相对于页面左上角的坐标,而不是相对于图片左上角的坐标,所以需要转换下
    const pageX = touch.pageX; // 相对于页面左上角的X坐标
    const pageY = touch.pageY; // 相对于页面左上角的Y坐标

  使用pageX减去图片左上角相对于页面左上角的横向距离

  使用pageY减去图片左上角相对于页面左上角的总向距离

  获取到的就是点击的点相对于图片的坐标了

    const clickXInImg = pageX - imgRect.left;
    const clickYInImg = pageY - imgRect.top;

3.4.4 判断点击的位置的坐标是否在区域内

 函数isPointInPolygon就是用来进行判断的算法

 参数分别是这个点的横坐标和纵坐标以及要判断的区域的坐标集合

// pages/index/index.js
Page({
  data: {
    imgSrc: 'https://xxx.aliyuncs.com/room/B-2.jpg', // 图片路径(需自行替换)
    originSize: { width: 2004, height: 1437 }, // 图片原始尺寸(必须与实际一致)
    clickAreas: [ // 不规则区域配置(相对坐标,0~1)
      {
        id: 'area1',
        name: '区域1',
        color: '#ff4d4f',
        points: [
          { x: 420, y: 1138},
          { x: 489, y:938 },
          { x: 640, y: 835 },
          { x: 535, y: 726 },
          { x: 311, y: 821 },
          { x: 289, y: 857 },
          { x: 153, y: 926 },
          { x: 82, y: 1081 },
          { x: 79, y: 1101 }
        ]
      },
      {
        id: 'area2',
        name: '区域2',
        color: '#ff4d4f',
        points: [
          { x: 525, y: 1136},
          { x: 727, y:1203 },
          { x: 992, y: 1118 },
          { x: 948, y: 986 },
          { x: 700, y: 1055 },
          { x: 546, y: 1041 }
        ]
      }
    ],
    adaptedAreas: [], // 适配后的绝对坐标区域
    selectedArea: '', // 选中的区域名称
    canvasCtx: null, // Canvas上下文
    imgRect: {} // 图片渲染后的位置信息(含宽高、距离顶部/左侧的距离)
  },

  onLoad() {
  },



  // 图片加载完成:获取渲染后的位置和尺寸(关键,用于坐标适配)
  handleImgLoad(e) {
    const _this = this;
    // 获取图片渲染后的位置信息(含宽高、left、top)
    const query = this.createSelectorQuery();
    query.select('.target-img')
      .boundingClientRect(rect => {
        // rect包含:width(渲染宽)、height(渲染高)、left(距离屏幕左侧)、top(距离屏幕顶部)
        _this.setData({ imgRect: rect });
        console.log('rect',rect)
        // 计算缩放比例(原始尺寸 → 渲染尺寸)
        const scaleX = rect.width / _this.data.originSize.width;
        const scaleY = rect.height / _this.data.originSize.height;

        // 适配坐标:相对坐标 → 绝对坐标(基于图片渲染后的位置)
        const adaptedAreas = _this.data.clickAreas.map(area => ({
          ...area,
          points: area.points.map(point => ({
            // 绝对坐标 = 相对坐标 × 缩放比例(基于图片自身的渲染尺寸)
            x: point.x * scaleX,
            y: point.y * scaleY
          }))
        }));
        console.log('adaptedAreas',adaptedAreas)
        _this.setData({ adaptedAreas }, () => {
        });
      })
      .exec();
  },


  // 处理触摸事件:正确获取坐标并判断区域
  handleTouch(e) {
    const { adaptedAreas, imgRect } = this.data;
    if (!imgRect.width || adaptedAreas.length === 0) return;

    // 1. 正确获取触摸坐标(关键:用pageX/pageY,避免滚动影响)
    const touch = e.touches[0];
    const pageX = touch.pageX; // 相对于页面左上角的X坐标
    const pageY = touch.pageY; // 相对于页面左上角的Y坐标

    // 2. 计算点击点相对于图片左上角的坐标(核心适配)
    // 图片左上角的页面坐标 = imgRect.left(X)、imgRect.top(Y)
    // 点击点在图片内的坐标 = 触摸坐标 - 图片左上角坐标
    const clickXInImg = pageX - imgRect.left;
    const clickYInImg = pageY - imgRect.top;

    console.log('touch',touch.pageX,touch.pageY)
    console.log('图片位置:', imgRect);
    console.log('图片内坐标:', clickXInImg, clickYInImg);
    console.log('区域',adaptedAreas)
    // 3. 验证点击是否在图片范围内(避免点击图片外区域误触发)
    if (
      clickXInImg < 0 || 
      clickXInImg > imgRect.width || 
      clickYInImg < 0 || 
      clickYInImg > imgRect.height
    ) {
      this.setData({ selectedArea: '' });
      return;
    }

    // 4. 判断点击点属于哪个不规则区域
    for (const area of adaptedAreas) {
      if (this.isPointInPolygon(clickXInImg, clickYInImg, area.points)) {
        this.setData({ selectedArea: area.name });
        // 可添加额外逻辑(如跳转、弹窗)
        wx.showToast({ title: `选中${area.name}`, icon: 'none' });
        break;
      }
    }
  },

  // 核心算法:射线法判断点是否在多边形内
  isPointInPolygon(clickX, clickY, points) {
    let isInside = false;
    const len = points.length;

      let c = false;
      for (let i = 0, j = len-1; i < len; j = i++) {
        if ( ((points[i].y>clickY) != (points[j].y>clickY)) &&
         (clickX < (points[j].x-points[i].x) * (clickY-points[i].y) / (points[j].y-points[i].y) + points[i].x) )
           c = !c;
      }
      console.log('isPointInPolygon',c)
      return c;
  
  }
});