圖片搜索“查看圖片”按鈕:瀏覽器擴(kuò)展實戰(zhàn))
用搜索引擎找圖這件事我?guī)缀趺刻於家蓭谆貙懜逭遗鋱D、驗證一張截圖的出處、給素材找高清版本。以前在圖片搜索結(jié)果頁上鼠標(biāo)懸停一張圖右下角會掛著幾個按鈕其中那個“查看圖片”最順手——點一下直接跳到原圖不用進(jìn)中間頁也不用反復(fù)試右鍵。后來這個按鈕沒了頁面只剩一個鋪滿屏的瀑布流點哪張都把我甩到一個夾著廣告和推薦位的中間頁面里再點一次才看到原圖。這篇東西講的就是怎么把那個按鈕找回來一個幾十行代碼的瀏覽器擴(kuò)展三分鐘裝好裝上之后按鈕重新長在縮略圖上點一下直出原圖。它解決的不是什么高深問題純粹是“一次點擊 vs 三次點擊”的效率損耗但只要你是重度找圖用戶——設(shè)計師、運營、做內(nèi)容搬運的、查證圖片真?zhèn)蔚摹@個損耗一天攢下來就是幾百次多余的跳轉(zhuǎn)。下文我把思路、代碼、踩過的坑全攤開懂一點 HTML 的可以直接抄完全不懂前端的照著步驟裝也能跑起來。1. 按鈕消失了消失的到底是什么1.1 先還原用戶視角的四個真實痛點很多人以為按鈕消失是“功能被砍了”其實更準(zhǔn)確的說法是“入口被挪走或藏起來了”。這兩件事在體驗上的差別很大功能被砍意味著能力徹底不存在入口被挪走意味著能力還在只是你現(xiàn)在得多繞幾步才能碰到它。我把自己和身邊幾個同事的抱怨歸了一下類痛點其實就四個多一跳。以前懸停即點直達(dá)原圖現(xiàn)在必須先點縮略圖進(jìn)中間頁再看一遍廣告和“相關(guān)搜索”才找到真正的圖片鏈接。右鍵失效。早期的搜索結(jié)果頁右鍵“在新標(biāo)簽頁中打開圖片”能拿到大圖改版之后部分圖片是懶加載的占位圖或者低分辨率縮略圖右鍵存下來只有幾十 KB放大就糊。位置漂移。有些圖在中間頁里藏的原始鏈接還帶著一串跳轉(zhuǎn)參數(shù)手動復(fù)制出來還得刪參數(shù)刪錯了直接 404。移動端更慘。窄屏下根本沒有懸停長按彈出的菜單里也沒有“查看圖片”這一項只能一步步點。這四條合在一起就是一個很典型的“產(chǎn)品為了引導(dǎo)流量做的改版”與“重度用戶的操作習(xí)慣”之間的沖突。改版方有它的道理——中間頁能塞更多相關(guān)內(nèi)容、能帶更多曝光但對于只想拿圖的人來說這條路純屬負(fù)擔(dān)。1.2 頁面變了但數(shù)據(jù)還在我一開始也擔(dān)心“是不是接口沒了”?;艘粋€晚上在開發(fā)者工具里翻結(jié)論讓人松口氣圖片的真實地址依然完整地躺在前端 DOM 里只是承接“查看圖片”這個動作的那個按鈕節(jié)點從縮略圖容器里被移除了或者被塞進(jìn)了一個需要額外交互才展開的菜單。這件事的關(guān)鍵在于圖片搜索頁是一個典型的前端異步渲染頁面。你看到的瀑布流不是一次性從服務(wù)器吐出來的 HTML而是頁面加載完之后由 JavaScript 分批請求數(shù)據(jù)、拼接 DOM、再插進(jìn)容器的。滾動一屏就插一批新節(jié)點。這個過程有個很要命的副作用——如果你在頁面load事件里跑一次查詢你只能掃到第一批圖片往下滾動新出來的全是“漏網(wǎng)之魚”。所以任何“找回按鈕”的方案技術(shù)核心都不是“調(diào)接口”而是兩件事找到圖片節(jié)點用一組盡量穩(wěn)的選擇器把頁面上所有縮略圖img撈出來。跟上頁面的節(jié)奏頁面每插入一批新節(jié)點就重新掃一遍把按鈕補上去。第一件事靠 CSS 選擇器第二件事靠MutationObserver——瀏覽器原生的 DOM 變更監(jiān)聽器。搞明白這兩點剩下的全是體力活。提示判斷一個“頁面功能消失”的問題是前端問題還是后端問題最快的辦法是打開開發(fā)者工具在 Elements 面板里搜一下你關(guān)心的數(shù)據(jù)還在不在。在就是前端的事不在才需要往后端想。2. 方案選型四條路擺在面前為什么選擴(kuò)展2.1 四條候選路線的橫向?qū)Ρ仍趧邮种拔野涯芟氲降穆纷佣歼^了一遍列成表更清楚方案實現(xiàn)成本生效范圍更新維護(hù)主要短板瀏覽器擴(kuò)展Content Script中需要寫 manifest 和注入腳本只在匹配的域名和路徑下生效頁面改版后改選擇器即可需要開啟開發(fā)者模式手動加載用戶腳本管理器 腳本低一個.user.js文件同樣按 URL 匹配同上依賴第三方腳本管理器多一層信任成本書簽小工具Bookmarklet極低一段javascript:代碼手動點擊才執(zhí)行改書簽即可每次都要點一次且容易被頁面的 CSP 攔瀏覽器自帶功能 / 系統(tǒng)級工具無全局不可控拿不到頁面內(nèi)的原始圖片地址我最后選擴(kuò)展理由有三個說穿了都很樸素第一它是“一次裝好長期自動生效”的。書簽小工具每次都要手動點我這人記性差點兩次就懶得點了。擴(kuò)展可以在頁面加載完成后自動跑用戶完全無感。第二它能把按鈕“長”在頁面上而不是彈一個浮層。書簽小工具最常見的做法是彈一個面板列出所有圖片鏈接邏輯上能用但體驗割裂——你還得在面板里再找一遍“我要的是哪張”。擴(kuò)展可以直接在每張縮略圖上掛一個按鈕位置對應(yīng)點誰是誰。第三調(diào)試體驗最好。擴(kuò)展的 Content Script 在開發(fā)者工具里有獨立的執(zhí)行上下文console.log打在頁面的 Console 里斷點能打選擇器能實時改。用戶腳本管理器雖然也能調(diào)但多一層注入出問題的時候排查鏈路更長。代價也很明確擴(kuò)展的權(quán)限模型比用戶腳本嚴(yán)格。Manifest V3 之后content_scripts的matches必須寫成明確的 URL 模式不能用*://*/*這種大撒網(wǎng)雖然技術(shù)上能寫但裝的時候會彈一個非常嚇人的“讀取所有網(wǎng)站數(shù)據(jù)”提示。我的做法是把matches收窄到搜索頁的路徑特征上這樣安裝時只提示“讀取此站點數(shù)據(jù)”心理負(fù)擔(dān)小很多也更符合最小權(quán)限原則。2.2 兩個必須先想清楚的約束選完路線之后還有兩個約束條件必須在寫代碼前定下來不然后面全是返工。約束一不能破壞頁面原有的點擊行為??s略圖本身是可點的點了會跳中間頁。我加的按鈕如果只是浮在上面點按鈕的時候事件會冒泡到父級a上結(jié)果就是“既開了新標(biāo)簽頁原頁面又跳走了”。所以按鈕的點擊處理里必須preventDefault()加stopPropagation()兩個都要少一個都不行。約束二不能重復(fù)注入。頁面是異步渲染的我用MutationObserver監(jiān)聽變更而“給容器加按鈕”這個動作本身也會觸發(fā) DOM 變更——如果不做去重就會陷入“加按鈕 → 觸發(fā)變更 → 再加按鈕 → 再觸發(fā)變更”的死循環(huán)幾秒鐘之后頁面上每張圖疊了十幾個按鈕瀏覽器直接卡死。解決辦法是給已經(jīng)處理過的容器打一個標(biāo)記屬性掃描時先查標(biāo)記。這兩個約束聽著像是常識但我在第一版里兩個都踩了后面第 6 節(jié)會詳細(xì)說現(xiàn)場。3. 核心實現(xiàn)拆解三個繞不開的技術(shù)點3.1 Manifest V3 下的最小骨架現(xiàn)代瀏覽器擴(kuò)展的清單文件已經(jīng)不是當(dāng)年那個寬松的 JSON 了Manifest V3 對權(quán)限做了大幅收緊。對我們這個場景真正需要的字段其實很少manifest_version必須是3這是硬性的。content_scripts是核心它聲明“在哪些頁面、注入哪些文件、什么時候注入”。matches決定生效范圍寫成搜索頁的 URL 特征。run_at決定注入時機這里我選document_idle。關(guān)于run_at有句話值得展開。它有三個常見取值document_startDOM 還沒建好就注入、document_endDOM 建好但資源可能沒加載完、document_idle瀏覽器覺得空閑了再注入通常在document_end到load之間。選document_idle的原因是我們的腳本本身要依賴MutationObserver去追后續(xù)渲染所以完全不需要搶跑搶跑反而會因為document.body還不存在而報一堆null引用錯誤。還有all_frames默認(rèn)是false意思是只在頂層文檔注入不進(jìn) iframe。搜索結(jié)果頁的圖片基本都在主文檔里保持false就行。如果哪天你把范圍擴(kuò)到了別的站點發(fā)現(xiàn)某些圖在 iframe 里再把它改成true——但要注意改成true之后同一個頁面里的每個 iframe 都會跑一份你的腳本去重邏輯得更小心。3.2 用 MutationObserver 追上異步渲染的頁面這是整個方案里唯一需要動點腦子地方。先說反面教材我第一版用的是window.addEventListener(load, ...)加上一個setInterval每 500ms 掃描一次。能用但很難看——頁面靜置的時候它也在空轉(zhuǎn)筆記本風(fēng)扇轉(zhuǎn)得明顯而且 500ms 的間隔在快速滾動時會漏掉一些節(jié)點。MutationObserver的用法本身不復(fù)雜觀察document.documentElement配置childList: true和subtree: true這樣整棵樹的任何層級新增節(jié)點都會通知到我。但直接回調(diào)里干活會出事因為瀑布流滾動的時候DOM 變更是一秒幾十次的高頻事件每次變更都全量掃一遍頁面主線程會被拖死。所以中間必須加一層節(jié)流收到通知不立刻掃而是起一個 200ms 的定時器定時器觸發(fā)時才執(zhí)行真正的掃描。200ms 這個數(shù)值是我試出來的——低于 100ms快速滾動時掃描次數(shù)暴漲幀率掉得能看出來高于 400ms新圖片出來之后按鈕要愣一下才出現(xiàn)手感很怪。200ms 基本是“視覺上即時、性能上無感”的平衡點。還有一個小細(xì)節(jié)滾動到底部再往上滾已經(jīng)處理過的容器會重新進(jìn)入視口MutationObserver可能因為懶加載替換src再次觸發(fā)。這時候前面提到的標(biāo)記屬性就派上用場了容器有標(biāo)記就直接跳過不做任何查詢。3.3 按鈕注入與去重別讓頁面長出三胞胎注入這步看著簡單——createElement(button)、設(shè)文本、掛事件、appendChild——但真正決定成敗的是掛在哪個節(jié)點上。我試過三種掛法掛在img上不行。img是替換元素直接給它加子節(jié)點會被瀏覽器忽略而且img的position屬性設(shè)置之后定位基準(zhǔn)也很怪。掛在外層a上基本可行但前提是搜索頁的結(jié)果確實是a包裹的。如果哪天改版換成div加 JS 跳轉(zhuǎn)選擇器就全廢了。掛在最近的定位祖先上最穩(wěn)。用img.closest(a, div)找到最近的可定位父容器給它加position: relative按鈕用絕對定位貼右下角。我最終用的是第二種加第三種的混合優(yōu)先找a找不到就退到最近的div。同時給容器加一個統(tǒng)一的類名方便 CSS 里統(tǒng)一處理定位。去重標(biāo)記我用的是自定義屬性>{ manifest_version: 3, name: Image View Restorer, version: 1.0.0, description: 在圖片搜索結(jié)果頁恢復(fù)“查看圖片”快捷入口, content_scripts: [ { matches: [ https://*.example-search.tld/search*, https://*.example-search.tld/imghp* ], js: [content.js], css: [inject.css], run_at: document_idle, all_frames: false } ] }matches里的example-search.tld是占位符你需要替換成自己實際訪問的搜索頁域名。寫這個模式有幾個規(guī)則要記牢*在主機部分只能匹配一級子域https://*.example.com/*能匹配a.example.com但匹配不了a.b.example.com。路徑部分的*可以匹配任意字符包括/所以/search*能覆蓋/search?q...和/search/images?...。協(xié)議必須寫全https://和http://是兩個不同的模式要分別寫通常只寫https://就夠了。路徑不寫*就代表精確匹配很容易漏。改完matches之后必須回到擴(kuò)展管理頁重新加載一次擴(kuò)展否則新規(guī)則不生效。這是新手最容易卡住的地方——改了半天代碼怎么刷新頁面都沒反應(yīng)以為代碼寫錯了其實是擴(kuò)展還跑在舊配置上。注意這里的域名不要圖省事寫成https://*/*。雖然技術(shù)上能跑但安裝時瀏覽器會提示“讀取和更改您在所有網(wǎng)站上的所有數(shù)據(jù)”一是心理上有負(fù)擔(dān)二是權(quán)限過大意味著哪天代碼出了 bug 可能影響到別的站點。4.3 content.js 完整代碼與逐段講解下面這份是能直接跑的版本我按功能分了段每段后面說清楚它在干嘛。// ---------- 1. 常量與狀態(tài) ---------- const MARK_ATTR data-ivr-injected; const BTN_CLASS ivr-view-btn; const HOST_CLASS ivr-host; const SCAN_DELAY 200; const MIN_SIZE 80; let scanTimer null; // ---------- 2. 地址推導(dǎo) ---------- const SIZE_RULES [ // 把 ?w200h200 之類的尺寸參數(shù)抬到大圖 { re: /([?](?:w|width|h|height|size|sz))\d{1,4}/gi, rep: $11600 }, // 把 xxx-320x480.jpg 這類后綴去掉 { re: /-\d{2,4}x\d{2,4}(\.(?:jpe?g|png|webp|gif))/i, rep: $1 } ]; function upgradeUrl(url) { if (!url) return ; let out url; for (const r of SIZE_RULES) { out out.replace(r.re, r.rep); } return out; } function resolveTargetUrl(img) { const raw img.currentSrc || img.src || ; const upgraded upgradeUrl(raw); if (upgraded upgraded ! raw) return upgraded; const anchor img.closest(a[href]); if (anchor anchor.href) return anchor.href; return raw; }resolveTargetUrl是這個擴(kuò)展里唯一帶點“猜測”成分的函數(shù)邏輯分三層先看縮略圖地址能不能通過替換尺寸參數(shù)變大圖能就直接用不能就退到外層鏈接再不行就用原始src。這三層是遞進(jìn)關(guān)系不是并列——因為大多數(shù)情況下縮略圖地址確實帶尺寸參數(shù)改造一下就能拿到大圖這是最優(yōu)解因為它省掉了一次頁面跳轉(zhuǎn)。// ---------- 3. 按鈕構(gòu)建 ---------- function buildButton(img) { const btn document.createElement(button); btn.type button; btn.className BTN_CLASS; btn.textContent 查看圖片; btn.setAttribute(MARK_ATTR, 1); btn.addEventListener(click, (e) { e.preventDefault(); e.stopPropagation(); const url resolveTargetUrl(img); if (url) { window.open(url, _blank, noopener,noreferrer); } }, false); // 阻止 mousedown 冒泡避免某些頁面用它做跳轉(zhuǎn) btn.addEventListener(mousedown, (e) e.stopPropagation(), false); return btn; }這里加了mousedown的阻止冒泡是踩坑之后補的。有些頁面不用click做跳轉(zhuǎn)而是監(jiān)聽父容器的mousedown或者pointerdown只攔click根本攔不住。把這三個事件都攔掉才算是徹底干凈。window.open的第三個參數(shù)noopener,noreferrer也別省。它是讓新開的頁面拿不到window.opener一是安全二是某些站點檢測到opener存在時會改行為比如強制在當(dāng)前頁跳轉(zhuǎn)。// ---------- 4. 掃描與注入 ---------- const IMG_SELECTORS [ div[data-attrid] img, a[rolelink] img, div[jsname] img, div a img ].join(,); function markHost(container) { if (!container) return null; if (container.hasAttribute(MARK_ATTR) || container.querySelector([${MARK_ATTR}])) { return null; } container.classList.add(HOST_CLASS); return container; } function scan(root) { let imgs; try { imgs root.querySelectorAll(IMG_SELECTORS); } catch (err) { return; } for (const img of imgs) { if (img.naturalWidth img.naturalWidth MIN_SIZE) continue; if (img.naturalHeight img.naturalHeight MIN_SIZE) continue; let host img.closest(a) || img.closest(div); host markHost(host); if (!host) continue; host.setAttribute(MARK_ATTR, 1); host.appendChild(buildButton(img)); } }IMG_SELECTORS里我并列了四條選擇器用逗號拼起來交給querySelectorAll語義上是“滿足任意一條即可”。為什么要冗余這么多條因為搜索頁的結(jié)果容器經(jīng)常改class尤其是帶哈希的那種像classabc123每次構(gòu)建都變但結(jié)構(gòu)性屬性相對穩(wěn)定>// ---------- 5. 監(jiān)聽與節(jié)流 ---------- function scheduleScan() { if (scanTimer) return; scanTimer setTimeout(() { scanTimer null; scan(document); }, SCAN_DELAY); } function boot() { scan(document); const mo new MutationObserver(scheduleScan); mo.observe(document.documentElement, { childList: true, subtree: true }); // 兜底單頁應(yīng)用的路由切換不一定觸發(fā) DOM 變更 window.addEventListener(popstate, scheduleScan, false); document.addEventListener(visibilitychange, () { if (!document.hidden) scheduleScan(); }, false); } if (document.readyState loading) { document.addEventListener(DOMContentLoaded, boot, { once: true }); } else { boot(); }最后那個readyState判斷是保險。雖然run_at: document_idle保證了注入時 DOM 基本就緒但如果哪天你把run_at改成document_start做實驗沒這個判斷就會拿到null。加上它腳本在兩種注入時機下都能正常工作。4.4 inject.css讓按鈕不礙事.ivr-host { position: relative !important; } .ivr-view-btn { position: absolute; right: 8px; bottom: 8px; z-index: 20; padding: 4px 10px; border: none; border-radius: 14px; background: rgba(16, 16, 16, 0.78); color: #fff; font-size: 12px; line-height: 1.5; font-family: inherit; cursor: pointer; opacity: 0; transform: translateY(4px); transition: opacity 0.15s ease, transform 0.15s ease; pointer-events: none; } .ivr-host:hover .ivr-view-btn, .ivr-view-btn:focus-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }設(shè)計上的幾個取舍說明一下。opacity: 0加pointer-events: none的組合是為了讓按鈕默認(rèn)隱形——圖片墻本來就很花每張圖都掛一個深色藥丸會非常吵。只在懸停時顯形視覺上干凈得多。pointer-events: none是配合opacity: 0用的不然按鈕雖然看不見但仍然可以點會莫名其妙觸發(fā)。focus-visible那條是為了鍵盤用戶Tab 到按鈕上時它要顯形否則鍵盤操作完全沒法用。這個細(xì)節(jié)很少有人寫但加上之后整個東西才算完整。4.5 加載與驗證裝的過程分三步熟練的話確實三分鐘打開擴(kuò)展管理頁右上角打開“開發(fā)者模式”開關(guān)。點“加載已解壓的擴(kuò)展程序”選中那個包含manifest.json的文件夾?;氐剿阉黜撍⑿乱淮?。驗證方法很簡單鼠標(biāo)懸停一張縮略圖右下角應(yīng)該浮出一個深色的小按鈕。點它新標(biāo)簽頁打開的就是大圖。如果沒出現(xiàn)按這個順序排查先看擴(kuò)展有沒有加載成功。管理頁里如果顯示紅色錯誤說明manifest.json有語法問題最常見的是多了一個逗號或者少了一個引號。再看matches寫對沒有。在搜索頁按 F12Console 里敲location.href把實際地址和你的模式對一遍。最后看 Console 有沒有報錯。Content Script 的錯誤會打在頁面的 Console 里紅字很顯眼。5. 從“能用”到“順手”的三個增強5.1 地址推導(dǎo)的邊界在哪里upgradeUrl那兩條正則只能覆蓋最常見的情況。真實世界里圖片地址的尺寸參數(shù)五花八門有的用?w200有的用?width200有的用路徑里的-320x480還有的把尺寸編在哈希里這種基本沒救只能老老實實跳中間頁拿原圖。我的建議是不要試圖寫完所有規(guī)則而是先抓高頻的跑一段時間看哪些圖還是拿不到大圖再把對應(yīng)規(guī)則補進(jìn)去。這是典型的“二八開”場景前兩條規(guī)則大概能覆蓋八成圖片剩下兩成補規(guī)則的成本急劇上升收益卻很小。有個例外情況值得單獨說如果原圖本身就比縮略圖小你的放大操作會拿到 404。因為服務(wù)器上根本沒有那個尺寸的文件。所以upgradeUrl返回的地址最好在打開前做一次可用性檢查——但window.open之前做異步檢查會丟失“用戶手勢”上下文導(dǎo)致彈窗被攔。折中方案是不檢查直接開如果 404 了用戶退回原頁面再點一次按鈕手動點“復(fù)制圖片地址”。我在按鈕上加了一個contextmenu監(jiān)聽右鍵按鈕直接復(fù)制地址到剪貼板算是給這種情況留了個后門。5.2 別讓樣式污染頁面.ivr-host那條規(guī)則用了!important這在我的代碼里是唯一一處。原因是搜索結(jié)果頁的樣式表非?!鞍缘馈贝罅渴褂酶邇?yōu)先級的定位規(guī)則不加!important經(jīng)常被覆蓋導(dǎo)致按鈕跑到頁面左上角去。但!important是把雙刃劍——它會讓這條規(guī)則在層疊里幾乎無法被覆蓋。所以我把它限制在最小的范圍只影響position這一個屬性而且只在帶ivr-host類的節(jié)點上生效。ivr-前綴的作用是避免和頁面原有類名撞車這個前綴策略是所有注入式擴(kuò)展都應(yīng)該遵守的基本規(guī)矩。還有一個更隱蔽的污染我最初給按鈕設(shè)的是all: unset想清掉瀏覽器默認(rèn)樣式。結(jié)果按鈕的字體、行高全丟了和頁面風(fēng)格完全不搭。后來改成顯式聲明font-family: inherit和line-height: 1.5讓它繼承頁面的排版風(fēng)格觀感自然得多。5.3 一個順手的鍵盤增強按鈕加好之后我給自己又補了一個功能按Shift 點擊直接復(fù)制大圖地址不打開新標(biāo)簽頁。btn.addEventListener(click, async (e) { e.preventDefault(); e.stopPropagation(); const url resolveTargetUrl(img); if (!url) return; if (e.shiftKey) { try { await navigator.clipboard.writeText(url); btn.textContent 已復(fù)制; setTimeout(() { btn.textContent 查看圖片; }, 1200); } catch (err) { btn.textContent 復(fù)制失敗; } return; } window.open(url, _blank, noopener,noreferrer); });navigator.clipboard.writeText在擴(kuò)展的 Content Script 里是能用的但有兩個前提頁面必須是安全上下文HTTPS且調(diào)用時要有用戶手勢。Shift 點擊恰好滿足這兩個條件所以很穩(wěn)。復(fù)制成功后把按鈕文字改成“已復(fù)制”1.2 秒后改回來這個反饋很輕但很有用——不然你點了之后不知道到底成沒成。6. 排坑實錄那些讓我熬夜的問題6.1 問題速查表這張表是我自己還有幾個同事遇到的問題匯總按“癥狀 → 可能原因 → 處理方式”組織遇到問題可以直接對著查癥狀最可能的原因處理方式按鈕完全不出現(xiàn)matches沒匹配上當(dāng)前地址在 Console 敲location.href比對模式按鈕出現(xiàn)但頁面滾動后新圖沒有沒監(jiān)聽 DOM 變更或監(jiān)聽了但沒節(jié)流檢查MutationObserver是否掛上同一張圖疊了多個按鈕去重判斷寫成了querySelector找自身改用hasAttribute點按鈕后原頁面也跳走了只攔了click沒攔mousedown兩個都要stopPropagation打開的圖很小幾十 KB拿到的是縮略圖地址補SIZE_RULES規(guī)則頁面滾動明顯掉幀掃描太頻繁或全量掃document調(diào)大節(jié)流間隔縮小掃描范圍刷新后擴(kuò)展沒生效改了manifest.json沒重載擴(kuò)展回管理頁點重新加載按鈕位置跑到頁面角落宿主容器沒有position: relative檢查.ivr-host是否生效6.2 三條讓我印象最深的心得第一條先確認(rèn)數(shù)據(jù)在不在再決定要不要寫代碼。我最開始花了大半天時間研究“怎么找替代入口”甚至想過寫個工具去模擬點擊中間頁再解析結(jié)果。后來在開發(fā)者工具里隨手document.querySelectorAll(img).length發(fā)現(xiàn)頁面上躺著好幾十個imgsrc全是真實的大圖地址。那一刻我意識到自己差點走了一條完全錯誤的路線。遇到“功能消失”類問題第一件事永遠(yuǎn)是打開開發(fā)者工具看一眼原始數(shù)據(jù)還在不在這個動作只要十秒鐘卻能省下大半天。第二條不要用class做選擇器的主力。第一版我寫的是.isv-r img和.Wf5APd img這類選擇器當(dāng)天能用。三天后我打開搜索頁按鈕全沒了——因為它把帶哈希的類名重新生成了。后來改成用>