手机浏览器触摸手势

为手机浏览器添加触摸手势,即装即用,无需配置。除了通用手势外,还有针对文字、图片、视频的特殊手势。还嫌不够?支持添加属于你的个性化手势。推荐使用狐猴浏览器、Edge浏览器和Yandex浏览器。

  1. // ==UserScript==
  2. // @name 手机浏览器触摸手势
  3. // @name:en Mobile browser touch gestures
  4. // @description 为手机浏览器添加触摸手势,即装即用,无需配置。除了通用手势外,还有针对文字、图片、视频的特殊手势。还嫌不够?支持添加属于你的个性化手势。推荐使用狐猴浏览器、Edge浏览器和Yandex浏览器。
  5. // @description:en Add touch gestures to your mobile browser, ready to use and no configuration required. In addition to general gestures, there are also special gestures for text, images, and videos. Do you still think it's not enough? Support adding personalized gestures that belong to you. We recommend using Lemur browser, Edge browser and Yandex browser.
  6. // @version 10.0.0
  7. // @author L.Xavier
  8. // @namespace https://gf.qytechs.cn/zh-CN/users/128493
  9. // @match *://*/*
  10. // @license MIT
  11. // @grant window.close
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @grant GM_openInTab
  15. // @grant GM_setClipboard
  16. // @grant GM_addValueChangeListener
  17. // @grant GM_xmlhttpRequest
  18. // @run-at document-start
  19. // ==/UserScript==
  20. // v10.0.0 2025-04-02 - 1.新增选词翻译功能,选中文字1s后翻译,点击翻译框复制翻译。2.新增对使用画布播放视频的网页支持。3.修复当iframe视频自动播放时,未设置iframe框架的标识,以至于需要点击视频后才可在框架外双击全屏。
  21. /*手势数据模块*/
  22. const gestureData={};
  23. gestureData.gesture={
  24. '↑→↓←':{name:'打开设置',code:'/*ONLY TOP*/gestureData.openSet();'},
  25. '◆◆':{name:'视频全屏',code:'gestureData.videoFullScreen();'},
  26. '●':{name:'手势穿透',code:'if(/^[TIV]/.test(gestureData.path)){gestureData.path=(gestureData.path.indexOf("I")>-1) ? "I" : "";}if(gestureData.path!=="I" && gestureData.settings["图片手势"]){if(gestureData.touchEle.nodeName!=="IMG"){let imgs=[...document.querySelectorAll("[_imgShow_=\'1\']")];if(gestureData.shadowList){for(let Ti=0,len=gestureData.shadowList.length;Ti<len;++Ti){imgs.push(...gestureData.shadowList[Ti].querySelectorAll("[_imgShow_=\'1\']"));}}for(let Ti=0,len=imgs.length;Ti<len;++Ti){if(imgs[Ti].nodeName!=="IMG" && getComputedStyle(imgs[Ti]).backgroundImage==="none"){continue;}let imgRect=imgs[Ti].getBoundingClientRect();if(gestureData.touchStart.clientX>imgRect.x && gestureData.touchStart.clientX<(imgRect.x+imgRect.width) && gestureData.touchStart.clientY>imgRect.y && gestureData.touchStart.clientY<(imgRect.y+imgRect.height)){gestureData.touchEle=imgs[Ti];break;}}}if(gestureData.path || gestureData.selectWords || !(gestureData.touchEle.compareDocumentPosition(gestureData.videoPlayer) & Node.DOCUMENT_POSITION_FOLLOWING)){if(gestureData.touchEle.nodeName==="IMG"){gestureData.path="I";}else{let bgImg=getComputedStyle(gestureData.touchEle).backgroundImage;if(bgImg!=="none"){gestureData.touchEle.src=bgImg.split(\'"\')[1];gestureData.path="I";}}}}'},
  27. '→←':{name:'后退',code:'/*ONLY TOP*/function pageBack(){if(gestureData.backTimer){history.go(-1);setTimeout(pageBack,20);}}gestureData.backTimer=setTimeout(()=>{gestureData.backTimer=0;window.close();},200);pageBack();'},
  28. '←→':{name:'前进',code:'/*ONLY TOP*/history.go(1);'},
  29. '↓↑':{name:'回到顶部',code:'/*WITH TOP*/let boxNode=gestureData.touchEle.parentNode;while(boxNode.nodeName!=="#document"){boxNode.scrollIntoView(true);if(boxNode.scrollTop){boxNode.scrollTo(0,0);}boxNode=boxNode.parentNode;}'},
  30. '↑↓':{name:'回到底部',code:'/*WITH TOP*/let boxNode=gestureData.touchEle.parentNode;while(boxNode.nodeName!=="#document"){if(getComputedStyle(boxNode).overflowY!=="hidden"){boxNode.scrollTo(0,boxNode.scrollHeight+999999);}boxNode=boxNode.parentNode;}'},
  31. '←↓':{name:'刷新页面',code:'/*ONLY TOP*/document.documentElement.style.cssText="filter:grayscale(100%)";history.go(0);'},
  32. '←↑':{name:'新建页面',code:'/*ONLY TOP*/gestureData.GM_openInTab("//limestart.cn");'},
  33. '→↓':{name:'关闭页面',code:'/*ONLY TOP*/window.close();'},
  34. '→↑':{name:'恢复页面',code:'/*ONLY TOP*/gestureData.GM_openInTab("chrome-native://recent-tabs");'},
  35. '↓↑●':{name:'新页面打开',code:'let linkNode=gestureData.touchEle;while(true){if(linkNode.href){gestureData.GM_openInTab(linkNode.href);break;}linkNode=linkNode.parentNode;if(linkNode.nodeName==="BODY"){gestureData.touchEle.click();break;}}'},
  36. '↑↓●':{name:'隐藏元素',code:'let boxNode=gestureData.touchEle,area=boxNode.offsetWidth*boxNode.offsetHeight,area_p=boxNode.parentNode.offsetWidth*boxNode.parentNode.offsetHeight,area_s=screen.width*screen.height;while(boxNode.parentNode.nodeName!=="BODY" && area/area_p>0.2 && area_p/area_s<0.9){boxNode=boxNode.parentNode;area_p=boxNode.parentNode.offsetWidth*boxNode.parentNode.offsetHeight;}if(boxNode.nodeName!=="HTML"){boxNode.remove();}'},
  37. '↓→':{name:'复制页面',code:'/*ONLY TOP*/gestureData.GM_openInTab(location.href);'},
  38. '→←→':{name:'半屏模式',code:'/*ONLY TOP*/if(gestureData.halfScreen){setTimeout(()=>{gestureData.halfScreen.remove();halfClose.remove();gestureData.halfScreen=null;document.documentElement.scrollTop=gestureData.scrollTop;},500);gestureData.scrollTop=document.body.scrollTop;let halfClose=gestureData.addStyle("html{transform:translateY(0) !important;}");}else{gestureData.scrollTop=document.documentElement.scrollTop;gestureData.halfScreen=gestureData.addStyle("html,body{height:43vh !important;overflow-y:auto !important;}html{transform:translateY(50vh) !important;transition:0.5s !important;overflow:hidden !important;}");document.body.scrollTop=gestureData.scrollTop;}'},
  39. '→↓↑←':{name:'视频解析',code:'/*ONLY TOP*/gestureData.GM_openInTab(`https://jx.xmflv.com/?url=${location.href}`);'},
  40. '↑→↓':{name:'停止定时器',code:'/*WITH TOP*/let start=gestureData.maxID|0,script=document.createElement("script");gestureData.maxID=setTimeout(Date);script.textContent=`for(let Ti=${start};Ti<${gestureData.maxID+1000};++Ti){clearTimeout(Ti);clearInterval(Ti);}`;document.body.insertAdjacentElement("beforeend",script);alert("已停止网页当前存在的定时器");'},
  41. 'T→↑':{name:'百度翻译',code:'gestureData.GM_openInTab(`//fanyi.baidu.com/#auto/auto/${encodeURIComponent(gestureData.selectWords)}`);'},
  42. 'T←↑':{name:'有道翻译',code:'gestureData.GM_openInTab(`//dict.youdao.com/w/eng/${encodeURIComponent(gestureData.selectWords)}`);'},
  43. 'T◆◆':{name:'双击搜索',code:'gestureData.GM_setClipboard(gestureData.selectWords);if(!/^((https?:)?\\/\\/)?([\\w\\-]+\\.)+\\w{2,4}(:\\d{1,5})?(\\/\\S*)?$/.test(gestureData.selectWords.trim())){gestureData.selectWords=`//bing.com/search?q=${encodeURIComponent(gestureData.selectWords)}&FORM=CHROMN`;}else if(!/^(https?:)?\\/\\//.test(gestureData.selectWords.trim())){gestureData.selectWords=`//${gestureData.selectWords.trim()}`;}gestureData.GM_openInTab(gestureData.selectWords.trim());'},
  44. 'I↓↑●':{name:'打开图片',code:'gestureData.GM_openInTab(gestureData.touchEle.src);'},
  45. 'I→↑●':{name:'百度搜图',code:'gestureData.GM_openInTab(`//graph.baidu.com/details?isfromtusoupc=1&tn=pc&carousel=0&promotion_name=pc_image_shituindex&extUiData%5bisLogoShow%5d=1&image=${gestureData.touchEle.src}`);'},
  46. 'V→':{name:'前进10s',code:'gestureData.videoPlayer.currentTime+=10;gestureData.tipBox.textContent="+10s ";gestureData.tipBox.style.display="block";setTimeout(()=>{gestureData.tipBox.style.display="none";},500);'},
  47. 'V←':{name:'后退10s',code:'gestureData.videoPlayer.currentTime-=10;gestureData.tipBox.textContent="-10s ";gestureData.tipBox.style.display="block";setTimeout(()=>{gestureData.tipBox.style.display="none";},500);'},
  48. 'V↑':{name:'增加倍速',code:'if(document.fullscreen){let playSpeed=gestureData.videoPlayer.playbackRate;playSpeed+=(playSpeed<1.5) ? 0.25 : 0.5;gestureData.tipBox.textContent=`×${playSpeed} ∞ `;gestureData.tipBox.style.display="block";gestureData.videoPlayer.playbackRate=playSpeed;setTimeout(()=>{gestureData.tipBox.style.display="none";},500)}'},
  49. 'V↓':{name:'减小倍速',code:'if(document.fullscreen){let playSpeed=gestureData.videoPlayer.playbackRate;playSpeed-=(playSpeed>1.5) ? 0.5 : (playSpeed>0.25 && 0.25);gestureData.tipBox.textContent=`×${playSpeed} ∞ `;gestureData.tipBox.style.display="block";gestureData.videoPlayer.playbackRate=playSpeed;setTimeout(()=>{gestureData.tipBox.style.display="none";},500)}'},
  50. 'V→●':{name:'快进播放',code:'gestureData.playSpeed=gestureData.videoPlayer.playbackRate;gestureData.videoPlayer.playbackRate=10;gestureData.tipBox.textContent="×10 ";gestureData.tipBox.style.display="block";'},
  51. 'V→○':{name:'停止快进',code:'gestureData.videoPlayer.playbackRate=gestureData.playSpeed;gestureData.tipBox.style.display="none";'},
  52. 'V←●':{name:'快退播放',code:'gestureData.videoTimer=setInterval(()=>{--gestureData.videoPlayer.currentTime;},100);gestureData.tipBox.textContent="- ×10 ";gestureData.tipBox.style.display="block";'},
  53. 'V←○':{name:'停止快退',code:'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";'},
  54. 'V↑▼':{name:'增加音量',code:'if(document.fullscreen){gestureData.videoPlayer.muted=false;gestureData.tipBox.textContent=(gestureData.videoPlayer.volume*100|0)+"%";gestureData.tipBox.style.display="block";let lastY=gestureData.touchEnd.screenY;gestureData.videoTimer=setInterval(()=>{if(lastY-gestureData.touchEnd.screenY){let tempVolume=gestureData.videoPlayer.volume+(lastY-gestureData.touchEnd.screenY)/100;gestureData.videoPlayer.volume=+(tempVolume>1) || (+(tempVolume>0) && tempVolume);gestureData.tipBox.textContent=(gestureData.videoPlayer.volume*100|0)+"%";lastY=gestureData.touchEnd.screenY;}},50);}'},
  55. 'V↑▽':{name:'关闭增加音量',code:'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";'},
  56. 'V↓▼':{name:'减少音量',code:'if(document.fullscreen){gestureData.videoPlayer.muted=false;gestureData.tipBox.textContent=(gestureData.videoPlayer.volume*100|0)+"%";gestureData.tipBox.style.display="block";let lastY=gestureData.touchEnd.screenY;gestureData.videoTimer=setInterval(()=>{if(lastY-gestureData.touchEnd.screenY){let tempVolume=gestureData.videoPlayer.volume+(lastY-gestureData.touchEnd.screenY)/100;gestureData.videoPlayer.volume=+(tempVolume>1) || (+(tempVolume>0) && tempVolume);gestureData.tipBox.textContent=(gestureData.videoPlayer.volume*100|0)+"%";lastY=gestureData.touchEnd.screenY;}},50);}'},
  57. 'V↓▽':{name:'关闭减少音量',code:'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";'},
  58. 'V→▼':{name:'右滑进度',code:'let lastX=gestureData.touchEnd.screenX,hour,minu,sec;function showTip(){minu=gestureData.videoPlayer.currentTime/60;sec=gestureData.videoPlayer.currentTime%60;hour=minu/60;minu%=60;gestureData.tipBox.textContent=`${hour|0}:${(minu<10) ? "0":""}${minu|0}:${(sec<10) ? "0" : ""}${sec|0}`;}showTip();gestureData.tipBox.style.display="block";gestureData.videoTimer=setInterval(()=>{let len=gestureData.touchEnd.screenX-lastX;if(len){gestureData.videoPlayer.currentTime+=len*(1+Math.abs(len)*gestureData.videoPlayer.duration/10800).toFixed(2);lastX=gestureData.touchEnd.screenX;}showTip();},50);'},
  59. 'V→▽':{name:'关闭右滑进度',code:'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";'},
  60. 'V←▼':{name:'左滑进度',code:'let lastX=gestureData.touchEnd.screenX,hour,minu,sec;function showTip(){minu=gestureData.videoPlayer.currentTime/60;sec=gestureData.videoPlayer.currentTime%60;hour=minu/60;minu%=60;gestureData.tipBox.textContent=`${hour|0}:${(minu<10) ? "0":""}${minu|0}:${(sec<10) ? "0" : ""}${sec|0}`;}showTip();gestureData.tipBox.style.display="block";gestureData.videoTimer=setInterval(()=>{let len=gestureData.touchEnd.screenX-lastX;if(len){gestureData.videoPlayer.currentTime+=len*(1+Math.abs(len)*gestureData.videoPlayer.duration/10800).toFixed(2);lastX=gestureData.touchEnd.screenX;}showTip();},50);'},
  61. 'V←▽':{name:'关闭左滑进度',code:'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";'}
  62. }
  63. gestureData.settings={
  64. '滑动系数':[0.2,0,0.5,2],//[当前值,最小值,最大值,取值精度]
  65. '文字手势':true,
  66. '图片手势':true,
  67. '视频手势':true,
  68. '选词翻译':false,
  69. '网页加速':false,
  70. '视频下载':false,
  71. '避免断触':false
  72. };
  73. //GM方法写入
  74. gestureData.GM_setValue=GM_setValue;
  75. gestureData.GM_getValue=GM_getValue;
  76. gestureData.GM_openInTab=GM_openInTab;
  77. gestureData.GM_setClipboard=GM_setClipboard;
  78. //存储数据读取
  79. gestureData.gesture=gestureData.GM_getValue('gesture',gestureData.gesture);
  80. gestureData.settings=gestureData.GM_getValue('settings',gestureData.settings);
  81. //脚本常量
  82. const LIMIT=((screen.width>screen.height ? screen.height : screen.width)*gestureData.settings['滑动系数'][0])**2,ATTACH_SHADOW=Element.prototype.attachShadow,CANVAS_2D_DRAWIMAGE=CanvasRenderingContext2D.prototype.drawImage,
  83. CHECK_M_OBSERVER=new MutationObserver(()=>{if(!checkTimer){checkTimer=setTimeout(loadCheck,200);}}),
  84. IMG_I_OBSERVER=new IntersectionObserver((entries)=>{for(let Ti=0,len=entries.length;Ti<len;++Ti){if(entries[Ti].intersectionRatio){entries[Ti].target.setAttribute('_imgShow_','1');}else{entries[Ti].target.setAttribute('_imgShow_','0');}}},{threshold:[0,0.5,1]}),
  85. A_I_OBSERVER=new IntersectionObserver((entries)=>{let link=null,nowTime=Date.now();for(let Ti=0,len=entries.length;Ti<len;++Ti){link=entries[Ti].target;if(entries[Ti].intersectionRatio){link.setAttribute('_linkShow_','1');if(nowTime>link._prefetch_){link._prefetch_=nowTime+300000;document.head.insertAdjacentHTML('beforeend',`<link rel="prefetch" href="${link.href.replace(/^https?:/,'')}"/>`);}}else{link.setAttribute('_linkShow_','0');}}},{rootMargin:'50%',threshold:[0,0.5,1]});
  86.  
  87. /*手势功能模块*/
  88. //手指功能变量
  89. let startPoint={},timeSpan=0,pressTime=0,raiseTime=0,slideTime=0,slideStamp=0,slideLimit=0,fingersNum=0,gestureTimer=0,clickTimer=0,isAllow=0,isClick=0;
  90. //手势执行
  91. gestureData.runCode=(code)=>{
  92. try{eval(code);}catch(error){
  93. if((error+'').indexOf('unsafe-eval')>-1){
  94. window.eval=(function(){
  95. this.gestureData=gestureData;this.close=window.close;//将数据传递给外部
  96. let script=document.createElement('script');
  97. return (js)=>{
  98. script.remove();script=document.createElement('script');
  99. script.textContent=`try{${js}}catch(error){alert(\`${gestureData.path}” 手势执行脚本错误:\\n${error} \`);}`;
  100. document.body.insertAdjacentElement('beforeend',script);
  101. }
  102. })();eval(code);
  103. }else{alert(`“${gestureData.path}” 手势执行脚本错误:\n${error} !`);}
  104. }
  105. }
  106. gestureData.runFrame=(runPath)=>{
  107. let code=gestureData.gesture[runPath].code;
  108. if(top===self || /^[TIV]/.test(runPath)){gestureData.runCode(code);}
  109. else{
  110. if(code.indexOf('/*ONLY TOP*/')<0){gestureData.runCode(code);}
  111. if(/\/\*(ONLY|WITH) TOP\*\//.test(code)){
  112. if(/[●▼]$/.test(runPath)){window._isPushing_=()=>{let _gestureData={};_gestureData.touchEnd=copyTouch(gestureData.touchEnd);top.postMessage({'type':'pushTouch','gestureData':_gestureData},'*');}}
  113. let _gestureData={};
  114. _gestureData.touchStart=copyTouch(gestureData.touchStart);
  115. _gestureData.touchEnd=copyTouch(gestureData.touchEnd);
  116. top.postMessage({'type':'runPath','runPath':gestureData.path,'gestureData':_gestureData},'*');
  117. }
  118. }
  119. }
  120. gestureData.runGesture=(newPath)=>{
  121. if(gestureData.gesture[gestureData.path]){
  122. gestureData.runFrame(gestureData.path);
  123. if(gestureData.gesture[newPath]){gestureData.path=newPath;}
  124. }else if(gestureData.gesture[gestureData.path.slice(1)] && /^[TIV]/.test(gestureData.path)){
  125. gestureData.runFrame(gestureData.path.slice(1));
  126. if(gestureData.gesture[newPath?.slice(1)]){gestureData.path=newPath;}
  127. } raiseTime=0;
  128. }
  129. //长按执行
  130. function longPress(){
  131. if(isAllow && !/[●○▽]$/.test(gestureData.path)){
  132. isAllow=isClick=0;
  133. startPoint=gestureData.touchEnd;
  134. let newPath=gestureData.path+'○';gestureData.path+='●';
  135. gestureData.runGesture(newPath);
  136. }
  137. }
  138. //持续滑动执行
  139. function slidingRun(){
  140. slideStamp=0;
  141. let newPath=gestureData.path+'▽';gestureData.path+='▼';
  142. gestureData.runGesture(newPath);
  143. gestureData.path=gestureData.path.replace('▼','');
  144. }
  145. //手指按下
  146. function touchStart(e){
  147. clearTimeout(gestureTimer);
  148. if((fingersNum=e.touches.length)>1){return;}
  149. pressTime=Date.now();timeSpan=pressTime-raiseTime;
  150. let lineLen=raiseTime && (e.changedTouches[0].screenX-gestureData.touchEnd.screenX)**2+(e.changedTouches[0].screenY-gestureData.touchEnd.screenY)**2;
  151. if(timeSpan>50 || lineLen>LIMIT){//断触判断
  152. startPoint=e.changedTouches[0];
  153. if(timeSpan>200 || lineLen>LIMIT*4){
  154. gestureData.path='';slideLimit=LIMIT;
  155. gestureData.touchEle=e.target;
  156. gestureData.touchEnd=gestureData.touchStart=startPoint;
  157. gestureData.selectWords=window.getSelection()+'';
  158. if(gestureData.selectWords && gestureData.settings['文字手势']){gestureData.path='T';}
  159. else if(document.contains(gestureData.videoPlayer) && gestureData.settings['视频手势']){
  160. let videoRect=gestureData.findVideoBox().getBoundingClientRect();
  161. if(fullsState>0 && gestureData.touchStart.clientY<(videoRect.y+videoRect.height/8)){gestureData.path='!';}
  162. else if(gestureData.touchStart.clientX>videoRect.x && gestureData.touchStart.clientX<(videoRect.x+videoRect.width) && gestureData.touchStart.clientY>videoRect.y && gestureData.touchStart.clientY<(videoRect.y+videoRect.height)){gestureData.path='V';}
  163. }
  164. }else if(isClick){e.preventDefault();}
  165. slideTime=pressTime;isAllow=isClick=1;
  166. }else if(isClick){clearTimeout(clickTimer);gestureData.path=gestureData.path.slice(0,-1);}
  167. gestureTimer=setTimeout(longPress,300+slideTime-pressTime);
  168. }
  169. //手指滑动
  170. function touchMove(e){
  171. clearTimeout(gestureTimer);
  172. gestureData.touchEnd=e.changedTouches ? e.changedTouches[0] : e;
  173. if(window._isPushing_){setTimeout(window._isPushing_);}
  174. if(/[○▽]$/.test(gestureData.path) || fingersNum>1){return;}
  175. let xLen=(gestureData.touchEnd.screenX-startPoint.screenX)**2,yLen=(gestureData.touchEnd.screenY-startPoint.screenY)**2,
  176. direction=(xLen>yLen*1.42) ? ((gestureData.touchEnd.screenX>startPoint.screenX) ? '→' : '←') : ((gestureData.touchEnd.screenY>startPoint.screenY) ? '↓' : '↑'),
  177. nowTime=Date.now(),pathLen=xLen+yLen,lastIcon=gestureData.path?.slice(-1);
  178. if(pathLen>LIMIT/576){
  179. slideTime=nowTime;isClick=0;
  180. if(lastIcon===direction || pathLen>slideLimit){
  181. if(lastIcon!==direction && (timeSpan<50 || 'TIV◆'.indexOf(lastIcon)>-1)){gestureData.path+=direction;slideLimit*=(slideLimit<LIMIT/2) || 0.64;slideStamp=nowTime+300;isAllow=1;timeSpan=0;}
  182. startPoint=gestureData.touchEnd;
  183. if(slideStamp && nowTime>slideStamp){setTimeout(slidingRun);}
  184. }else if(pathLen>LIMIT/100){slideStamp=isAllow=0;}
  185. }
  186. gestureTimer=setTimeout(longPress,300+slideTime-nowTime);
  187. }
  188. //手指抬起
  189. function touchEnd(e){
  190. clearTimeout(gestureTimer);
  191. if(--fingersNum>0){if(!/[○▽]$/.test(gestureData.path)){gestureData.path='!';}return;}
  192. if(window._isPushing_){window._isPushing_=null;}
  193. gestureData.touchEnd=e.changedTouches[0];
  194. raiseTime=Date.now();setTimeout(iframeLock);
  195. if(/[○▽]$/.test(gestureData.path)){setTimeout(gestureData.runGesture);return;}
  196. if(isClick){gestureData.path+='◆';if(/^V◆◆$|^T/.test(gestureData.path)){e.preventDefault();e.stopPropagation();window.getSelection().empty();}}
  197. if(isAllow){gestureTimer=setTimeout(gestureData.runGesture,199);}
  198. }
  199. //延迟点击,避免断触触发点击
  200. function delayClick(e){
  201. if(e.isTrusted){
  202. e.preventDefault();e.stopImmediatePropagation();
  203. if(timeSpan<50){return;}
  204. let ev=new PointerEvent('click',{bubbles:true,cancelable:true,clientX:e.clientX,clientY:e.clientY,composed:true,detail:1,layerX:e.layerX,layerY:e.layerY,offsetX:e.offsetX,offsetY:e.offsetY,pageX:e.pageX,pageY:e.pageY,pointerId:e.pointerId,pointerType:e.pointerType,screenX:e.screenX,screenY:e.screenY,sourceCapabilities:e.sourceCapabilities,view:e.view,x:e.x,y:e.y});
  205. clickTimer=setTimeout(()=>{e.target.dispatchEvent(ev);},50);
  206. }
  207. }
  208.  
  209. /*视频功能模块*/
  210. //视频功能变量
  211. let oriLock=0,resizeTimer=0,fullsState=0;
  212. //videoPlayer赋值
  213. async function setVideo(player){
  214. let _videoPlayer=player.target || player;
  215. if(gestureData.videoPlayer?.paused===false && _videoPlayer.muted===true){return;}
  216. gestureData.videoPlayer=_videoPlayer;
  217. videoOriLock();
  218. gestureData.videoPlayer.insertAdjacentElement('afterend',gestureData.tipBox);
  219. if(gestureData.settings['视频下载']){
  220. await gestureData.findVideoBox()?.insertAdjacentElement('beforeend',gestureData.videoPlayer._downloadTip_);
  221. if(window._urlObjects_[gestureData.videoPlayer.src]){
  222. gestureData.videoPlayer._downloadTip_.textContent='正在捕获';
  223. gestureData.videoPlayer._downloadTip_.buffers=window._urlObjects_[gestureData.videoPlayer.src].sourceBuffers;
  224. window._urlObjects_[gestureData.videoPlayer.src]._downloadTip_=gestureData.videoPlayer._downloadTip_;
  225. delete window._urlObjects_[gestureData.videoPlayer.src];
  226. }else if(gestureData.videoPlayer._downloadTip_.textContent==='未加载'){
  227. if(!gestureData.videoPlayer.src && gestureData.videoPlayer.children.length){gestureData.videoPlayer.src=gestureData.videoPlayer.firstChild.src;}
  228. if(gestureData.videoPlayer.src.indexOf('blob:') && gestureData.videoPlayer.src){gestureData.videoPlayer._downloadTip_.textContent='可下载';}
  229. }
  230. }
  231. }
  232. //video方向锁定
  233. function videoOriLock(){
  234. if(!gestureData.videoPlayer.videoWidth){if(!gestureData.videoPlayer.error && document.contains(gestureData.videoPlayer)){setTimeout(videoOriLock,100);}oriLock=0;return;}
  235. oriLock=+(gestureData.videoPlayer.videoWidth>gestureData.videoPlayer.videoHeight);
  236. if(fullsState>0 && oriLock){top.postMessage({'type':'GYRO'},'*');}
  237. else{screen.orientation.unlock();}
  238. }
  239. //video框架锁定
  240. function iframeLock(){
  241. if(top!==self && !window._isShow_){gestureData.GM_setValue('isShow',Date.now());}
  242. }
  243. //画布视频检测
  244. CanvasRenderingContext2D.prototype.drawImage=async function(){
  245. let ele=arguments[0];
  246. if(ele.nodeName==='VIDEO' && !document.contains(ele)){
  247. ele.style.display='none';
  248. this.canvas.insertAdjacentElement('afterend',ele);
  249. }
  250. return CANVAS_2D_DRAWIMAGE.apply(this,arguments);
  251. }
  252. //video全屏/退出全屏
  253. gestureData.videoFullScreen=async ()=>{
  254. if(resizeTimer){return;}
  255. if(document.fullscreen){await document.exitFullscreen()?.catch(Date);}
  256. else if(gestureData.videoPlayer){await gestureData.findVideoBox()?.requestFullscreen()?.catch(Date);}
  257. else if(iframeEles.length){gestureData.GM_setValue('fullscreen',Date.now());}
  258. }
  259. //获取video全屏样式容器
  260. gestureData.findVideoBox=(player=gestureData.videoPlayer)=>{
  261. if(!document.contains(player)){return null;}
  262. if(player._videoBox_?.contains(player) && (document.fullscreen || player._boxHeight_===player._videoBox_.clientHeight)){return player._videoBox_;}
  263. player._videoBox_=player.parentNode;player.setAttribute('_videobox_','');
  264. let parentEle=player._videoBox_.parentNode,videoStyle=getComputedStyle(player),childStyle=getComputedStyle(player._videoBox_),childWidth=0,childHeight=0,_childWidth=0,_childHeight=0;
  265. if(player._videoBox_.offsetParent===parentEle){
  266. childWidth=Math.round(player.offsetWidth+(+videoStyle.marginLeft.slice(0,-2))+(+videoStyle.marginRight.slice(0,-2)));
  267. childHeight=Math.round(player.offsetHeight+(+videoStyle.marginTop.slice(0,-2))+(+videoStyle.marginBottom.slice(0,-2)));
  268. _childWidth=Math.round(player._videoBox_.offsetWidth+(+childStyle.marginLeft.slice(0,-2))+(+childStyle.marginRight.slice(0,-2)));
  269. _childHeight=Math.round(player._videoBox_.offsetHeight+(+childStyle.marginTop.slice(0,-2))+(+childStyle.marginBottom.slice(0,-2)));
  270. }else{
  271. childWidth=Math.round(player.offsetWidth+(+videoStyle.left.slice(0,-2) || 0)+(+videoStyle.marginLeft.slice(0,-2))+(+videoStyle.marginRight.slice(0,-2))+(+videoStyle.right.slice(0,-2) || 0));
  272. childHeight=Math.round(player.offsetHeight+(+videoStyle.top.slice(0,-2) || 0)+(+videoStyle.marginTop.slice(0,-2))+(+videoStyle.marginBottom.slice(0,-2))+(+videoStyle.bottom.slice(0,-2) || 0));
  273. _childWidth=Math.round(player._videoBox_.offsetWidth+(+childStyle.left.slice(0,-2) || 0)+(+childStyle.marginLeft.slice(0,-2))+(+childStyle.marginRight.slice(0,-2))+(+childStyle.right.slice(0,-2) || 0));
  274. _childHeight=Math.round(player._videoBox_.offsetHeight+(+childStyle.top.slice(0,-2) || 0)+(+childStyle.marginTop.slice(0,-2))+(+childStyle.marginBottom.slice(0,-2))+(+childStyle.bottom.slice(0,-2) || 0));
  275. }
  276. childWidth=(childWidth>_childWidth) ? childWidth : _childWidth;
  277. childHeight=(childHeight>_childHeight) ? childHeight : _childHeight;
  278. while(childWidth>=parentEle.clientWidth && (childWidth<1.2*parentEle.clientWidth || !parentEle.clientWidth) && (childHeight<1.2*parentEle.clientHeight || !parentEle.clientHeight) && parentEle.nodeName!=='BODY'){
  279. if(childHeight<parentEle.clientHeight){
  280. let isBreak=1;
  281. for(let childEle of parentEle.children){
  282. childStyle=getComputedStyle(childEle);
  283. childHeight=Math.round(childEle.offsetHeight+(+childStyle.top.slice(0,-2) || 0)+(+childStyle.marginTop.slice(0,-2))+(+childStyle.marginBottom.slice(0,-2))+(+childStyle.bottom.slice(0,-2) || 0));
  284. if(childHeight>=parentEle.clientHeight && player.clientHeight*1.2>parentEle.clientHeight){isBreak=0;break;}
  285. }
  286. if(isBreak){break;}
  287. }
  288. if(parentEle.clientHeight){
  289. player._videoBox_.setAttribute('_videobox_','');
  290. player._videoBox_=parentEle;player._boxHeight_=parentEle.clientHeight;
  291. childStyle=getComputedStyle(parentEle);
  292. if(parentEle.offsetParent===parentEle.parentNode){
  293. _childWidth=Math.round(parentEle.offsetWidth+(+childStyle.marginLeft.slice(0,-2))+(+childStyle.marginRight.slice(0,-2)));
  294. _childHeight=Math.round(parentEle.offsetHeight+(+childStyle.marginTop.slice(0,-2))+(+childStyle.marginBottom.slice(0,-2)));
  295. }else{
  296. _childWidth=Math.round(parentEle.offsetWidth+(+childStyle.left.slice(0,-2) || 0)+(+childStyle.marginLeft.slice(0,-2))+(+childStyle.marginRight.slice(0,-2))+(+childStyle.right.slice(0,-2) || 0));
  297. _childHeight=Math.round(parentEle.offsetHeight+(+childStyle.top.slice(0,-2) || 0)+(+childStyle.marginTop.slice(0,-2))+(+childStyle.marginBottom.slice(0,-2))+(+childStyle.bottom.slice(0,-2) || 0));
  298. }
  299. childWidth=(childWidth>_childWidth) ? childWidth : _childWidth;
  300. childHeight=(childHeight>_childHeight) ? childHeight : _childHeight;
  301. }
  302. parentEle=parentEle.parentNode;
  303. }
  304. player._videoBox_.setAttribute('_videobox_','outer');
  305. return player._videoBox_;
  306. }
  307. //全屏检测事件
  308. function regRESIZE(){
  309. let videoCss=gestureData.addStyle(''),stopResize=()=>{resizeTimer=0;};
  310. window.addEventListener('resize',()=>{
  311. clearTimeout(resizeTimer);resizeTimer=setTimeout(stopResize,200);
  312. if(document.fullscreen && !fullsState){
  313. fullsState=document.fullscreenElement;
  314. if(fullsState.nodeName==='IFRAME'){fullsState=-1;return;}
  315. let srcFindVideo=fullsState.getElementsByTagName('video'),srcVideo=(fullsState.nodeName==='VIDEO') ? fullsState : srcFindVideo[0];
  316. if(!fullsState.hasAttribute('_videobox_') && (!srcVideo || srcFindVideo.length>1 || srcVideo._videoBox_.offsetWidth*srcVideo._videoBox_.offsetHeight*1.2<fullsState.offsetWidth*fullsState.offsetHeight)){fullsState=-1;return;}
  317. if(srcVideo!==gestureData.videoPlayer){gestureData.videoPlayer?.pause();setVideo(srcVideo);}
  318. fullsState=1;if(oriLock){top.postMessage({'type':'GYRO'},'*');}
  319. videoCss.textContent='*[_videobox_]{inset:0 !important;margin:0 !important;padding:0 !important;}*[_videobox_=""]{width:100% !important;height:100% !important;max-width:100% !important;max-height:100% !important;}video{transform:none !important;object-fit:contain !important;}';
  320. }else if(fullsState && !document.fullscreen){fullsState=0;videoCss.textContent='';}
  321. },true);
  322. }
  323.  
  324. /*视频下载模块*/
  325. if(gestureData.settings['视频下载']){
  326. //原始方法存储
  327. const CREATE_OBJ_URL=URL.createObjectURL,ADD_SOURCE_BUFFER=MediaSource.prototype.addSourceBuffer,APPEND_BUFFER=SourceBuffer.prototype.appendBuffer,END_OF_STREAM=MediaSource.prototype.endOfStream;
  328. //初始化视频下载
  329. window._initDownload_=(player)=>{
  330. player._downloadTip_=document.createElement('div');
  331. player._downloadTip_.style.cssText='position:absolute;right:0;top:20px;background:#3498db;border-radius:20px 0 0 20px;text-align:center;padding:20px;line-height:0px;color:#fff;min-width:60px;font-size:16px;font-family:system-ui;z-index:2147483647;';
  332. player._downloadTip_.target=player;
  333. player._downloadTip_.textContent='未加载';
  334. if(window._urlObjects_[player.src]){
  335. player._downloadTip_.textContent='正在捕获';
  336. player._downloadTip_.buffers=window._urlObjects_[player.src].sourceBuffers;
  337. window._urlObjects_[player.src]._downloadTip_=player._downloadTip_;
  338. delete window._urlObjects_[player.src];
  339. }else{
  340. if(!player.src && player.children.length){player.src=player.firstChild.src;}
  341. if(player.src.indexOf('blob:') && player.src){player._downloadTip_.textContent='可下载';}
  342. }
  343. player._downloadTip_.onclick=window._downloadVideo_;
  344. player._videoBox_.insertAdjacentElement('beforeend',player._downloadTip_);
  345. }
  346. //下载视频
  347. window._downloadVideo_=function(data){
  348. if(this.textContent==='未加载'){return;}
  349. if(data.target){data=this;data.src=this.target.src;}
  350. let buffers=data.buffers;
  351. if(top!==self){
  352. let _buffers=[];
  353. for(let Ti=0,len=buffers.length;Ti<len;++Ti){
  354. _buffers.push({'mime':buffers[Ti]._mime_,'bufferList':buffers[Ti]._bufferList_});
  355. }
  356. top.postMessage({'type':'download','buffers':_buffers,'src':data.src},'*');
  357. return;
  358. }
  359. let a=document.createElement('a');a.download=document.title;a.style.display='none';document.body.insertAdjacentElement('beforeend',a);
  360. if(data.src.indexOf('blob:') && data.src){a.href=data.src;a.click();}
  361. else if(buffers.length){
  362. for(let Ti=0,len=buffers.length;Ti<len;++Ti){
  363. a.href=URL.createObjectURL(new Blob(buffers[Ti]._bufferList_,{'type':buffers[Ti]._mime_}));
  364. a.click();
  365. URL.revokeObjectURL(a.href);
  366. }
  367. }
  368. a.remove();
  369. }
  370. //存储MediaSource
  371. window._urlObjects_={};
  372. URL.createObjectURL=(obj)=>{
  373. let url=CREATE_OBJ_URL(obj);
  374. if(obj.sourceBuffers){window._urlObjects_[url]=obj;}
  375. return url;
  376. }
  377. //添加捕获
  378. MediaSource.prototype.addSourceBuffer=function(mime){
  379. let sourceBuffer=ADD_SOURCE_BUFFER.call(this,mime);
  380. sourceBuffer._bufferList_=[];
  381. sourceBuffer._mime_=mime;
  382. sourceBuffer._mediaSource_=this;
  383. return sourceBuffer;
  384. }
  385. //捕获片段
  386. SourceBuffer.prototype.appendBuffer=function(buffer){
  387. this._bufferList_.push(buffer);
  388. if(this._mime_.indexOf('video')>-1 && this._mediaSource_._downloadTip_){this._mediaSource_._downloadTip_.textContent=`已捕获${this._bufferList_.length}个片段`;}
  389. APPEND_BUFFER.call(this,buffer);
  390. }
  391. //捕获完成
  392. MediaSource.prototype.endOfStream=function(){
  393. if(this._downloadTip_){this._downloadTip_.textContent='可下载';}
  394. END_OF_STREAM.call(this);
  395. }
  396. }
  397.  
  398. /*选词翻译模块*/
  399. if(gestureData.settings['选词翻译']){
  400. //选词翻译变量
  401. let selectTimer=0,translateBox=null;
  402. //语言检测
  403. function detectLanguage(text){
  404. //中文检测(包含扩展汉字)
  405. let chineseRegex=/[\u4E00-\u9FFF\u3400-\u4DBF\u{20000}-\u{2EBEF}]/u;
  406. if(chineseRegex.test(text)) return 'zh-CN';
  407. // 常见语言字符检测
  408. let japaneseRegex=/[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/,
  409. koreanRegex=/[\uAC00-\uD7AF]/,
  410. cyrillicRegex=/[\u0400-\u04FF]/,
  411. arabicRegex=/[\u0600-\u06FF]/;
  412. if(japaneseRegex.test(text)) return 'ja';//日语
  413. if(koreanRegex.test(text)) return 'ko';//韩语
  414. if(cyrillicRegex.test(text)) return 'ru';//俄语
  415. if(arabicRegex.test(text)) return 'ar';//阿拉伯语
  416. //默认英语(适用于拉丁字母)
  417. return 'en';
  418. }
  419. // 翻译函数
  420. async function translateText(text){
  421. let sourceLang=detectLanguage(text),targetLang=sourceLang==='zh-CN' ? 'en' : 'zh-CN';
  422. return new Promise((resolve)=>{
  423. GM_xmlhttpRequest({
  424. method:'GET',
  425. url:`https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=${sourceLang}|${targetLang}`,
  426. onload:(response)=>{
  427. try{
  428. let result=JSON.parse(response.responseText);
  429. if(result.responseStatus===200){
  430. resolve(result.responseData.translatedText);
  431. }else{
  432. resolve('糟糕X﹏X,翻译失败了!');
  433. }
  434. }catch{
  435. resolve('糟糕X﹏X,翻译失败了!');
  436. }
  437. },
  438. onerror:()=>{resolve('糟糕X﹏X,翻译失败了!');}
  439. });
  440. });
  441. }
  442. //处理文本选择
  443. function handleSelection(){
  444. clearTimeout(selectTimer);
  445. if(!translateBox){
  446. translateBox=document.createElement('div');
  447. translateBox.style.cssText='position:fixed;transform:translateX(-25%);max-width:80%;padding:15px 25px;background:#1e1e1e;border-radius:8px;font-size:16px;font-family:system-ui;color:#fff;z-index:2147483647;border:2px solid #eee;display:none;align-items:center;';
  448. document.body.insertAdjacentElement('beforeend',translateBox);
  449. translateBox.ontouchstart=function(){gestureData.GM_setClipboard(this.textContent);alert('翻译复制成功!');}
  450. }
  451. let selection=window.getSelection().toString().trim();
  452. if(!selection){translateBox.style.display='none';return;}
  453. selectTimer=setTimeout(async ()=>{
  454. if(!window.getSelection().toString().trim()){translateBox.style.display='none';return;}
  455. translateBox.textContent=await translateText(selection);
  456. if(!window.getSelection().toString().trim()){translateBox.style.display='none';return;}
  457. translateBox.style.left=gestureData.touchEnd.clientX+'px';
  458. translateBox.style.top=Math.min(gestureData.touchEnd.clientY+screen.width*.05,window.innerHeight-screen.width*.2)+'px';
  459. if(gestureData.touchEnd.clientX<screen.width*.2){translateBox.style.transform='translateX(-10%)';}
  460. else if(gestureData.touchEnd.clientX>window.innerWidth-screen.width*.2){translateBox.style.left=gestureData.touchEnd.clientX-screen.width*.2+'px';translateBox.style.transform='none';}
  461. else{translateBox.style.transform='translateX(-25%)';}
  462. translateBox.style.display='flex';
  463. },1000)
  464. }
  465. //选词翻译事件注册(不可用)
  466. document.addEventListener('selectionchange',handleSelection,true);
  467. }
  468.  
  469. /*功能补充模块*/
  470. //功能补充变量
  471. let videoEles=[],imgEles=[],linkEles=[],iframeEles=document.getElementsByTagName('iframe'),checkTimer=0;
  472. //修改Trusted-Types策略
  473. window.trustedTypes?.createPolicy('default',{createHTML:string=>string,createScript:string=>string,createScriptURL:string=>string});
  474. //设置shadow-root (open)
  475. Element.prototype.attachShadow=function(){
  476. if(!gestureData.shadowList){gestureData.shadowList=[];}
  477. let shadowRoot=ATTACH_SHADOW.call(this,arguments[0]);
  478. gestureData.shadowList.push(shadowRoot);
  479. CHECK_M_OBSERVER.observe(shadowRoot,{childList:true,subtree:true});
  480. return shadowRoot;
  481. }
  482. //页面加载检测
  483. async function loadCheck(){
  484. linkEles=[...document.querySelectorAll('a:not([_linkShow_])')];
  485. videoEles=[...document.querySelectorAll('video:not([_videoBox_])')];
  486. imgEles=[...document.querySelectorAll('img:not([_imgShow_]),[style*="url("]:not([_imgShow_])')];
  487. //检测shadow-root
  488. if(gestureData.shadowList){
  489. for(let Ti=0,len=gestureData.shadowList.length;Ti<len;++Ti){
  490. linkEles.push(...gestureData.shadowList[Ti].querySelectorAll('a:not([_linkShow_])'));
  491. videoEles.push(...gestureData.shadowList[Ti].querySelectorAll('video:not([_videoBox_])'));
  492. await imgEles.push(...gestureData.shadowList[Ti].querySelectorAll('img:not([_imgShow_]),[style*="url("]:not([_imgShow_])'));
  493. }
  494. }
  495. //video播放事件绑定
  496. if(videoEles.length && gestureData.settings['视频手势']){
  497. if(!gestureData.tipBox){
  498. //启动全屏检测
  499. regRESIZE();
  500. if(top!==self){top.postMessage({'type':'forceFullScreen'},'*');}
  501. //tip操作提示
  502. gestureData.tipBox=document.createElement('div');
  503. gestureData.tipBox.style.cssText='min-width:80px;height:50px;padding:0 10px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:none;color:#1e87f0;font-size:22px;line-height:50px;text-align:center;background-color:rgba(0,0,0,0.6);border-radius:20px;font-family:system-ui;z-index:2147483647;';
  504. }
  505. for(let Ti=0,len=videoEles.length;Ti<len;++Ti){
  506. if(!videoEles[Ti]._videoBox_){
  507. await gestureData.findVideoBox(videoEles[Ti]);
  508. if(gestureData.settings['视频下载']){await window._initDownload_(videoEles[Ti]);}
  509. if(!videoEles[Ti].paused){
  510. setVideo(videoEles[Ti]);
  511. if(top!==self && window._isShow_===undefined){gestureData.GM_setValue('isShow',Date.now());}
  512. }
  513. videoEles[Ti].addEventListener('playing',setVideo,true);
  514. }
  515. }
  516. }
  517. //图片可视事件绑定
  518. if(gestureData.settings['图片手势']){
  519. for(let Ti=0,len=imgEles.length;Ti<len;++Ti){
  520. imgEles[Ti].setAttribute('_imgShow_','0');
  521. IMG_I_OBSERVER.observe(imgEles[Ti]);
  522. }
  523. }
  524. //链接预加载绑定
  525. if(gestureData.settings['网页加速']){
  526. for(let Ti=0,len=linkEles.length;Ti<len;++Ti){
  527. linkEles[Ti].setAttribute('_linkShow_','0');
  528. if(linkEles[Ti].href.indexOf('/')>-1){
  529. linkEles[Ti]._prefetch_=0;
  530. A_I_OBSERVER.observe(linkEles[Ti]);
  531. linkEles[Ti].addEventListener('click',function(){this._prefetch_=0;window.stop();},true);
  532. }
  533. }
  534. }
  535. checkTimer=0;
  536. }
  537. //添加样式表
  538. gestureData.addStyle=(css)=>{
  539. let style=document.createElement('style');
  540. style.textContent=css;
  541. if(document.head){
  542. document.head.insertAdjacentElement('beforeend',style);
  543. return style;
  544. }else{setTimeout(()=>{gestureData.addStyle(css)});}
  545. }
  546. //复制坐标对象
  547. function copyTouch(oldObj){
  548. let newObj={};
  549. for(let Ti in oldObj){
  550. if(Ti==='target'){continue;}
  551. newObj[Ti]=oldObj[Ti];
  552. }
  553. return newObj;
  554. }
  555. //手势功能设置UI
  556. gestureData.openSet=()=>{
  557. let gestureName='',gesturePath='',gestureBox=document.createElement('div'),pathEle=null,_clickTimer=0;
  558. //页面生成
  559. gestureData.addStyle('*{overflow:hidden !important;}'+
  560. '#_gestureBox_{background-color:#fff;width:100%;height:100%;position:fixed;padding:0;margin:0;inset:0;overflow-y:auto !important;z-index:2147483647;}'+
  561. '#_gestureBox_ *{font-family:system-ui;margin:0;padding:0;text-align:center;font-size:5vmin;line-height:12vmin;user-select:none !important;transform:none;text-indent:0;}'+
  562. '#_gestureBox_ ::placeholder{color:#999;font-size:2.5vmin;line-height:6vmin;}'+
  563. '#_gestureBox_ h1{width:60%;height:12vmin;color:#0074d9;background-color:#dee6ef;margin:3vmin auto;border-radius:12vmin;box-shadow:0.9vmin 0.9vmin 3vmin #dfdfdf;}'+
  564. '#_gestureBox_ #_addGesture_{width:14vmin;height:14vmin;margin:3vmin auto;line-height:14vmin;background-color:#dee6ef;color:#032e58;font-size:7.5vmin;border-radius:15vmin;box-shadow:0.3vmin 0.3vmin 1.5vmin #dfdfdf;}'+
  565. '#_gestureBox_ ._gestureLi_{height:18vmin;width:100%;border-bottom:0.3vmin solid #dfdfdf;}'+
  566. '#_gestureBox_ ._gestureLi_ p{margin:3vmin 0 0 1%;width:38%;height:12vmin;border-left:1.8vmin solid;color:#ffb400;background-color:#fff1cf;float:left;white-space:nowrap;text-overflow:ellipsis;text-shadow:0.3vmin 0.3vmin 3vmin #ffcb56;}'+
  567. '#_gestureBox_ ._gestureLi_ ._gesturePath_{margin:3vmin 0 0 3%;float:left;width:38%;height:12vmin;background-color:#f3f3f3;color:#000;box-shadow:0.3vmin 0.3vmin 1.5vmin #ccc9c9;border-radius:3vmin;white-space:nowrap;text-overflow:ellipsis;}'+
  568. '#_gestureBox_ ._gestureLi_ ._delGesture_{margin:3vmin 2% 0 0;width:15vmin;height:12vmin;float:right;color:#f00;text-decoration:line-through;}'+
  569. '#_gestureBox_ #_revisePath_{background-color:rgba(0,0,0,0.7);width:100%;height:100%;position:fixed;inset:0;display:none;color:#000;}'+
  570. '#_gestureBox_ #_revisePath_ span{width:15vmin;height:15vmin;font-size:12.5vmin;line-height:15vmin;position:absolute;}'+
  571. '#_gestureBox_ #_revisePath_ div{color:#3339f9;position:absolute;width:30%;height:12vmin;font-size:10vmin;bottom:15%;}'+
  572. '#_gestureBox_ #_revisePath_ p{color:#3ba5d8;position:absolute;top:15%;font-size:10vmin;height:12vmin;width:100%;}'+
  573. '#_gestureBox_ #_revisePath_ #_path_{top:40%;color:#ffee03;height:100%;word-wrap:break-word;font-size:15vmin;line-height:18vmin;}'+
  574. '#_gestureBox_ #_editGesture_{overflow-y:auto !important;background-color:#fff;width:100%;height:100%;position:fixed;inset:0;display:none;color:#000;}'+
  575. '#_gestureBox_ #_editGesture_ p{color:#3339f9;font-size:7.5vmin;text-align:left;margin:6vmin 0 0 9vmin;width:100%;height:9vmin;line-height:9vmin;}'+
  576. '#_gestureBox_ #_editGesture_ #_gestureName_{margin-top:6vmin;width:80%;height:12vmin;color:#000;border:0.3vmin solid #dadada;border-radius:3vmin;text-align:left;padding:0 3vmin;}'+
  577. '#_gestureBox_ #_editGesture_ ._label_box_>label{display:inline-block;margin-top:6vmin;position:relative;}'+
  578. '#_gestureBox_ #_editGesture_ ._label_box_>label>input{position:absolute;top:0;left:-6vmin;}'+
  579. '#_gestureBox_ #_editGesture_ ._label_box_>label>div{width:20vw;border:#ddd solid 0.3vmin;height:12vmin;color:#666;position:relative;}'+
  580. '#_gestureBox_ #_editGesture_ ._label_box_>label>input:checked + div{border:#d51917 solid 0.3vmin;color:#d51917;}'+
  581. '#_gestureBox_ #_editGesture_ ._label_box_>label>input + div:after{top:auto;left:auto;bottom:-3vmin;right:0;transition:none;}'+
  582. '#_gestureBox_ #_editGesture_ ._label_box_>label>input:checked + div:after{content:"";display:block;border:none;width:6vmin;height:6vmin;background-color:#d51917;transform:skewY(-45deg);position:absolute;}'+
  583. '#_gestureBox_ #_editGesture_ ._label_box_>label>input:checked + div:before{content:"";display:block;width:0.9vmin;height:2.4vmin;border-right:#fff solid 0.6vmin;border-bottom:#fff solid 0.6vmin;transform:rotate(35deg);position:absolute;bottom:0.6vmin;right:1.2vmin;z-index:1;}'+
  584. '#_gestureBox_ #_editGesture_ #_gestureCode_{overflow-y:auto !important;width:80%;margin-top:6vmin;height:40%;text-align:left;line-height:6vmin;padding:3vmin;border:0.3vmin solid #dadada;border-radius:3vmin;}'+
  585. '#_gestureBox_ #_editGesture_ button{width:30vmin;height:15vmin;font-size:7.5vmin;line-height:15vmin;display:inline-block;color:#fff;background-color:#2866bd;margin:6vmin 3vmin 0 3vmin;border:none;}'+
  586. '#_gestureBox_ #_settingsBox_{overflow-y:auto !important;background-color:#fff;width:100%;height:100%;position:fixed;inset:0;display:none;color:#000;}'+
  587. '#_gestureBox_ #_settingsBox_ p{color:#3339f9;text-align:left;margin:9vmin 0 0 9vmin;float:left;height:6vmin;line-height:6vmin;clear:both;}'+
  588. '#_gestureBox_ #_settingsBox_ ._slideRail_{overflow:initial !important;width:55%;background-color:#a8a8a8;float:left;margin:12vmin 0 0 3vmin;height:0.6vmin;position:relative;}'+
  589. '#_gestureBox_ #_settingsBox_ ._slideRail_ ._slideButton_{line-height:9vmin;color:#fff;background-color:#2196f3;min-width:9vmin;height:9vmin;border-radius:9vmin;font-size:4vmin;position:absolute;top:-4.5vmin;box-shadow:0.3vmin 0.3vmin 1.8vmin #5e8aee;padding:0 1vmin;}'+
  590. '#_gestureBox_ #_settingsBox_ ._switch_{position:relative;display:inline-block;width:18vmin;height:9vmin;float:left;margin:7.5vmin 42% 0 3vmin;}'+
  591. '#_gestureBox_ #_settingsBox_ ._switch_ input{display:none;}'+
  592. '#_gestureBox_ #_settingsBox_ ._slider_{border-radius:9vmin;position:absolute;cursor:pointer;inset:0;background-color:#ccc;transition:0.4s;}'+
  593. '#_gestureBox_ #_settingsBox_ ._slider_:before{border-radius:50%;position:absolute;content:"";height:7.5vmin;width:7.5vmin;left:0.6vmin;bottom:0.6vmin;background-color:white;transition:0.4s;}'+
  594. '#_gestureBox_ #_settingsBox_ input:checked + ._slider_{background-color:#2196F3;}'+
  595. '#_gestureBox_ #_settingsBox_ input:checked + ._slider_:before{transform:translateX(9vmin);}'+
  596. '#_gestureBox_ #_settingsBox_ #_saveSettings_{display:block;clear:both;width:30vmin;height:15vmin;font-size:7.5vmin;line-height:15vmin;color:#fff;background-color:#2866bd;border:none;margin:12vmin 0 0 calc(50% - 15vmin);float:left;}');
  597. gestureBox.id='_gestureBox_';
  598. document.body.insertAdjacentElement('beforeend',gestureBox);
  599. gestureBox.innerHTML='<h1 id="_openSettings_">手势轨迹设置</h1><div id="_addGesture_">+</div><div id="_gestureUL_"></div>'+
  600. '<div id="_revisePath_"><span style="top:0;left:0;text-align:left;">┌</span><span style="top:0;right:0;text-align:right;">┐</span><span style="bottom:0;left:0;text-align:left;">└</span><span style="bottom:0;right:0;text-align:right;">┘</span>'+
  601. '<p>请滑动手指</p><p id="_path_"></p><div id="_clearPath_" style="left:10%;">清除</div><div id="_cancleRevise_" style="right:10%;">保存</div></div>'+
  602. '<div id="_editGesture_"><p>手势名称:</p><input type="text" id="_gestureName_" maxlength="12" placeholder="最大输入12个字符">'+
  603. '<p>手势类型:</p><div class="_label_box_"><label><input type="radio" id="_G_" name="_gestureType_" value=""><div>一般</div></label><label><input type="radio" id="_T_" name="_gestureType_" value="T"><div>文字</div></label><label><input type="radio" id="_I_" name="_gestureType_" value="I"><div>图片</div></label><label><input type="radio" id="_V_" name="_gestureType_" value="V"><div>视频</div></label></div>'+
  604. '<p>手势执行脚本:</p><textarea id="_gestureCode_" placeholder="可用变量说明↓\n gestureData:手势数据常量,如果你需要在不同手势间传递变量,你可以赋值gestureData.变量名=变量值;\n gestureData.touchEle:手指触摸的源元素;\n gestureData.selectWords:选中的文字;\n gestureData.touchStart:触摸开始坐标对象;\n gestureData.touchEnd:触摸最新坐标对象;\n gestureData.path:滑动的路径;\n gestureData.videoPlayer:正在播放的视频元素。'+
  605. '\n\n可用方法说明↓\n gestureData.addStyle(CSS样式):将CSS样式添加到网页上;\n gestureData.runGesture():以path为路径执行手势,你可以修改path后执行此方法;\n gestureData.GM_openInTab(链接):打开链接;\n gestureData.GM_setClipboard(文本):复制文本到剪切板;\n gestureData.GM_setValue(变量名,变量值):在油猴中存储数据;\n gestureData.GM_getValue(变量名,默认值):从油猴中取出数据,没有则使用默认值。'+
  606. '\n\n可识别代码注释说明(仅对一般手势生效)↓\n 默认情况:存在iframe时,所有手势只会在触发手势的页面对象执行!\n 添加/*ONLY TOP*/:手势只在顶级页面对象执行;\n 添加/*WITH TOP*/:手势同时在当前页面对象和顶级页面对象执行。"></textarea>'+
  607. '<div style="width:100%;height:0.3vmin;"></div><button id="_closeEdit_">关闭</button><button id="_saveGesture_">保存</button></div>'+
  608. '<div id="_settingsBox_"><h1>功能开关设置</h1><span id="_settingList_"></span><button id="_saveSettings_">保存</button></div>';
  609. pathEle=document.getElementById('_path_');
  610.  
  611. //编辑手势
  612. function editGesture(){
  613. gestureName=this.parentNode.getAttribute('name');
  614. if(['打开设置','视频全屏','手势穿透'].indexOf(gestureName)>-1){alert('该手势脚本无法修改!');return;}
  615. gesturePath=this.parentNode.getAttribute('path');
  616. let selectType=(/^[TIV]/.test(gesturePath)) ? `_${gesturePath.slice(0,1)}_` : '_G_';
  617. document.getElementById(selectType).click();
  618. document.getElementById('_gestureName_').value=gestureName;
  619. document.getElementById('_gestureCode_').value=gestureData.gesture[gesturePath].code;
  620. document.getElementById('_editGesture_').style.display='block';
  621. }
  622. //修改路径
  623. function revisePath(){
  624. gestureName=this.parentNode.getAttribute('name');
  625. gesturePath=this.parentNode.getAttribute('path');
  626. pathEle.textContent='';
  627. window.removeEventListener('touchmove',touchMove,true);
  628. window.removeEventListener('pointermove',touchMove,true);
  629. document.getElementById('_revisePath_').style.display='block';
  630. }
  631. //删除手势
  632. function delGesture(){
  633. gestureName=this.parentNode.getAttribute('name');
  634. if(['打开设置','视频全屏','手势穿透'].indexOf(gestureName)>-1){alert('该手势无法删除!');return;}
  635. if(!confirm(`确定删除"${gestureName}"手势`)){return;}
  636. gesturePath=this.parentNode.getAttribute('path');
  637. delete gestureData.gesture[gesturePath];
  638. gestureData.GM_setValue('gesture',gestureData.gesture);
  639. init();
  640. }
  641. //滑动条
  642. function silideBar(e){
  643. e.preventDefault();fingersNum=2;
  644. let diffX=e.changedTouches[0].clientX-gestureData.touchStart.clientX,
  645. leftPX=(+this.style.left.slice(0,-2))+diffX,vmin=this.offsetWidth/2,setArr=gestureData.settings[this.id];
  646. leftPX=(leftPX<-vmin) ? -vmin : ((leftPX>(diffX=this.parentNode.offsetWidth-vmin)) ? diffX : leftPX);
  647. this.style.left=leftPX+'px';
  648. this.textContent=((leftPX+vmin)/this.parentNode.offsetWidth*(setArr[2]-setArr[1])+setArr[1]).toFixed(setArr[3]);
  649. gestureData.touchStart=e.changedTouches[0];
  650. }
  651. //长按执行
  652. function _longPress(){if(isClick || !/^$|[●○▼▽]$/.test(pathEle.textContent)){isClick=0;startPoint=gestureData.touchEnd;pathEle.textContent+='●';}}
  653. //持续滑动执行
  654. function _slidingRun(){slideStamp=0;pathEle.textContent+='▼';}
  655. //点击执行
  656. function _clickRun(){if(!/[○▼▽]$/.test(pathEle.textContent)){pathEle.textContent+='◆';}}
  657. //界面初始化
  658. function init(){
  659. let gestureUL=document.getElementById('_gestureUL_');
  660. gestureUL.textContent='';
  661. for(let Ti in gestureData.gesture){
  662. let gestureLi=document.createElement('div'),nameEle=document.createElement('p'),pathEle=document.createElement('div'),delEle=document.createElement('div');
  663. gestureLi.className='_gestureLi_';gestureLi.setAttribute('name',gestureData.gesture[Ti].name);gestureLi.setAttribute('path',Ti);
  664. nameEle.textContent=gestureData.gesture[Ti].name;nameEle.addEventListener('click',editGesture,true);
  665. pathEle.className='_gesturePath_';pathEle.textContent=Ti;pathEle.addEventListener('click',revisePath,true);
  666. delEle.className='_delGesture_';delEle.textContent='删除';delEle.addEventListener('click',delGesture,true);
  667. gestureLi.insertAdjacentElement('beforeend',nameEle);
  668. gestureLi.insertAdjacentElement('beforeend',pathEle);
  669. gestureLi.insertAdjacentElement('beforeend',delEle);
  670. gestureUL.insertAdjacentElement('beforeend',gestureLi);
  671. }
  672. }
  673. init();
  674.  
  675. //.新建手势
  676. document.getElementById('_addGesture_').addEventListener('click',()=>{
  677. gestureName=gesturePath='';
  678. document.getElementById('_G_').click();
  679. document.getElementById('_gestureName_').value='';
  680. document.getElementById('_gestureCode_').value='';
  681. document.getElementById('_editGesture_').style.display='block';
  682. },true);
  683. //保存手势
  684. document.getElementById('_saveGesture_').addEventListener('click',()=>{
  685. let name=document.getElementById('_gestureName_').value;
  686. if(!name){alert('请输入手势名称!');return;}
  687. if(document.querySelector(`#_gestureBox_ ._gestureLi_[name="${name}"]:not([path="${gesturePath}"])`)){alert('存在同名手势!');return;}
  688. let typeEle=document.getElementsByName('_gestureType_');
  689. for(let Ti=0,len=typeEle.length;Ti<len;++Ti){
  690. if(typeEle[Ti].checked){
  691. let newPath=typeEle[Ti].value+((gestureName && gesturePath.indexOf('[')<0) ? ((/^[TIV]/.test(gesturePath)) ? gesturePath.slice(1) : gesturePath) : (`[${name}]`));
  692. if(newPath!==gesturePath){
  693. if(gestureData.gesture[newPath]){
  694. let pathTXT=typeEle[Ti].value+`[${gestureData.gesture[newPath].name}]`;
  695. gestureData.gesture[pathTXT]=gestureData.gesture[newPath];
  696. }
  697. gestureData.gesture[newPath]=gestureData.gesture[gesturePath] || {};
  698. delete gestureData.gesture[gesturePath];
  699. }
  700. gestureData.gesture[newPath].name=name;
  701. gestureData.gesture[newPath].code=document.getElementById('_gestureCode_').value;
  702. break;
  703. }
  704. }
  705. gestureData.GM_setValue('gesture',gestureData.gesture);
  706. init();
  707. document.getElementById('_editGesture_').style.display='none';
  708. },true);
  709. //关闭编辑
  710. document.getElementById('_closeEdit_').addEventListener('click',()=>{
  711. document.getElementById('_editGesture_').style.display='none';
  712. },true);
  713. //路径修改事件
  714. document.getElementById('_revisePath_').addEventListener('touchstart',()=>{
  715. if(fingersNum>1){return;}
  716. clearTimeout(gestureTimer);clearTimeout(_clickTimer);
  717. gestureTimer=setTimeout(_longPress,300+slideTime-pressTime);
  718. },true);
  719. document.getElementById('_revisePath_').addEventListener('touchmove',(e)=>{
  720. e.preventDefault();clearTimeout(gestureTimer);
  721. gestureData.touchEnd=e.changedTouches[0];
  722. if(/[○▼▽]$/.test(pathEle.textContent) || fingersNum>1){return;}
  723. let xLen=(gestureData.touchEnd.screenX-startPoint.screenX)**2,yLen=(gestureData.touchEnd.screenY-startPoint.screenY)**2,
  724. direction=(xLen>yLen) ? ((gestureData.touchEnd.screenX>startPoint.screenX) ? '→' : '←') : ((gestureData.touchEnd.screenY>startPoint.screenY) ? '↓' : '↑'),
  725. nowTime=Date.now(),pathLen=xLen+yLen,lastIcon=pathEle.textContent.slice(-1);
  726. if(pathLen>LIMIT/576){
  727. slideTime=nowTime;isClick=0;
  728. if(lastIcon===direction || pathLen>LIMIT){
  729. if(lastIcon!==direction){pathEle.textContent+=direction;slideStamp=nowTime+300;}
  730. startPoint=gestureData.touchEnd;
  731. if(slideStamp && nowTime>slideStamp){_slidingRun();}
  732. }else if(pathLen>LIMIT/100){slideStamp=0;}
  733. }
  734. gestureTimer=setTimeout(_longPress,300+slideTime-nowTime);
  735. },true);
  736. document.getElementById('_revisePath_').addEventListener('touchend',(e)=>{
  737. if(!isClick || fingersNum>0){return;}
  738. if(gestureData.path.indexOf('◆◆')>-1){gestureData.path='';
  739. switch(pathEle.textContent.slice(-1)){
  740. case '●':{pathEle.textContent=pathEle.textContent.slice(0,-1)+'○';break;}
  741. case '○':{pathEle.textContent=pathEle.textContent.slice(0,-1)+'●';break;}
  742. case '▼':{pathEle.textContent=pathEle.textContent.slice(0,-1)+'▽';break;}
  743. case '▽':{pathEle.textContent=pathEle.textContent.slice(0,-1)+'▼';break;}
  744. default:{pathEle.textContent+='◆';setTimeout(_clickRun,100);break;}
  745. }
  746. }else{_clickTimer=setTimeout(_clickRun,200);}
  747. });
  748. //清除路径
  749. document.getElementById('_clearPath_').addEventListener('touchend',(e)=>{
  750. e.stopPropagation();
  751. if(!isClick || fingersNum>0){return;}
  752. if(gestureData.path.indexOf('◆◆')>-1){gestureData.path='';pathEle.textContent='';}
  753. else{pathEle.textContent=pathEle.textContent.slice(0,-1);}
  754. });
  755. //保存修改路径
  756. document.getElementById('_cancleRevise_').addEventListener('touchend',(e)=>{
  757. e.preventDefault();e.stopPropagation();
  758. if(!isClick || fingersNum>0){return;}
  759. if(pathEle.textContent){
  760. if(gestureName==='视频全屏' && pathEle.textContent.slice(-1)!=='◆'){alert('视频全屏需要以◆结尾!');return;}
  761. if(gestureData.gesture[pathEle.textContent]?.name==='手势穿透'){alert('路径与"手势穿透"功能冲突!');return;}
  762. if(/^[TIV]/.test(gesturePath)){pathEle.textContent=gesturePath.slice(0,1)+pathEle.textContent;}
  763. if(gestureData.gesture[pathEle.textContent]){
  764. let pathTXT=((/^[TIV]/.test(gesturePath)) ? gesturePath.slice(0,1) : '')+`[${gestureData.gesture[pathEle.textContent].name}]`;
  765. gestureData.gesture[pathTXT]=gestureData.gesture[pathEle.textContent];
  766. }
  767. gestureData.gesture[pathEle.textContent]=gestureData.gesture[gesturePath];
  768. delete gestureData.gesture[gesturePath];
  769. gestureData.GM_setValue('gesture',gestureData.gesture);
  770. init();
  771. }
  772. window.addEventListener('touchmove',touchMove,{capture:true,passive:true});
  773. window.addEventListener('pointermove',touchMove,{capture:true,passive:true});
  774. document.getElementById('_revisePath_').style.display='none';
  775. });
  776. //打开功能开关设置
  777. document.getElementById('_openSettings_').addEventListener('click',()=>{
  778. gestureBox.style.cssText='overflow-y:hidden !important';
  779. document.getElementById('_settingsBox_').style.display='block';
  780. let settingList=document.getElementById('_settingList_');
  781. settingList.textContent='';
  782. for(let Ti in gestureData.settings){
  783. settingList.innerHTML+=`<p>${Ti}:</p>`;
  784. if(typeof(gestureData.settings[Ti])==='boolean'){
  785. settingList.innerHTML+=`<label class="_switch_"><input type="checkbox" id="${Ti}" ${((gestureData.settings[Ti]) ? 'checked' : '')}><div class="_slider_"></div></label>`;
  786. }else if(typeof(gestureData.settings[Ti])==='object'){
  787. settingList.innerHTML+=`<div class="_slideRail_"><div class="_slideButton_" id="${Ti}"></div></div>`;
  788. let slideButton=document.getElementById(Ti),
  789. leftPX=slideButton.parentNode.offsetWidth*(gestureData.settings[Ti][0]-gestureData.settings[Ti][1])/(gestureData.settings[Ti][2]-gestureData.settings[Ti][1])-slideButton.offsetWidth/2;
  790. slideButton.style.left=leftPX+'px';
  791. slideButton.textContent=gestureData.settings[Ti][0].toFixed(gestureData.settings[Ti][3]);
  792. }
  793. }
  794. let slideList=document.getElementsByClassName('_slideButton_');
  795. for(let Ti=0,len=slideList.length;Ti<len;++Ti){
  796. slideList[Ti].addEventListener('touchmove',silideBar,true);
  797. }
  798. },true);
  799. //保存功能开关设置
  800. document.getElementById('_saveSettings_').addEventListener('click',()=>{
  801. gestureBox.style.cssText='';
  802. for(let Ti in gestureData.settings){
  803. if(typeof(gestureData.settings[Ti])==='boolean'){
  804. gestureData.settings[Ti]=document.getElementById(Ti).checked;
  805. }else if(typeof(gestureData.settings[Ti])==='object'){
  806. gestureData.settings[Ti][0]=+document.getElementById(Ti).textContent;
  807. }
  808. }
  809. gestureData.GM_setValue('settings',gestureData.settings);
  810. document.getElementById('_settingsBox_').style.display='none';
  811. },true);
  812. }
  813.  
  814. /*事件注册(不可用)模块*/
  815. (function(){
  816. if(top===self){
  817. //清除后退定时器
  818. window.addEventListener('popstate',()=>{clearTimeout(gestureData.backTimer);gestureData.backTimer=0;},true);
  819. window.addEventListener('beforeunload',()=>{clearTimeout(gestureData.backTimer);gestureData.backTimer=0;},true);
  820. //接收iframe数据
  821. window.addEventListener('message',async (e)=>{
  822. let data=e.data;
  823. switch(data.type){
  824. case 'GYRO':{//锁定横屏模式
  825. await screen.orientation.lock('landscape')?.catch(Date);
  826. break;}
  827. case 'forceFullScreen':{//iframe强制可全屏
  828. for(let Ti=0,len=iframeEles.length;Ti<len;++Ti){
  829. if(iframeEles[Ti].contentWindow===e.source){
  830. if(!iframeEles[Ti].allowFullscreen){
  831. iframeEles[Ti].allowFullscreen=true;
  832. if(iframeEles[Ti].getAttribute('src') && iframeEles[Ti].src.indexOf('/')>-1){
  833. iframeEles[Ti].src=iframeEles[Ti].src;
  834. }
  835. }
  836. break;
  837. }
  838. }
  839. break;}
  840. case 'runPath':{//iframe手势在顶级页面执行
  841. for(let Ti=0,len=iframeEles.length;Ti<len;++Ti){
  842. if(iframeEles[Ti].contentWindow===e.source){
  843. let ifrRect=iframeEles[Ti].getBoundingClientRect();
  844. gestureData.touchStart=data.gestureData.touchStart;gestureData.touchEnd=data.gestureData.touchEnd;
  845. gestureData.touchStart.target=gestureData.touchEnd.target=gestureData.touchEle=iframeEles[Ti];
  846. gestureData.touchStart.pageX=gestureData.touchStart.clientX+=ifrRect.x;
  847. gestureData.touchStart.pageY=gestureData.touchStart.clientY+=ifrRect.y;
  848. gestureData.touchEnd.pageX=gestureData.touchEnd.clientX+=ifrRect.x;
  849. gestureData.touchEnd.pageY=gestureData.touchEnd.clientY+=ifrRect.y;
  850. break;
  851. }
  852. }
  853. gestureData.path=data.runPath;setTimeout(gestureData.runGesture);
  854. break;}
  855. case 'pushTouch':{//iframe手势坐标传递
  856. let ifrRect=gestureData.touchEle.getBoundingClientRect();
  857. gestureData.touchEnd=data.gestureData.touchEnd;
  858. gestureData.touchEnd.target=gestureData.touchEle;
  859. gestureData.touchEnd.pageX=gestureData.touchEnd.clientX+=ifrRect.x;
  860. gestureData.touchEnd.pageY=gestureData.touchEnd.clientY+=ifrRect.y;
  861. break;}
  862. case 'download':{//iframe视频下载
  863. window._downloadVideo_(data);
  864. break;}
  865. }
  866. },true);
  867. }else{
  868. //iframe视频全屏
  869. GM_addValueChangeListener('fullscreen',async (name,old_value,new_value,remote)=>{
  870. if(!document.hidden && window._isShow_){
  871. await gestureData.findVideoBox()?.requestFullscreen()?.catch(Date);
  872. }
  873. });
  874. //iframe锁定
  875. GM_addValueChangeListener('isShow',(name,old_value,new_value,remote)=>{
  876. if(!document.hidden){window._isShow_=!remote;}
  877. });
  878. }
  879. //加载检测
  880. checkTimer=setTimeout(loadCheck,200);
  881. CHECK_M_OBSERVER.observe(document,{childList:true,subtree:true});
  882. //手势事件注册(不可用)
  883. window.addEventListener('touchstart',touchStart,{capture:true,passive:false});
  884. window.addEventListener('touchmove',touchMove,{capture:true,passive:true});
  885. window.addEventListener('pointermove',touchMove,{capture:true,passive:true});
  886. window.addEventListener('touchend',touchEnd,{capture:true,passive:false});
  887. window.addEventListener('touchcancel',touchEnd,{capture:true,passive:false});
  888. if(gestureData.settings['图片手势']){window.addEventListener('contextmenu',(e)=>{if((gestureData.path.indexOf("I")>-1 || e.target.nodeName==='IMG') && gestureData.touchEle.src!==location.href){e.preventDefault();}},true);}//长按图片时禁止弹出菜单
  889. if(gestureData.settings['避免断触']){window.addEventListener('click',delayClick,true);}
  890. //禁止网页检测焦点
  891. window.addEventListener('visibilitychange',(e)=>{e.stopImmediatePropagation();//禁止页面切换检测
  892. if(document.hidden){//视频后台播放
  893. let playState=gestureData.videoPlayer?.paused,playTime=gestureData.videoPlayer?.currentTime+0.2,playSpeed=gestureData.videoPlayer?.playbackRate,playVolume=gestureData.videoPlayer?.volume;
  894. setTimeout(()=>{if(playState!==gestureData.videoPlayer?.paused){
  895. gestureData.videoPlayer.load();gestureData.videoPlayer.currentTime=playTime;
  896. gestureData.videoPlayer.onloadstart=()=>{gestureData.videoPlayer.play();gestureData.videoPlayer.playbackRate=playSpeed;gestureData.videoPlayer.volume=playVolume;}
  897. }});
  898. }else if(gestureData.settings['网页加速']){//更新预加载链接
  899. let links=[...document.querySelectorAll('a[_linkShow_="1"]')],nowTime=Date.now();
  900. if(gestureData.shadowList){
  901. for(let Ti=0,len=gestureData.shadowList.length;Ti<len;++Ti){
  902. links.push(...gestureData.shadowList[Ti].querySelectorAll('a[_linkShow_="1"]'));
  903. }
  904. }
  905. for(let Ti=0,len=links.length;Ti<len;++Ti){
  906. if(nowTime>links[Ti]._prefetch_){
  907. links[Ti]._prefetch_=Date.now()+300000;
  908. document.head.insertAdjacentHTML('beforeend',`<link rel="prefetch" href="${links[Ti].href.replace(/^https?:/,'')}"/>`);
  909. }
  910. }
  911. }
  912. },true);
  913. window.addEventListener('pagehide',(e)=>{e.stopImmediatePropagation();},true);
  914. window.addEventListener('blur',(e)=>{e.stopImmediatePropagation();});
  915. //禁止修改复制内容
  916. window.addEventListener('beforecopy',(e)=>{e.stopImmediatePropagation();},true);
  917. window.addEventListener('copy',(e)=>{if(gestureData.selectWords){return;}e.stopImmediatePropagation();},true);
  918. //禁止网页写入剪切板
  919. document.execCommand=()=>{};
  920. if(navigator.clipboard){navigator.clipboard.writeText=()=>{};navigator.clipboard.write=()=>{};}
  921. //解除选中限制
  922. gestureData.addStyle('html,html *{user-select:auto !important;touch-action:manipulation;overscroll-behavior-x:none !important;}');
  923. window.addEventListener('selectstart',(e)=>{e.stopImmediatePropagation();},true);
  924. //预取+预渲染,高版本内核才支持speculationrules推测规则api
  925. if(gestureData.settings['网页加速'] && HTMLScriptElement.supports && HTMLScriptElement.supports('speculationrules')){
  926. let specScript=document.createElement('script'),
  927. specRules={
  928. 'prerender':[{
  929. 'source':'document',
  930. 'where':{'and':[{'selector_matches':'a[_linkShow_="1"]'}]},
  931. 'referrer_policy':'strict-origin-when-cross-origin',
  932. 'eagerness':'immediate'
  933. }],
  934. 'prefetch':[{
  935. 'source':'document',
  936. 'where':{'and':[{'selector_matches':'a[_linkShow_="1"]'}]},
  937. 'referrer_policy':'strict-origin-when-cross-origin',
  938. 'eagerness':'immediate'
  939. }],
  940. 'prefetch_with_subresources':[{
  941. 'source':'document',
  942. 'where':{'and':[{'selector_matches':'a[_linkShow_="1"]'}]},
  943. 'referrer_policy':'strict-origin-when-cross-origin',
  944. 'eagerness':'immediate'
  945. }]
  946. };
  947. specScript.type='speculationrules';
  948. specScript.textContent=JSON.stringify(specRules);
  949. document.head?.insertAdjacentElement('beforeend',specScript);
  950. }
  951. window.addEventListener('beforeunload',(e)=>{e.stopImmediatePropagation();},true);
  952. window.addEventListener('unload',(e)=>{e.stopImmediatePropagation();},true);
  953. })();

QingJ © 2025

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