js实现图片标记点位在不同分辨率下的坐标映射

2,931 阅读2分钟

前言

在开发中,除了常用的图片缩放和剪切以外,还有对图片进行标记点位的功能,如鼠标点击图片标记点位和已有的标记点位展示,如果不设置固定宽高的img标签,而是使用自适应img标签,在不同分辨率下会使图片大小不一,从而导致坐标的改变,使标记点位偏移。

计算公式及原理

  • 相对原图坐标映射计算:
    1. 当前图片比原图宽高多出(减少)了多少px = clientWidth - naturalWidth(或自定义宽高);
    2. 多出(减少)的px占当前图片的百分之几 = 步骤1的得数 / clientWidth;
    3. 原图宽高在当前图片中占比百分之几 = 1 - 步骤2的得数;
    4. 缩放图片的坐标映射为原图的坐标(称为映射坐标) = 步骤3的得数 * offsetX(鼠标坐标);
  • 知道了每个计算的含义,接下来可以把计算简化为:(naturalWidth / clientWidth) * offsetX;
  • 标记坐标映射计算:
    1. 缩放系数(大于1是放大,小于1是缩小) = clientWidth / 原图宽高 or 自定义宽高
    2. 标记在缩放图片上的实际坐标 = 缩放系数 * 映射坐标

QQ截图20211225222158.png

QQ截图20211225222246.png

上述“原图”和“naturalWidth”可以是自己定义的宽高

提示:

  1. 测试使用的chrome和FireFox,发现offsetWidth(Height)和offsetX(Y)会相差1px。目前使用三目运算符来判断解决相差1px的问题。
  2. 目前发现的规律是使用高度或宽度有>=0.5的小数点,使用flex,inline-block或不使用也会相差1px。
  3. 以上说的相差1px并不是全部图片,而是有些会有些不会(单独一张图片时又会是正常)

效果预览

res.gif

源代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        html, body {
            margin: 0;
            width: 100%;
        }

        h1 {
            margin: 0;
        }

        .img1920, .img319 {
            display: flex;
        }

        .img1920 div, .img319 div{
            position: relative;
        }

        .img1920 div, .img319 div {
            margin-right: 30px;
        }

        .tips {
            position: absolute;
            left: 0;
            top: 0;
            z-index: 100;
        }

        .tips p {
            margin: 0;
            color: red;
        }
    </style>
</head>
<body>
<div class="tips">
    <span class="icon">👆</span>
    <p></p>
</div>
<h1>319*400</h1>
<div class="img319">
    <div>
        <img src="123.jpg" alt="" style="width: 220px">
        <p>小图X:<span>0</span></p>
        <p>小图Y:<span>0</span></p>
    </div>
    <div>
        <img src="123.jpg" alt="" style="width: 420px">
        <p>中图X:<span>0</span></p>
        <p>中图Y:<span>0</span></p>
    </div>
    <div>
        <img src="123.jpg" alt="" style="width: 643px">
        <p>大图X:<span>0</span></p>
        <p>大图Y:<span>0</span></p>
    </div>
    <div>
        <img src="123.jpg" alt="" style="width: 319px;height: 400px">
        <p>原图X:<span>0</span></p>
        <p>原图Y:<span>0</span></p>
    </div>
</div>
<h1>1920*1080:</h1>
<div class="img1920">
    <div>
        <img src="1532619760582.jpg" alt="" style="width: 220px">
        <p>小图X:<span>0</span></p>
        <p>小图Y:<span>0</span></p>
    </div>
    <div>
        <img src="1532619760582.jpg" alt="" style="width: 420px">
        <p>中图X:<span>0</span></p>
        <p>中图Y:<span>0</span></p>
    </div>
    <div>
        <img src="1532619760582.jpg" alt="" style="width: 643px">
        <p>大图X:<span>0</span></p>
        <p>大图Y:<span>0</span></p>
    </div>
</div>
<h1>1920*1080放大2000*2000:</h1>
<div>
    <div>
        <img src="1532619760582.jpg" alt="" style="width: 2000px;height: 2000px">
        <p>原图X:<span>0</span></p>
        <p>原图Y:<span>0</span></p>
    </div>
</div>
<script>
    let doc = document
    let img = doc.querySelectorAll('img');
    let len = img.length;
    let tips = doc.querySelector('.tips')
    let img319 = doc.querySelector('.img319')
    let img319Div = img319.querySelectorAll('div')
    let body = doc.querySelector('body')
    let x = 0;
    let y = 0;
    let numY = 0;
    let numX = 0;

    body.onclick = function (e) {
        tips.style.left = `${e.pageX - 5}px`
        tips.style.top = `${e.pageY - 2}px`

        tips.querySelector('p').innerText = `x:${x} y:${y}`;
    }

    /**
     * 标记坐标展示计算
     **/
    img319.onclick = function (e) {
        for(let item of img319Div){
            let span = doc.createElement('span')
            span.style.width = '10px'
            span.style.height = '10px'
            span.style.background = 'red'
            span.style.position = 'absolute'
            span.style.left = `${((item.querySelector('img').clientWidth / item.querySelector('img').naturalWidth) * x) - 5}px`
            span.style.top = `${((item.querySelector('img').clientHeight / item.querySelector('img').naturalHeight) * y) - 5}px`
            item.append(span)
        }
    }

    for (let i = 0; i < len; i++) {
        img[i].onmousemove = mousemove
    }

    function mousemove(e) {
        // console.log('X:', this.offsetWidth, e.offsetX, 'Y:', this.offsetHeight, e.offsetY);
        numY = this.offsetHeight - 1 === e.offsetY ? this.offsetHeight : e.offsetY;
        numX = this.offsetWidth - 1 === e.offsetX ? this.offsetWidth : e.offsetX;

        // 缩放多出(减少)了多少px = this.clientWidth - this.naturalWidth
        // 缩放多出(减少)了多少px占当前图片的百分之几 = 缩放多出(减少)了多少px / this.clientWidth
        // 原图片宽高在当前图片宽高中占比百分之几 = 1 - 缩放多出(减少)了多少px占当前图片的百分之几
        // 鼠标在缩放图片上时映射为原图宽高的坐标 = 原图片宽高在当前图片宽高中占比百分之几 * numX
        // console.log((1 - (this.clientWidth - this.naturalWidth) / this.clientWidth) * numX)

        // 可简化为 (this.naturalWidth / this.clientWidth) * numX
        x = Math.round((this.naturalWidth / this.clientWidth) * numX);
        y = Math.round((this.naturalHeight / this.clientHeight) * numY);

        this.nextElementSibling.querySelector('span').innerText = x;
        this.nextElementSibling.nextElementSibling.querySelector('span').innerText = y
    }
</script>
</body>
</html>

建议上传后端标记坐标时,带上当前创建这条标记时的图片宽高,接口获取到标记坐标和宽高后在循环计算