如何为图片缩放平移脚本添加无跳变的移动端触摸捏合缩放支持
图片移动端触摸缩放平移实现(解决捏合位置跳变)
已有鼠标滚轮缩放、拖拽平移功能正常,需添加移动端触摸支持,核心解决捏合操作时基于当前缩放比例计算,避免图片位置跳变的问题。
修正后的完整代码
JavaScript
const img = document.getElementById('image'); const stage = document.querySelector('.stage'); const container = document.querySelector('.container'); let mouseX; let mouseY; let mouseTX; let mouseTY; let panning = false; const ts = { scale: 1, translate: { x: 0, y: 0 } }; // 鼠标滚轮缩放 stage.onwheel = function(event) { event.preventDefault(); var func = stage.onwheel; stage.onwheel = null; let rec = stage.getBoundingClientRect(); let x = (event.clientX - rec.x) / ts.scale; let y = (event.clientY - rec.y) / ts.scale; let delta = (event.wheelDelta ? event.wheelDelta : -event.deltaY); ts.scale = (delta > 0) ? (ts.scale + 0.2) : (ts.scale - 0.2); if (ts.scale < 1) ts.scale = 1 let m = (delta > 0) ? 0.1 : -0.1; ts.translate.x += (-x * m * 2) + (stage.offsetWidth * m); ts.translate.y += (-y * m * 2) + (stage.offsetHeight * m); setTransform(); stage.onwheel = func; }; // 鼠标拖拽平移 stage.onmousedown = function(event) { event.preventDefault(); panning = true; mouseX = event.clientX; mouseY = event.clientY; mouseTX = ts.translate.x; mouseTY = ts.translate.y; }; stage.onmouseup = () => panning = false; document.onmouseup = () => panning = false; stage.onmousemove = function(event) { event.preventDefault(); if (!panning) return; const x = event.clientX; const y = event.clientY; ts.translate.x = mouseTX + (x - mouseX); ts.translate.y = mouseTY + (y - mouseY); setTransform(); }; // 设置变换 function setTransform() { const steps = `translate(${ts.translate.x}px,${ts.translate.y}px) scale(${ts.scale}) translate3d(0,0,0)`; stage.style.transform = steps; } // 重置 function reset() { ts.scale = 1; ts.translate = { x: 0, y: 0 }; stage.style.transform = 'none'; } setTransform(); // 移动端触摸支持 if ("ontouchstart" in window) { const touchState = { isPinching: false, start: { scale: 1, translateX: 0, translateY: 0, midX: 0, midY: 0, distance: 0 } }; // 计算两指距离 const getTouchDistance = (event) => { return Math.hypot( event.touches[0].pageX - event.touches[1].pageX, event.touches[0].pageY - event.touches[1].pageY ); }; // 获取触摸中点相对于容器的坐标 const getTouchMidpoint = (event) => { const rect = container.getBoundingClientRect(); return { x: (event.touches[0].pageX + event.touches[1].pageX) / 2 - rect.left, y: (event.touches[0].pageY + event.touches[1].pageY) / 2 - rect.top }; }; stage.addEventListener('touchstart', (event) => { event.preventDefault(); if (event.touches.length === 2) { // 开始捏合 touchState.isPinching = true; const midPoint = getTouchMidpoint(event); // 记录当前状态:缩放比例、平移量、触摸中点(转换为图片内部坐标) touchState.start.scale = ts.scale; touchState.start.translateX = ts.translate.x; touchState.start.translateY = ts.translate.y; touchState.start.midX = (midPoint.x - ts.translate.x) / ts.scale; touchState.start.midY = (midPoint.y - ts.translate.y) / ts.scale; touchState.start.distance = getTouchDistance(event); } else if (event.touches.length === 1) { // 开始单点平移 panning = true; const rect = container.getBoundingClientRect(); mouseX = event.touches[0].pageX; mouseY = event.touches[0].pageY; mouseTX = ts.translate.x; mouseTY = ts.translate.y; } }); stage.addEventListener('touchmove', (event) => { event.preventDefault(); if (event.touches.length === 2 && touchState.isPinching) { // 捏合缩放 const currentDistance = getTouchDistance(event); const midPoint = getTouchMidpoint(event); // 计算相对缩放倍数 const scaleFactor = currentDistance / touchState.start.distance; // 更新总缩放比例,限制范围1-4 ts.scale = Math.min(Math.max(touchState.start.scale * scaleFactor, 1), 4); // 调整平移量,保证触摸中点位置不变 ts.translate.x = midPoint.x - touchState.start.midX * ts.scale; ts.translate.y = midPoint.y - touchState.start.midY * ts.scale; setTransform(); } else if (event.touches.length === 1 && panning) { // 单点平移 const x = event.touches[0].pageX; const y = event.touches[0].pageY; ts.translate.x = mouseTX + (x - mouseX); ts.translate.y = mouseTY + (y - mouseY); setTransform(); } }); stage.addEventListener('touchend', (event) => { touchState.isPinching = false; panning = false; }); }
CSS
.container { position: relative; width: 600px; border: 1px solid; overflow: hidden; } .stage { transform-origin: 50% 50%; cursor: grab; position: relative; width: 100%; } #image { width: 100%; height: auto; display: block; } /* 触摸设备下隐藏鼠标样式 */ @media (hover: none) and (pointer: coarse) { .stage { cursor: default; } }
HTML
<div class="container"> <div class="stage"> <img id="image" src="https://cdn.pixabay.com/photo/2018/01/14/23/12/nature-3082832__480.jpg" /> </div> </div>
关键修改说明
- 捏合初始状态记录:在
touchstart时,不仅记录两指距离,还保存当前的缩放比例、平移量,以及触摸中点转换为图片内部的坐标(去除平移和缩放影响后的坐标) - 相对缩放计算:捏合时用当前两指距离与初始距离的比值作为相对缩放倍数,乘以初始缩放比例得到新的缩放值,而非直接覆盖
- 平移量修正:根据新的缩放比例重新计算平移量,确保触摸中点在缩放过程中始终对应图片上的同一位置,彻底避免跳变
- 统一单点触摸逻辑:将单点触摸平移和鼠标平移逻辑对齐,保证跨设备体验一致
内容的提问来源于stack exchange,提问作者Toniq
相关产品推荐
相关产品推荐

