img_preview

微信公证号文章中,图片预览

当前为 2023-07-27 提交的版本,查看 最新版本

// ==UserScript==
// @name         img_preview
// @namespace    http://tampermonkey.net/
// @version      0.0.15
// @description  微信公证号文章中,图片预览
// @author       Enjoy
// @icon         https://foruda.gitee.com/avatar/1671100286067517749/4867929_enjoy_li_1671100285.png!avatar60
// @match        *://mp.weixin.qq.com/s/*
// @grant        GM_addElement
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @license      GPL License
// ==/UserScript==

// 函数文档 https://www.tampermonkey.net/documentation.php#api:GM_addElement

(function () {
  GM_addElement('script',{
    textContent: `strFn();${strFn.toString()}`
  })
  return
  function strFn() {
  setTimeout(() => {
    onImgPreview({
      selector: 'img.wxw-img',
      backgroundColor: "rgba(0,0,0,0)"
    })
    console.log('执行 img-preview')
  },5 * 1000)

  // onImgPreview({
  //   backgroundColor: "rgba(0,0,0,0)"
  // })

  function onImgPreview(options = {}) {
    let defaultOptions = {
      eventsProxySelector: '.container',
      selector: 'img',
      showRootSelector: '#img_preview',
      backgroundColor: "rgba(0,0,0,0.75)",
    }
    let { eventsProxySelector,
      selector = 'img',
      showRootSelector,
      backgroundColor } = Object.assign({},defaultOptions,options)

    let scale = 1
    let offset = { left: 0,top: 0 }
    let origin = 'center'
    let initialData = {
      offset: {},
      origin: 'center',
      scale: 1
    }
    let startPoint = { x: 0,y: 0 }
    // 记录初始触摸点位
    let isTouching = false
    // 标记是否正在移动
    let isMove = false
    // 正在移动中,与点击做区别
    let touches = new Map()
    // 触摸点数组
    let lastDistance = 0
    let lastScale = 1
    // 记录下最后的缩放值
    let scaleOrigin = { x: 0,y: 0,}

    const { innerWidth: winWidth,innerHeight: winHeight } = window
    // let cloneEl = null
    let cloneEl = document.createElement('img')
    let originalEl = null

    /** @描述 事件委托对象 */
    //  (eventsProxySelector && document.querySelector(eventsProxySelector)) ||
    let eventsProxy = window.document.body

    let shadow = createShadowRoot(showRootSelector)

    eventsProxy.addEventListener('dblclick',function (e) {
      console.log('e.target',e.target)
      let src = e.target.src || window.getComputedStyle(e.target).backgroundImage.match(/^url\("([^\s]+)"\)$/i)?.[1]
      if (!src) return;
      e.preventDefault()
      let findOneInPage = [...eventsProxy.querySelectorAll(selector)].find(item => item === e.target)

      if (findOneInPage) {

        let findOneInModal = [...shadow.querySelectorAll(selector)].find(item => item.src === src)
        if (findOneInModal) {
          if (!findOneInModal.classList.contains('active')) {
            findOneInModal.classList.add('active')
            return;
          } else {
            findOneInModal.remove()
            findOneInModal = null
          }
        }

        if (!findOneInModal) {
          originalEl = e.target
          originalEl.src = src

          cloneEl = document.createElement('img')
          cloneEl.src = originalEl.src
          // originalEl.style.opacity = 0
          openPreview(shadow,cloneEl)
        }

      } else {

      }
    })

    /** @描述 创建shadowbox */
    function createShadowRoot(showRootSelector) {
      let dom = document.querySelector(`${showRootSelector}`)
      let shadowRoot = dom?.shadowRoot
      if (!dom) {
        dom = document.createElement('div')
        dom.setAttribute('id',showRootSelector.replace(/[.#]/g,''))
        dom.setAttribute('style','width:0;height:0')
        document.documentElement.appendChild(dom)
      }
      if (!dom.shadowRoot) {
        shadowRoot = dom.attachShadow({ mode: 'open' })
        // 创建蒙层容器
        const mask = document.createElement('div')
        mask.classList.add('modal')
        mask.appendChild(createStyle())
        // 添加在body下
        shadowRoot.appendChild(mask)
      }
      return shadowRoot
    }

    /** @描述 创建shadowbox中的样式 */
    function createStyle() {
      const style = document.createElement('style')
      style.innerHTML = `/* 图片预览 */
                        .modal {
                        touch-action: none;
                        position: fixed;
                        z-index: 99;
                        top: 0;
                        left: 0;
                        width: 100vw;
                        height: 100vh;
                        background-color: ${backgroundColor};
                        user-select: none;
                        pointer-events: none;
                        }
                        .modal>*{
                          pointer-events: auto;
                        }
                        .modal>img {
                        position: absolute;
                        padding: 0;
                        margin: 0;
                        box-shadow: #09818f 0px 0px 15px 0px;
                        border-radius: 10px;
                        /* transition: all var(--delay_time); */
                        transform: translateZ(0);
                        }

                        img.active {
                          animation: activeImg 0.5s 4 ease-out forwards;
                          transition: all;
                        }

                        @keyframes activeImg {
                          0% {
                            box-shadow: #09818f 0px 0px 15px 0px;
                          }
                          50% {
                            box-shadow: red 0px 0px 20px 0px;
                          }
                          100% {
                            box-shadow: #09818f 0px 0px 15px 0px;
                          }
                        }
                        `
      return style
    }

    function openPreview(shadow,cloneEl) {
      scale = 1
      const { offsetWidth,offsetHeight } = originalEl
      const { top,left } = originalEl.getBoundingClientRect()
      console.log(`{ top,left } => %O `,{ top,left });


      // 注册(不可用)事件

      cloneEl.addEventListener("dblclick",dblclickFunc)
      cloneEl.addEventListener('mousewheel',zoom,{ passive: false })
      cloneEl.addEventListener('pointerdown',onPointerdown)
      cloneEl.addEventListener('pointerup',onPointerup)
      cloneEl.addEventListener('pointermove',onPointermove)
      cloneEl.addEventListener('pointercancel',onPointercancel)


      // 遮罩点击事件
      function dblclickFunc() {
        setTimeout(() => {
          if (isMove) {
            isMove = false
          } else {
            changeStyle(cloneEl,['transition: all .3s',`left: ${left}px`,`top: ${top}px`,`transform: translate(0,0)`,`width: ${offsetWidth}px`])
            setTimeout(() => {
              mask.removeChild(this)
              originalEl.style.opacity = 1
              cloneEl.removeEventListener('dblclick',dblclickFunc)
            },300)
          }
        },280)
      }

      // function clickFunc() {
      //   offset = initialData.offset
      //   origin = initialData.origin
      //   scale = initialData.scale
      //   changeStyle(cloneEl,[`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`,`transform-origin: ${origin}`])
      // }


      // 添加图片

      let mask = shadow.querySelector('.modal')
      mask.appendChild(cloneEl)

      // 移动图片到屏幕中心位置

      /** @描述 原图片 中心点 */
      const originalCenterPoint = {
        x: offsetWidth / 2 + left,
        y: offsetHeight / 2 + top
      }

      /** @描述 页面 中心点 */
      const winCenterPoint = {
        x: winWidth / 2,
        y: winHeight / 2
      }

      console.log(`originalCenterPoint,winCenterPoint => %O `,originalCenterPoint,winCenterPoint);

      /** @描述  原图片中心点到页面中心点的 偏移量*/
      const offsetDistance = {
        left: winCenterPoint.x - originalCenterPoint.x + left,
        top: winCenterPoint.y - originalCenterPoint.y + top
      }

      /** @描述 放大后的 */
      const diffs = {
        left: ((adaptScale() - 1) * offsetWidth) / 2,
        top: ((adaptScale() - 1) * offsetHeight) / 2
      }
      console.log(`offsetDistance2 => %O `,JSON.parse(JSON.stringify(offsetDistance)),JSON.parse(JSON.stringify(diffs)));
      changeStyle(cloneEl,[`left: ${left}px`,`top: ${top}px`])
      changeStyle(cloneEl,['transition: all 0.3s',`width: ${offsetWidth * adaptScale() + 'px'}`,`transform: translate(${offsetDistance.left - left - diffs.left}px, ${offsetDistance.top - top - diffs.top}px)`])

      /** @描述 消除偏差:让图片相对于window  0 0定位,通过translate设置中心点重合*/
      setTimeout(() => {
        changeStyle(cloneEl,['transition: all 0s',`left: 0`,`top: 0`,`transform: translate(${offsetDistance.left - diffs.left}px, ${offsetDistance.top - diffs.top}px)`])
        console.log(`offsetDistance3 => %O `,offsetDistance,diffs);
        offset = {
          left: offsetDistance.left - diffs.left,
          top: offsetDistance.top - diffs.top
        }
        // 记录值
        record()
      },300)
    }

    // 滚轮缩放
    const zoom = (event) => {
      if (!event.deltaY) {
        return
      }
      event.preventDefault()
      origin = `${event.offsetX}px ${event.offsetY}px`
      // 缩放执行
      if (event.deltaY < 0) {
        scale += 0.1
        // 放大
      } else if (event.deltaY > 0) {
        scale >= 0.2 && (scale -= 0.1)
        // 缩小
      }
      if (scale < initialData.scale) {
        reduction()
      }
      offset = getOffsetCorrection(event.offsetX,event.offsetY)
      changeStyle(cloneEl,['transition: all .15s',`transform-origin: ${origin}`,`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`])
    }

    // 获取中心改变的偏差
    function getOffsetCorrection(x = 0,y = 0) {
      const touchArr = Array.from(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
      }
      origin = `${x}px ${y}px`
      const offsetLeft = (scale - 1) * (x - scaleOrigin.x) + offset.left
      const offsetTop = (scale - 1) * (y - scaleOrigin.y) + offset.top
      scaleOrigin = {
        x,
        y
      }
      return {
        left: offsetLeft,
        top: offsetTop
      }
    }

    // 操作事件
    function onPointerdown(e) {
      e.preventDefault()
      touches.set(e.pointerId,e)
      // TODO: 点击存入触摸点
      isTouching = true
      startPoint = {
        x: e.clientX,
        y: e.clientY
      }
      if (touches.size === 2) {
        // TODO: 判断双指触摸,并立即记录初始数据
        lastDistance = getDistance()
        lastScale = scale
      }
    }

    // window.addEventListener('pointerdown',onPointerdown)
    // window.addEventListener('pointerup',onPointerup)
    // window.addEventListener('pointermove',onPointermove)
    // window.addEventListener('pointercancel',onPointercancel)

    function onPointerup(e) {
      touches.delete(e.pointerId)
      // TODO: 抬起移除触摸点
      if (touches.size <= 0) {
        isTouching = false
      } else {
        const touchArr = Array.from(touches)
        // 更新点位
        startPoint = {
          x: touchArr[0][1].clientX,
          y: touchArr[0][1].clientY
        }
      }
      setTimeout(() => {
        isMove = false
      },300);
    }
    function onPointermove(e) {
      e.preventDefault()
      if (isTouching) {
        isMove = true
        if (touches.size < 2) {
          // 单指滑动
          offset = {
            left: offset.left + (e.clientX - startPoint.x),
            top: offset.top + (e.clientY - startPoint.y),
          }
          changeStyle(cloneEl,['transition: all 0s',`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`,`transform-origin: ${origin}`])
          // 更新点位
          startPoint = {
            x: e.clientX,
            y: e.clientY
          }
        } else {
          // 双指缩放
          touches.set(e.pointerId,e)
          const ratio = getDistance() / lastDistance
          scale = ratio * lastScale
          offset = getOffsetCorrection()
          if (scale < initialData.scale) {
            reduction()
          }
          changeStyle(cloneEl,['transition: all 0s',`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`,`transform-origin: ${origin}`])
        }
      }
    }

    function onPointercancel(e) {
      touches.clear()
      // 可能存在特定事件导致中断,真机操作时 pointerup 在某些边界情况下不会生效,所以需要清空
    }


    // 修改样式,减少回流重绘
    function changeStyle(el,arr) {
      // console.log(`el.style.cssText => %O `,el.style.cssText,arr);
      const original = el.style.cssText.split(';')
      original.pop()
      // console.log(` original.concat(arr).join(';') + ';' => %O `, original.concat(arr).join(';') + ';');
      el.style.cssText = original.concat(arr).join(';') + ';'
    }

    // 计算自适应屏幕的缩放
    function adaptScale() {
      const { offsetWidth: w,offsetHeight: h } = originalEl
      let scale = winWidth / w
      if (h * scale > winHeight - 80) {
        scale = (winHeight - 80) / h
      }
      return scale
    }

    // 获取距离
    function getDistance() {
      const touchArr = Array.from(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)
    }

    // 记录初始化数据
    function record() {
      initialData = Object.assign({},{
        offset,
        origin,
        scale
      })
    }

    // 还原记录,用于边界处理
    let timer = null
    function reduction() {
      timer && clearTimeout(timer)
      timer = setTimeout(() => {
        // offset = initialData.offset
        // origin = initialData.origin
        // scale = initialData.scale
        changeStyle(cloneEl,[`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`,`transform-origin: ${origin}`])
      },300)
    }
  }
}

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址