You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3中响应式Image Map坐标适配及区域点击失效问题求助

解决方案:Image Map响应式适配与点击事件修复

一、先修复点击事件不生效问题

Vue3中直接在<area>标签上用@click可能因浏览器对map/area的事件处理机制失效,提供两种可行方案:

方案1:监听map元素的点击事件,判断目标area

给每个<area>添加data-key标识按键,点击map时通过事件目标获取key值:

<template>
  <div class="passwordkeyboard">
    <img ref="keypadImg" class="keypadimg" src="/pwd.png" usemap="#image-map" />
    <map id="image-map" name="image-map" @click="handleMapClick">
      <area v-for="(coords, key) in keyCoords" :key="key" :data-key="key" :alt="`key${key}`" :coords="coords" shape="rect">
    </map>
  </div>
</template>

<script>
export default {
  props: ['selfValue'],
  data() {
    return {
      current: '',
      keyCoords: {
        1: "57,15,9,64",
        2: "76,16,125,64",
        8: "144,16,191,64",
        3: "211,15,260,64",
        4: "278,15,327,64",
        7: "9,95,58,144",
        5: "76,95,125,144",
        6: "143,95,192,144",
        0: "211,95,260,144",
        9: "277,95,328,144"
      }
    };
  },
  methods: {
    press(key) {
      this.current = `${this.current}${key}`;
      console.log(key);
      this.$emit('pressed', this.current);
    },
    handleMapClick(event) {
      const target = event.target;
      if (target.tagName === 'AREA') {
        const key = target.getAttribute('data-key');
        this.press(key);
      }
    }
  },
  watch: {
    current() {
      this.$emit('pressed', this.current);
    },
    selfValue(newVal) {
      this.current = newVal;
    }
  }
};
</script>

方案2:原生事件绑定(mounted中手动绑定)

通过原生JS给每个area绑定click事件,适合需要兼容旧浏览器的场景:

<script>
export default {
  // ... 其他data和方法同方案1
  mounted() {
    const areas = document.querySelectorAll('#image-map area');
    areas.forEach(area => {
      const key = area.getAttribute('data-key');
      area.addEventListener('click', () => this.press(key));
    });
  }
};
</script>

二、实现Image Map坐标的响应式适配

核心逻辑是:基于图片原始尺寸与当前显示尺寸的缩放比例,动态更新每个area的坐标值,步骤如下:

完整示例代码

<script>
export default {
  props: ['selfValue'],
  data() {
    return {
      current: '',
      // 存储图片原始尺寸的坐标(数组形式方便计算)
      originalKeyCoords: {
        1: [57,15,9,64],
        2: [76,16,125,64],
        8: [144,16,191,64],
        3: [211,15,260,64],
        4: [278,15,327,64],
        7: [9,95,58,144],
        5: [76,95,125,144],
        6: [143,95,192,144],
        0: [211,95,260,144],
        9: [277,95,328,144]
      },
      scaledKeyCoords: {},
      originalImgWidth: 0,
      originalImgHeight: 0
    };
  },
  methods: {
    press(key) {
      this.current = `${this.current}${key}`;
      console.log(key);
      this.$emit('pressed', this.current);
    },
    // 计算缩放后的坐标
    calculateScaledCoords() {
      const img = this.$refs.keypadImg;
      if (!img) return;

      const currentWidth = img.offsetWidth;
      const currentHeight = img.offsetHeight;
      // 计算宽高缩放比例
      const scaleX = currentWidth / this.originalImgWidth;
      const scaleY = currentHeight / this.originalImgHeight;

      // 遍历原始坐标,生成缩放后的字符串格式坐标
      this.scaledKeyCoords = Object.fromEntries(
        Object.entries(this.originalKeyCoords).map(([key, coords]) => {
          const scaled = coords.map((val, index) => {
            // x坐标用width比例,y坐标用height比例,取整保证像素精度
            return index % 2 === 0 ? Math.round(val * scaleX) : Math.round(val * scaleY);
          });
          return [key, scaled.join(',')];
        })
      );
    },
    // 初始化图片原始尺寸
    initImgSize() {
      const img = this.$refs.keypadImg;
      if (img.complete) {
        this.originalImgWidth = img.naturalWidth;
        this.originalImgHeight = img.naturalHeight;
        this.calculateScaledCoords();
      } else {
        img.addEventListener('load', () => {
          this.originalImgWidth = img.naturalWidth;
          this.originalImgHeight = img.naturalHeight;
          this.calculateScaledCoords();
        });
      }
    }
  },
  mounted() {
    this.initImgSize();
    // 监听窗口 resize 事件,实时更新坐标
    window.addEventListener('resize', this.calculateScaledCoords);
  },
  beforeUnmount() {
    // 移除监听,避免内存泄漏
    window.removeEventListener('resize', this.calculateScaledCoords);
  },
  watch: {
    current() {
      this.$emit('pressed', this.current);
    },
    selfValue(newVal) {
      this.current = newVal;
    }
  }
};
</script>

<template>
  <div class="passwordkeyboard">
    <img ref="keypadImg" class="keypadimg" src="/pwd.png" usemap="#image-map" />
    <map id="image-map" name="image-map" @click="handleMapClick">
      <area v-for="(coords, key) in scaledKeyCoords" :key="key" :data-key="key" :alt="`key${key}`" :coords="coords" shape="rect">
    </map>
  </div>
</template>

<style scoped>
/* 确保图片自适应容器,触发缩放逻辑 */
.keypadimg {
  max-width: 100%;
  height: auto;
}
</style>

关键说明

  • naturalWidth/naturalHeight:获取图片的原始尺寸,不受CSS缩放影响,是计算比例的基准。
  • resize监听:窗口大小变化时重新计算坐标,组件销毁时需移除监听避免内存泄漏。
  • 坐标取整:coords要求整数像素值,用Math.round保证精度。

内容的提问来源于stack exchange,提问作者bahy llam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 05:12:21