参考于(参考方案支持pc端与移动端):原生 JS 手写一个优雅的图片预览功能,带你吃透背后原理 - 知乎 (zhihu.com)
可当作一个vue组件通过ref调取初始化监听事件addlistionAll函数调用()传递的数据为图片路径,如果要预览多张图片可自行修改。
以下为vue文件代码:
<template> <div id="list" class="modal" v-if="state.visable"> <img class="item" :src="state.imageUrls" /> </div> </template> <script lang="ts"> import { defineComponent, reactive } from "vue"; // import { require } from '@/utlis/cont' // import { useRouter } from 'vue-router' import { useMain } from "@/store/home"; import { storeToRefs } from "pinia"; export default defineComponent({ setup(_props) { const state = reactive({ visable: false, imageUrls: "" as any, showPreview: false, startPoint: { x: 0, y: 0 }, // 记录初始触摸点位 isTouching: false, // 标记是否正在移动 isMove: false, // 正在移动中,与点击做区别 offset: { left: 0, top: 0, } as any, //鼠标/手指移动 imgdome: "" as any, //当前拖动的dom元素 scale: 1, // 放大或缩小 lastScale: 1, // 最后的缩放值 origin: "center", //点击拖动的位置 touches: new Map(), // 记录触摸点数组 根据数组长度来判断是单击、双击 lastDistance: 0, initialData: { offset: { left: 0, top: 0 }, origin: "center", scale: 1 }, timer: "" as any, // 还原记录,用于边界处理 scaleOrigin: { x: 0, y: 0 } as any, }); onMounted(() => {}); // const router = useRouter() const store = useMain(); const states = storeToRefs(store); // 函数式初始化调用 const openPreview = (src: any) => { state.scale = 1; state.visable = true; state.imageUrls = src; state.imgdome = ""; const settime = setInterval(() => { if (state.imgdome) { clearInterval(settime); return; } addlistionAll(); }, 500); }; // 初始化监听事件 const addlistionAll = () => { state.imgdome = document.querySelector(".item"); if (!state.imgdome) return; console.log(state.imgdome); document .querySelector("#list")! .addEventListener("mousewheel", zoom, { passive: false }); //根据数组长度来判断是单击、双击 window.addEventListener("pointerdown", function (e) { e.preventDefault(); state.touches.set(e.pointerId, e); // TODO: 点击存入触摸点 state.isTouching = true; state.startPoint = { x: e.clientX, y: e.clientY }; if (state.touches.size === 2) { // TODO: 判断双指触摸,并立即记录初始数据 state.lastDistance = getDistance(); state.lastScale = state.scale; } }); // 鼠标/手指按下 window.addEventListener("pointerdown", function (e) { e.preventDefault(); state.touches.set(e.pointerId, e); // TODO: 点击存入触摸点 state.isTouching = true; state.startPoint = { x: e.clientX, y: e.clientY }; if (state.touches.size === 2) { // TODO: 判断双指触摸,并立即记录初始数据 state.lastDistance = getDistance(); state.lastScale = state.scale; } }); // 鼠标/手指抬起 window.addEventListener("pointerup", function (e) { state.touches.delete(e.pointerId); // TODO: 抬起移除触摸点 if (state.touches.size <= 0) { state.isTouching = false; } else { const touchArr = Array.from(state.touches); // 更新点位 state.startPoint = { x: touchArr[0][1].clientX, y: touchArr[0][1].clientY, }; } setTimeout(() => { state.isMove = false; }, 300); }); // 鼠标/手指移动 window.addEventListener("pointermove", (e) => { if (state.isTouching) { state.isMove = true; // console.log(state.touches.size,state.scale); if (state.touches.size < 2) { console.log( state.offset.left % 1 == 0 || state.offset.top % 1 == 0 ); // if (!(state.offset.left % 1 == 0 || state.offset.top % 1 == 0)) // return; // 单指滑动/鼠标移动 state.offset = { left: +state.offset.left + (e.clientX - state.startPoint.x), top: +state.offset.top + (e.clientY - state.startPoint.y), }; changeStyle(state.imgdome, [ "transition: all 0s", `transform: translate(${state.offset.left + "px"}, ${ state.offset.top + "px" }) scale(${state.scale}) `, `transform-origin: ${state.origin}`, ]); // 注意移动完也要更新初始点位,否则图片会加速逃逸可视区域 state.startPoint = { x: e.clientX, y: e.clientY }; } else { // 双指缩放 state.touches.set(e.pointerId, e); const ratio = getDistance() / state.lastDistance; state.scale = ratio * state.lastScale; state.offset = getOffsetCorrection(); if (state.scale < state.initialData.scale) { reduction(); } changeStyle(state.imgdome, [ "transition: all 0s", `transform: translate(${state.offset.left + "px"}, ${ state.offset.top + "px" }) scale(${state.scale})`, `transform-origin: ${state.origin}`, ]); } } }); window.addEventListener("pointercancel", function (_e) { state.touches.clear(); // 可能存在特定事件导致中断,所以需要清空 }); // 将当前url地址添加到浏览器的历史记录中 window.history.pushState(null, "", document.URL); //第三个参数不写默认为当前url地址 // 监听物理返回 window.addEventListener( "popstate", function () { state.visable = false; window.removeEventListener("popstate", function () {}, false); }, false ); }; // 用于修改样式的工具类,并且可以减少回流重绘,后面代码中会频繁用到 const changeStyle = (el: any, arr: any) => { const original = el.style.cssText.split(";"); original.pop(); el.style.cssText = original.concat(arr).join(";") + ";"; }; // 获取中心改变的偏差 const getOffsetCorrection = (x = 0, y = 0) => { const touchArr = Array.from(state.touches); if (touchArr.length === 2) { const start = touchArr[0][1]; const end = touchArr[1][1]; x = (start.offsetX + end.offsetX) / 2; y = (start.offsetY + end.offsetY) / 2; } state.origin = `${x}px ${y}px`; const offsetLeft = (state.scale - 1) * (x - state.scaleOrigin.x) + state.offset.left; const offsetTop = (state.scale - 1) * (y - state.scaleOrigin.y) + state.offset.top; state.scaleOrigin = { x, y }; return { left: offsetLeft, top: offsetTop }; }; // 获取两个手指之间的距离 const getDistance = () => { const touchArr = Array.from(state.touches); if (touchArr.length < 2) { return 0; } const start = touchArr[0][1]; const end = touchArr[1][1]; return Math.hypot(end.x - start.x, end.y - start.y); }; // // 记录初始化数据 // const record = () => { // state.initialData = Object.assign( // {}, // { offset: state.offset, origin: state.origin, scale: state.scale } // ); // }; // 缩放 const zoom = (event: any) => { console.log(state.origin); if (!event.deltaY) { return; } event.preventDefault(); state.origin = `${event.offsetX}px ${event.offsetY}px`; // 缩放执行 if (event.deltaY < 0) { state.scale += 0.1; // 放大 } else if (event.deltaY > 0) { state.scale >= 0.2 && (state.scale -= 0.1); // 缩小 } if (state.scale < state.initialData.scale) { reduction(); } state.offset = getOffsetCorrection(event.offsetX, event.offsetY); changeStyle(state.imgdome, [ "transition: all .15s", `transform-origin: ${state.origin}`, `transform: translate(${state.offset.left + "px"}, ${ state.offset.top + "px" }) scale(${state.scale})`, ]); }; // 还原记录,用于边界处理 const reduction = () => { state.timer && clearTimeout(state.timer); state.timer = setTimeout(() => { state.offset = state.initialData.offset; state.origin = state.initialData.origin; state.scale = state.initialData.scale; changeStyle(state.imgdome, [ `transform: translate(${state.offset.left + "px"}, ${ state.offset.top + "px" }) scale(${state.scale})`, `transform-origin: ${state.origin}`, ]); }, 100); }; return { state, states, openPreview, }; }, }); </script> <style lang="scss" scoped> /* 图片预览 */ .modal { touch-action: none; position: fixed; z-index: 99; top: 0; left: 0; width: 100vw; height: 100vh; background-color: #222; user-select: none; img { position: absolute; padding: 0; margin: 0; object-fit: contain; width: 100vw; height: 100vh; /* transition: all var(--delay_time); */ transform: translateZ(0); } } </style>
讯享网
预览效果:


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/68724.html