您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
图片预览:通过双击按键,把图片固定在页面上,边读文字边看图,同时支持缩放、移动功能
当前为
// ==UserScript== // @name 边读边看图 // @namespace http://tampermonkey.net/ // @version 0.2.8 // @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 () { let crxName = 'img_preview_style' let modifyDate = new Date().toLocaleString() GM_addElement('style',{ textContent: `.pages_skin_pc .rich_media_area_primary_inner{margin-left:initial;}`, id: crxName }) run() return function run() { class ImgPreviwer { constructor(options = {}) { this.state = this.mergeOptions(options) this.shadowRoot = this.createShadowRoot() this.onPreviwerEvent() return this.shadowRoot } /** @描述 状态 */ state = null shadowRoot = null /** @描述 创建 shadowRoot */ createShadowRoot(selector = '#imgPreview') { let dom = document.querySelector(`${selector}`) if (!dom) { dom = document.createElement('div') dom.setAttribute('id',selector.replace(/[.#]/g,'')) dom.setAttribute('style','width:0;height:0') document.documentElement.appendChild(dom) } if (!dom.shadowRoot) { // 创建蒙层容器 const maskContent = document.createElement('div') maskContent.classList.add('modal') maskContent.appendChild(this.createStyle(this.state)) // 添加在body下 dom.attachShadow({ mode: 'open' }) dom.shadowRoot.appendChild(maskContent) } return dom.shadowRoot } /** @描述 合并选项 */ mergeOptions(options) { let opt = {} let defaultOptions = { contentSelector: 'body', selector: 'img', showRootSelector: '#img_preview', backgroundColor: "rgba(0,0,0,0)", extraStyle:'' } Object.assign(opt,defaultOptions,options) return opt } /** @描述 创建shadowbox中的样式 */ createStyle({contentSelector,selector,backgroundColor,extraStyle}) { const style = document.createElement('style'); style.innerHTML = `${contentSelector} ${selector} { cursor: zoom-in; } /* 图片预览 */ .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 100px 0px; } 100% { box-shadow: #09818f 0px 0px 15px 0px; } } ${extraStyle} ` return style } /** @描述 预览操作 */ onPreviwerEvent() { let that = this let { contentSelector,selector } = that.state let eventsProxy = document.querySelector(contentSelector) || window.document.body eventsProxy.addEventListener('dblclick',function (e) { 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 = [...that.shadowRoot.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.style.opacity = 0 new ImgPreviewer(that.shadowRoot,e.target,src) } } }) } } class ImgPreviewer { constructor(shadowRoot,originalEl,src) { this.state = Object.assign({},this.state,this.mergeOptions(shadowRoot,originalEl,src)) let cloneEl = this.appendImg(src) this.state.cloneEl = cloneEl this.fixPosition(cloneEl) this.addEvents(cloneEl) return cloneEl } state = { scale: 1, offset: { left: 0,top: 0 }, origin: 'center', initialData: { offset: {}, origin: 'center', scale: 1 }, startPoint: { x: 0,y: 0 }, // 记录初始触摸点位 isTouching: false, // 标记是否正在移动 isMove: false, // 正在移动中,与点击做区别 touches: new Map(), // 触摸点数组 lastDistance: 0, lastScale: 1, // 记录下最后的缩放值 scaleOrigin: { x: 0,y: 0,}, } mergeOptions(shadowRoot,originalEl,src) { const { innerWidth: winWidth,innerHeight: winHeight } = window const { offsetWidth,offsetHeight } = originalEl // Element.getBoundingClientRect() 方法返回元素的大小及其相对于【视口】的位置 const { top,left } = originalEl.getBoundingClientRect() return ({ shadowRoot, originalEl, src, winWidth, winHeight, offsetWidth, offsetHeight, top, left, maskContent: shadowRoot.querySelector('.modal') }) } /** @描述 添加图片 */ appendImg(src) { let cloneEl = document.createElement('img') cloneEl.src = src this.state.maskContent.appendChild(cloneEl) return cloneEl } /** @描述 添加监听事件 */ addEvents(cloneEl,events = ['dblclick','mousewheel','pointerdown','pointerup','pointermove','pointercancel']) { let that = this events.forEach(item => { if (item === 'mousewheel') { cloneEl.addEventListener('mousewheel',that[`on${item}`],{ passive: false }) return } cloneEl.addEventListener(item,that[`on${item}`]) }) } /** @描述 双击事件 */ ondblclick = (e) => { e.preventDefault() let that = this let state = that.state setTimeout(() => { if (state.isMove) { state.isMove = false } else { that.changeStyle(state.cloneEl,['transition: all .3s',`left: ${state.left}px`,`top: ${state.top}px`,`transform: translate(0,0)`,`width: ${state.offsetWidth}px`]) setTimeout(() => { state.maskContent.removeChild(state.cloneEl) // originalEl.style.opacity = 1 state.cloneEl.removeEventListener('dblclick',that.ondblclick) },300) } },280) } /** @描述 指针按下事件*/ onpointerdown = (e) => { e.preventDefault() let that = this let state = that.state 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 = that.getDistance() state.lastScale = state.scale } } /** @描述 滚轮缩放 */ onmousewheel = (e) => { e.preventDefault() if (!e.deltaY) return; let that = this let state = that.state state.origin = `${e.offsetX}px ${e.offsetY}px` // 缩放执行 if (e.deltaY < 0) { // 放大 state.scale += 0.1 } else if (e.deltaY > 0) { state.scale >= 0.2 && (state.scale -= 0.1) // 缩小 } if (state.scale < state.initialData.scale) { console.log(`state.scale < state.initialData.scale => %O `,state.scale,state.initialData.scale); that.reduction() } state.offset = that.getOffsetPageCenter(e.offsetX,e.offsetY) that.changeStyle(state.cloneEl,['transition: all .15s',`transform-origin: ${state.origin}`,`transform: translate(${state.offset.left + 'px'}, ${state.offset.top + 'px'}) scale(${state.scale})`]) } /** @描述 获取中心改变的偏差 */ getOffsetPageCenter(x = 0,y = 0) { let state = this.state 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 } } /** @描述 获取距离*/ getDistance() { const touchArr = Array.from(this.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) } /** @描述 修改样式,减少回流重绘*/ changeStyle(el,arr) { const original = el.style.cssText.split(';') original.pop() el.style.cssText = original.concat(arr).join(';') + ';' } /** @描述 还原记录,用于边界处理 */ reduction() { let that = this let state = that.state that.timer && clearTimeout(that.timer) that.timer = setTimeout(() => { // offset = state.initialData.offset // origin = state.initialData.origin // scale = state.initialData.scale console.log(`state => %O `,state); that.changeStyle(state.cloneEl,[`transform: translate(${state.offset.left + 'px'}, ${state.offset.top + 'px'}) scale(${state.scale})`,`transform-origin: ${state.origin}`]) },300) } /** @描述 松开指针 事件 */ onpointerup = (e) => { e.preventDefault() let that = this let state = that.state 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); } /** @描述 指针移动事件 */ onpointermove = (e) => { e.preventDefault() let that = this let state = that.state if (state.isTouching) { state.isMove = true if (state.touches.size < 2) { // 单指滑动 state.offset = { left: state.offset.left + (e.clientX - state.startPoint.x), top: state.offset.top + (e.clientY - state.startPoint.y) } that.changeStyle(state.cloneEl,['transition: all 0s',`transform: translate(${state.offset.left + 'px'}, ${state.offset.top + 'px'}) scale(${state.scale})`,`transform-origin: ${origin}`]) // 更新点位 state.startPoint = { x: e.clientX, y: e.clientY } } else { // 双指缩放 state.touches.set(e.pointerId,e) const ratio = that.getDistance() / state.lastDistance state.scale = ratio * state.lastScale state.offset = that.getOffsetPageCenter() if (state.scale < state.initialData.scale) { that.reduction() } that.changeStyle(state.cloneEl,['transition: all 0s',`transform: translate(${state.offset.left + 'px'}, ${state.offset.top + 'px'}) scale(${state.scale})`,`transform-origin: ${state.origin}`]) } } } /** @描述 取消指针事件 */ onpointercancel = (e) => { e.preventDefault() this.state.touches.clear() // 可能存在特定事件导致中断,真机操作时 pointerup 在某些边界情况下不会生效,所以需要清空 } /** @描述 移动图片到屏幕中心位置 */ fixPosition(cloneEl) { let that = this let state = that.state /** @描述 原图片 中心点 */ const originalCenterPoint = { x: state.offsetWidth / 2 + state.left, y: state.offsetHeight / 2 + state.top } /** @描述 页面 中心点 */ const winCenterPoint = { x: state.winWidth / 2, y: state.winHeight / 2 } /** @描述 新建图片的定位点:通过原图片中心点到页面中心点的 偏移量*/ const offsetDistance = { left: winCenterPoint.x - originalCenterPoint.x + state.left, top: winCenterPoint.y - originalCenterPoint.y + state.top } /** @描述 放大后的 */ let scaleNum = this.adaptScale() const diffs = { left: ((scaleNum - 1) * state.offsetWidth) / 2, top: ((scaleNum - 1) * state.offsetHeight) / 2 } console.log(`state => %O `,state ); this.changeStyle(cloneEl,[`left: ${state.left}px`,`top: ${state.top}px`,'transition: all 0.3s',`width: ${state.offsetWidth * scaleNum + 'px'}`,`transform: translate(${offsetDistance.left - state.left - diffs.left}px, ${offsetDistance.top - state.top - diffs.top}px)`]) /** @描述 消除偏差:让图片相对于window 0 0定位,通过translate设置中心点重合*/ // setTimeout(() => { // that.changeStyle(cloneEl,['transition: all 0s',`left: 0`,`top: 0`,`transform: translate(${offsetDistance.left - diffs.left}px, ${offsetDistance.top - diffs.top}px)`]) // that.state.offset = { // left: offsetDistance.left - diffs.left, // top: offsetDistance.top - diffs.top // } // // 记录值 // that.record() // },300) } /** @描述 记录初始化数据 */ record() { let state = this.state state.initialData = Object.assign({},{ offset: state.offset, origin: state.origin, scale: state.scale }) } /** @描述 计算自适应屏幕的缩放 */ adaptScale() { let { winWidth,winHeight,originalEl } = this.state const { offsetWidth: w,offsetHeight: h } = originalEl let scale = winWidth / w if (h * scale > winHeight - 80) { scale = (winHeight - 80) / h } return scale } } let shadowRoot = new ImgPreviwer({ backgroundColor: "rgba(0,0,0,0)" }) } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址