// ==UserScript== // @name 防止鏈接被翻譯破壞 // @namespace https://www.velhlkj.com/ // @version 2.3 // @description 對鏈接進行處裡,防止Google翻譯時對鏈接進行破壞,可以兼容動態插入的鏈接,使用時先在需要翻譯的頁面右鍵,然後在油猴的選單中點擊此腳本名稱,此時再進行Google翻譯,可以正常點擊鏈接了。 // @author 龍翔翎 // @match // @include * // @icon data:image/png;base64,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 // @grant none // @run-at context-menu // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; var inProgress = false; //是否正在處理 function setTrans(sources){ if(inProgress) return; //正在處理時再次調用則中斷執行 inProgress = true; //將狀態設為正在處理 let linkIndex = 0; //初始化編號 for(const source of sources){ //逐個鏈接進行處理 if(source.querySelector("img")) continue; //圖片鏈接跳過 let unique = new Date().getTime(); //獲取毫秒時間戳作為唯一性表示(同一毫秒內不可能執行兩次setTrans調用,配合linkIndex保證data-vntl-mark的絕對唯一) const faker = source.cloneNode(true); //複製原始鏈接元素來創建偽鏈接 source.setAttribute('translate', 'no'); //原始鏈接設為不可翻譯(第三方翻譯插件可能不遵守此屬性) /*>>> 處理原始鏈接樣式以隱藏原始鏈接*/ source.style.width = "0"; source.style.height = "0"; source.style.overflow = "hidden"; source.style.margin = "0"; source.style.padding = "0"; source.style.border = "0"; if(window.getComputedStyle(source).display == "inline"){ source.style.display = "inline-block"; source.style.float = "left"; } /*<<< 原始鏈接樣式處理完畢*/ source.setAttribute("data-vntl-mark", "vel-no-translate-link-" + unique + "_" + linkIndex); //為原始鏈接添加data-vntl-mark屬性,屬性值是唯一ID(這裡不直接使用id屬性的原因是為了防止部份網頁已經對鏈接設定了id,佔用可能會出問題) faker.removeAttribute("id"); //移除偽鏈接的id屬性,避免id衝突 faker.removeAttribute("href"); //移除偽鏈接的href屬性,偽鏈接不應具有任何跳轉能力 faker.setAttribute('data-faker', source.getAttribute("data-vntl-mark")); //添加偽鏈接的data-faker屬性,用來紀錄對應的原始鏈接data-vntl-mark faker.setAttribute("onclick",`document.querySelector('a[data-vntl-mark="${source.getAttribute("data-vntl-mark")}"]').click();`); //添加偽鏈接的click事件,用以觸發原始鏈接點擊。(這裡只能用onclick,Google翻譯會重新創建被翻譯的元素,會導致EventListener這類綁定上去的事件,及內部自定義屬性全部丟失,這也是原始鏈接被翻譯後可能會有問題的主要原因,但明文寫在html中的則不會消失,因此用onclick是唯一可用的方案) source.after(faker); //將偽鏈接添加到原始鏈接之後(不知什麼時候起,原生js居然提供了after方法) linkIndex ++; //增加編號 } inProgress = false; //全部執行完畢把狀態設為不在處理中 } document.addEventListener("DOMNodeInserted", function (event) { //監聽節點插入事件 用以更新動態加入的內容 if(event.target.getAttribute && event.target.getAttribute("data-faker")) return; //如果被加入的元素是偽鏈接(faker)則中斷處理 setTrans(document.querySelectorAll('a:not([data-faker]):not([data-vntl-mark])')); //提取所有不是 [偽鏈接] 與 [被處理過的鏈接] 的鏈接並傳給setTrans處理 }, false); document.addEventListener("DOMNodeRemoved",function(event){ //監聽節點移除事件 用以監控原始鏈接是否被刪除 if(event.target.getAttribute && event.target.getAttribute("data-vntl-mark")){ //如果被刪除的是原始鏈接 let mark = event.target.getAttribute("data-vntl-mark"); //從Event獲取原始鏈接元素並讀取data-vntl-mark let faker = document.querySelector(`[data-faker="${mark}"]`); //依data-vntl-mark找到對應的偽鏈接元素 if(faker) faker.parentElement.removeChild(faker); //刪除偽鏈接元素 } }) //初始執行(初始時機為點擊右鍵選單中插件名的動作)初始執行只觸發一次 setTrans(document.querySelectorAll('a:not([data-faker]):not([data-vntl-mark])')); //提取所有不是 [偽鏈接] 與 [被處理過的鏈接] 的鏈接並傳給setTrans處理 let tipstyle = document.createElement("style"); //創建樣式表元素 //為樣式表添加內容 tipstyle.textContent = ` @keyframes showtip { 0% { opacity: 0; } 15% { opacity: 1; } 85%{ opacity: 1; } 100%{ opacity: 0; } } #vel-patch-tip { display: block; position: fixed; top: calc(50% - 50px); left: calc(50% - 225px); width: 450px; height: 100px; line-height: 100px; background-color: #056b00; backdrop-filter: blur(30px); opacity: 0; border-radius: 10px; animation: showtip 3s; text-align: center; pointer-events: none; z-index:99999999; color: white; font-weight: bold; font-size: 20px; box-shadow: 0 0 10px rgba(0,0,0,0.5); }`; tipstyle.id = "vel-patch-tipstyle"; //設定樣式表ID用以刪除 document.body.appendChild(tipstyle); //將樣式表添加到頁面 let tip = document.createElement("div"); //創建提示框元素 tip.textContent = "鏈接防破壞補釘應用成功,可以開始翻譯頁面"; //添加提示框內容 tip.id = "vel-patch-tip"; //設定提示框ID用以刪除 document.body.appendChild(tip); //添加提示框(因為animation屬性,它會自動以動畫顯示並消失) //設定一個計時器以在提示框播放完畢後刪除添加的臨時樣式表與div提示框。 setTimeout(function(){ document.body.removeChild(document.querySelector(`#vel-patch-tipstyle`)); document.body.removeChild(document.querySelector(`#vel-patch-tip`)); },3000); })();