js 实现双指缩放 您所在的位置:网站首页 如何缩放页面比例 js 实现双指缩放

js 实现双指缩放

2023-06-08 21:17| 来源: 网络整理| 查看: 265

前言

随着智能手机、平板电脑等触控设备的普及,交互方式也发生了改变。相对于使用鼠标和键盘进行交互的电脑,触控设备可以直接使用手指进行交互,而且基本上都支持多点触控。多点触控最常见的操作莫过于双指缩放了。比如双指缩放网页大小、朋友圈双指缩放图片进行查看。那么如此常见的手势操作,你有没有想过它是如何实现的呢?下面跟着我一探究竟吧!

缩放原理

原理其实很简单,双指向外扩张表示放大,向内收缩表示缩小,缩放比例是通过计算双指当前的距离 / 双指上一次的距离获得的。详见下图:

p.jpg

计算出缩放比例后再通过下面两种方式实现缩放。

通过transform进行缩放 通过修改宽高来实现缩放 主流的方法都是采用transform来实现,因为性能更好。本篇文章两种方式都会介绍,任你选择。不过在讲之前,还是要先搞懂两个数学公式以及PointerEvent指针事件。因为接下来会用到。如果对PointerEvent指针事件不太熟悉的小伙伴,也可以看看这篇文章js PointerEvent指针事件简单介绍。 两点间距离公式

设两个点A、B以及坐标分别为A(x1, y1)、B(x2, y2),则A和B两点之间的距离为:

∣AB∣=√()|AB| = √()∣AB∣=√()

e693d73856f43706273b0197b3cc42bf.svg

