// ==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)
}
}
}
})();