边读边看图

图片预览:通过双击按键,把图片固定在页面上,边读文字边看图,同时支持缩放、移动功能

目前为 2023-08-10 提交的版本。查看 最新版本

// ==UserScript==
// @name         边读边看图
// @namespace    http://tampermonkey.net/
// @version      0.2.4
// @description  图片预览:通过双击按键,把图片固定在页面上,边读文字边看图,同时支持缩放、移动功能
// @author       Enjoy
// @icon         https://foruda.gitee.com/avatar/1671100286067517749/4867929_enjoy_li_1671100285.png!avatar60
// @match        *://*/*
// @exclude    *hrwork*
// @exclude    *zhaopinyun*
// @exclude    *localhost*
// @exclude    *127.0.0.1*
// @grant        GM_addElement
// @grant        GM_setClipboard
// @license      GPL License
// ==/UserScript==

// 函数文档 https://www.tampermonkey.net/documentation.php#api:GM_addElement
// @match        *://mp.weixin.qq.com/s/*

(function () {
  GM_addElement('style',{
    textContent: `.pages_skin_pc .rich_media_area_primary_inner{margin-left:initial;}`,
    id:"img_preview_style"
  })
  run()
  return

  function run() {
    // setTimeout(() => {
    onImgPreview({
      // selector: 'img.wxw-img',
      backgroundColor: "rgba(0,0,0,0)"
    })
    console.log('执行 img-preview')
    // },5 * 1000)
    document.addEventListener('click',openPageByUrlText)

    //

    return

    /** @描述 单击网址字符串打开对应页码 */
    function openPageByUrlText(e) {
      let dom = e.target
      if (dom.tagName.toLocaleLowerCase() === 'a') return;
      let innerHTML = dom.innerHTML
      let href = innerHTML.match(/^\s*(https?[^\s))]+)\s*$/)
      if (href) {
        window.open(href[1])
      }
    }

    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 20px;
                            }
                            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或关注我们的公众号极客氢云获取最新地址