/** * 获取两点间距离 * @param {object} a 第一个点坐标 * @param {object} b 第二个点坐标 * @returns */ function getDistance(a, b) { const x = a.x - b.x; const y = a.y - b.y; return Math.hypot(x, y); // Math.sqrt(x * x + y * y); } 中点坐标公式

设两个点A、B以及坐标分别为A(x1, y1)、B(x2, y2),则A和B两点的中点P的坐标为:

4a36acaf2edda3cce013415d11e93901203f92dc.png

/** * 获取中点坐标 * @param {object} a 第一个点坐标 * @param {object} b 第二个点坐标 * @returns */ function getCenter(a, b) { const x = (a.x + b.x) / 2; const y = (a.y + b.y) / 2; return { x: x, y: y }; } 获取图片缩放尺寸 span""/span const image = document.getElementById('image'); let result, // 图片缩放宽高 x, // x轴偏移量 y, // y轴偏移量 scale = 1, // 缩放比例 maxScale, minScale = 0.5; // 由于图片是异步加载,需要在load方法里获取naturalWidth,naturalHeight image.addEventListener('load', function () { result = getImgSize(image.naturalWidth, image.naturalHeight, window.innerWidth, window.innerHeight); maxScale = Math.max(Math.round(image.naturalWidth / result.width), 3); // 图片宽高 image.style.width = result.width + 'px'; image.style.height = result.height + 'px'; // 垂直水平居中显示 x = (window.innerWidth - result.width) * 0.5; y = (window.innerHeight - result.height) * 0.5; image.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0) scale(1)'; }); // 图片赋值需放在load回调之后,因为图片缓存后读取很快,有可能不执行load回调 image.src='../images/xxx.jpg'; /** * 获取图片缩放尺寸 * @param {number} naturalWidth * @param {number} naturalHeight * @param {number} maxWidth * @param {number} maxHeight * @returns */ function getImgSize(naturalWidth, naturalHeight, maxWidth, maxHeight) { const imgRatio = naturalWidth / naturalHeight; const maxRatio = maxWidth / maxHeight; let width, height; // 如果图片实际宽高比例 >= 显示宽高比例 if (imgRatio >= maxRatio) { if (naturalWidth > maxWidth) { width = maxWidth; height = maxWidth / naturalWidth * naturalHeight; } else { width = naturalWidth; height = naturalHeight; } } else { if (naturalHeight > maxHeight) { width = maxHeight / naturalHeight * naturalWidth; height = maxHeight; } else { width = naturalWidth; height = naturalHeight; } } return { width: width, height: height } } 双指缩放逻辑 // 全局变量 let isPointerdown = false, // 按下标识 pointers = [], // 触摸点数组 point1 = { x: 0, y: 0 }, // 第一个点坐标 point2 = { x: 0, y: 0 }, // 第二个点坐标 diff = { x: 0, y: 0 }, // 相对于上一次pointermove移动差值 lastPointermove = { x: 0, y: 0 }, // 用于计算diff lastPoint1 = { x: 0, y: 0 }, // 上一次第一个触摸点坐标 lastPoint2 = { x: 0, y: 0 }, // 上一次第二个触摸点坐标 lastCenter; // 上一次中心点坐标 // 绑定 pointerdown image.addEventListener('pointerdown', function (e) { pointers.push(e); point1 = { x: pointers[0].clientX, y: pointers[0].clientY }; if (pointers.length === 1) { isPointerdown = true; image.setPointerCapture(e.pointerId); lastPointermove = { x: pointers[0].clientX, y: pointers[0].clientY }; } else if (pointers.length === 2) { point2 = { x: pointers[1].clientX, y: pointers[1].clientY }; lastPoint2 = { x: pointers[1].clientX, y: pointers[1].clientY }; lastCenter = getCenter(point1, point2); } lastPoint1 = { x: pointers[0].clientX, y: pointers[0].clientY }; }); // 绑定 pointermove image.addEventListener('pointermove', function (e) { if (isPointerdown) { handlePointers(e, 'update'); const current1 = { x: pointers[0].clientX, y: pointers[0].clientY }; if (pointers.length === 1) { // 单指拖动查看图片 diff.x = current1.x - lastPointermove.x; diff.y = current1.y - lastPointermove.y; lastPointermove = { x: current1.x, y: current1.y }; x += diff.x; y += diff.y; image.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0) scale(' + scale + ')'; } else if (pointers.length === 2) { const current2 = { x: pointers[1].clientX, y: pointers[1].clientY }; // 计算相对于上一次移动距离比例 ratio > 1放大,ratio < 1缩小 let ratio = getDistance(current1, current2) / getDistance(lastPoint1, lastPoint2); // 缩放比例 const _scale = scale * ratio; if (_scale > maxScale) { ratio = maxScale / scale; scale = maxScale; } else if (_scale < minScale) { ratio = minScale / scale; scale = minScale; } else { scale = _scale; } // 计算当前双指中心点坐标 const center = getCenter(current1, current2); // 计算图片中心偏移量,默认transform-origin: 50% 50% // 如果transform-origin: 30% 40%,那origin.x = (ratio - 1) * result.width * 0.3 // origin.y = (ratio - 1) * result.height * 0.4 // 如果通过修改宽高或使用transform缩放,但将transform-origin设置为左上角时。 // 可以不用计算origin,因为(ratio - 1) * result.width * 0 = 0 const origin = { x: (ratio - 1) * result.width * 0.5, y: (ratio - 1) * result.height * 0.5 }; // 计算偏移量,认真思考一下为什么要这样计算(带入特定的值计算一下) x -= (ratio - 1) * (center.x - x) - origin.x - (center.x - lastCenter.x); y -= (ratio - 1) * (center.y - y) - origin.y - (center.y - lastCenter.y); image.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0) scale(' + scale + ')'; lastCenter = { x: center.x, y: center.y }; lastPoint1 = { x: current1.x, y: current1.y }; lastPoint2 = { x: current2.x, y: current2.y }; } } e.preventDefault(); }); // 绑定 pointerup image.addEventListener('pointerup', function (e) { if (isPointerdown) { handlePointers(e, 'delete'); if (pointers.length === 0) { isPointerdown = false; } else if (pointers.length === 1) { point1 = { x: pointers[0].clientX, y: pointers[0].clientY }; lastPointermove = { x: pointers[0].clientX, y: pointers[0].clientY }; } } }); // 绑定 pointercancel image.addEventListener('pointercancel', function (e) { if (isPointerdown) { isPointerdown = false; pointers.length = 0; } }); /** * 更新或删除指针 * @param {PointerEvent} e * @param {string} type */ function handlePointers(e, type) { for (let i = 0; i < pointers.length; i++) { if (pointers[i].pointerId === e.pointerId) { if (type === 'update') { pointers[i] = e; } else if (type === 'delete') { pointers.splice(i, 1); } } } } 注意事项

由于transform书写顺序并不满足交换律,换句话说transform: translateX(300px) scale(2);和transform: scale(2) translateX(300px);是不相等的。开发时请根据相应的书写顺序做处理。详见下图:

微信图片_20210802192116.png

效果演示

Demo:jsdemo.codeman.top/html/pinch.…

二维码图片_8月3日14时14分23秒.png

写在最后

我已将常用的手势例如单击,双击,长按,滑动,拖拽,滚轮缩放,双指缩放,双指旋转等封装成了插件。项目已在Github开源,感兴趣的小伙伴可以去看看。项目地址:github.com/18223781723…



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有