移动端图片预览功能(单图,长图)可拖拽,可手势缩放

 参考于(参考方案支持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>

预览效果:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
移动端图片添加手指缩放功能后,可以通过以下方式来实现滑动图片查看全部内容: 1. 在HTML中,使用一个容器包含图片,并设置该容器的宽度和高度,用于显示图片: ```html <div id="container" style="width: 100%; height: 100%;"> <img id="image" src="path/to/image.jpg" style="max-width: 100%; max-height: 100%;"> </div> ``` 2. 在CSS中,设置图片的最大宽度和最大高度为100%以适应容器的大小: ```css #image { max-width: 100%; max-height: 100%; } ``` 3. 在JS中,使用hammer.js插件为容器添加手指缩放功能,并监听容器的滑动事件,实现查看全部图片内容的效果: ```javascript // 初始化hammer对象 var hammer = new Hammer(document.getElementById("container")); // 缩放事件 hammer.get("pinch").set({ enable: true }); hammer.on("pinch", function (e) { // 缩放代码 }); // 拖拽事件 hammer.get("pan").set({ enable: true, direction: Hammer.DIRECTION_ALL }); hammer.on("pan", function (e) { // 拖拽代码 }); // 滑动事件 var startX = 0; var startY = 0; hammer.on("panstart", function (e) { startX = e.center.x; startY = e.center.y; }); hammer.on("panmove", function (e) { var deltaX = e.center.x - startX; var deltaY = e.center.y - startY; // 移动代码 }); // 缩放结束事件 hammer.on("pinchend", function (e) { // 缩放结束后,自动调整图片位置和大小,使其填满容器 }); ``` 在滑动事件中,记录手指的起始位置,然后计算手指移动的距离,根据移动距离来移动图片的位置,实现滑动效果。在缩放结束事件中,可以根据图片的实际大小和容器的大小,自动调整图片的位置和大小,使其填满容器。 综上所述,以上就是给图片添加手指缩放功能后,实现滑动查看全部图片内容的代码实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值