發表文章

目前顯示的是有「js」標籤的文章
文字排版,甚至是文字與元素的動態互動,常常因繁瑣且頻繁地計算DOM,造成效能不佳。 然而,當Cheng Lou在twitter po出這篇文章時,所有前端人員都震驚了:只需要用數學方法觀察文字大小和字體計算換行符,「不需要去碰DOM的解決方案」,完全避開了Reflow惡夢。 My dear front-end developers (and anyone who’s interested in the future of interfaces): I have crawled through depths of hell to bring you, for the foreseeable years, one of the more important foundational pieces of UI engineering (if not in implementation then certainly at… pic.twitter.com/BKnwCDIp75 — Cheng Lou (@_chenglou) March 28, 2026 你可以查看此篇文的各方高手引用實作,就能看出這是多麼劃時代的解決方案。金字塔、文繞圖(圖片會動)、Bad Apple動畫等文字排版完美解決且非常順暢,在文章中玩Air Hockey也沒有問題。 Cheng Lou也把pretext技術開源放到 github ,你也能親自發揮你的創意體驗一下它的強大。 Riding the wave of the pretext.js without pretext 😅 pic.twitter.com/D8CAAZqcpJ — Felix Martinez (@Sirokos) March 30, 2026 這個影片是有人用pretext實作的衝浪效果。不怕做不到,只怕沒創意靈感。
圖片
為什麼 API 權限全開,還是會封鎖連線? 開發前後端分離架構,或是串接不同環境 API 時,常發生一種情況:後端已設定 Access-Control-Allow-Origin: * (允許所有網域存取),且 Postman 測試正常,但從瀏覽器環境透過 JavaScript 發送請求給內網或私有環境的 API 伺服器時,Chrome 卻強制阻擋連線。 這是 Chrome 實施「 區域網路存取權 」(Local Network Access, LNA)規範所致,目的是為了保護使用者免於跨網站要求偽造 (CSRF) 攻擊。 使用者在初次進入網站時, 若網頁向「本機」網路索取資料時 ,可能會看到這2種提示訊息視窗(也可能不會看到就自動封鎖或允許): 這會帶來什麼影響? 這項安全機制會導致即使後端 API 權限全開,瀏覽器仍攔截請求。具體的錯誤訊息如下: Access to fetch at 'https://api.example.com/apiName' from origin 'https://www.example.com' has been blocked by CORS policy: Permission was denied for this request to access the `unknown` address space. net::ERR_FAILED 有些人一看到「blocked by CORS policy」這幾個字可能會誤以為是CORS的問題,實際上並非如此,而是上面所述的LNA或HTTPS問題。 看到 Permission was denied for this request to access the ... address space 關鍵字,即代表觸發「區域網路存取限制」。瀏覽器判定來源(如 www.example.com )位於公網,而目標解析後位於私有網路或無法判定的位址空間,直接封鎖。 技術債的償還:API 呼叫的正規做法 遇到此問題時,有些網站管理者會教使用者如何解除封鎖,並叫他們點擊「允許」: 點擊網址左邊的icon。 開啟存取權。 重新整理頁面。 跳出提示視窗時點擊允許。 使用者手動解除封...
圖片
許多行銷人員都會依賴 GTM 插入 Facebook Pixel 的程式碼,但你是否有想過,當一個網站上出現多個 Pixel ID 的時候,一直 init(重新宣告)Pixel ID、直接觸發事件、整段程式碼貼上,會重複追蹤事件(一直Add to Cart)、覆蓋變數影響其它 Pixel ID……等問題,最終除了讓網頁效能降低外,收集到的數據也不準確。 曾經經手過一個充滿好幾個 meta pixel 和 bing uet 的網站,單純把 GTM 拿掉,效能就可以提升超過40分。 自訂事件一般寫法 許多行銷人員會在 GTM 中使用「自訂 HTML」直接貼上像這樣的程式碼來達成他想要的自訂事件: <script> !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)}; if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0]; s.parentNode.insertBefore(t,s)}(window, document,'script', 'https://connect.facebook.net/en_US/fbevents.js'); fbq('init', '1234567890'); fbq('trackCustom', '自訂事件名稱', { '額外要傳送的數據Object': 999 }); </script> 但他完全沒有考慮可能會發生的問題: https://connect.facebook.net/en_US/fbevents.js 是否已經載入過了。( 這個其實官方程式碼已經處理: if(f.fbq)return; ) Pixel ID 是否已經宣告...
圖片
在Chromium的環境中,可以直接寫JavaScript導入生成摘要的AI(Gemini Nano): Summarizer API 。 這個 API 可以幫助生成文章或對話的重點或摘要、前導訊息、生成標題……等用途。 官方明確提出會在 Chrome 138 的版本導入此 API,但其實在很早之前就鼓勵開發者試著用Gemini Nano開發擴充功能。 在開始使用之前,可以順便閱讀 The People + AI Guidebook 與 Ameba 平台實際運用的情境 。 開始使用 查看是否支援 Summarizer API: if ('Summarizer' in self) { // The Summarizer API is supported. } Summarizer.availability() 可以判斷模型是否已準備好:它會回傳 "unavailable"(不支援)、"downloadable"(尚未下載)、"downloading"(下載中)、"available"(支援且可執行)。 而如果是 downloadable 或 downloading,可以在 Summarizer.create() 中使用 downloadprogress 查看下載進度: const summarizer = await Summarizer.create({ monitor(m) { m.addEventListener('downloadprogress', (e) => { console.log(`Downloaded ${e.loaded * 100}%`); }); } }); 呼叫 function 一般摘要: const theArticle = document.querySelector('#article').innerHTML; const summary = await summarizer.summarize(theArticle, { context: 'This article is about vtuber of ho...
圖片
準備好你的擴充功能檔案。 註冊開發人員帳戶。 上傳擴充功能檔案。 填寫相關資訊。 提交審核。 前陣子 manifest_version 從 2 強制升級成 3,我認為是一個很好的入坑時機點,直接學新的版本就好了,所以試著上傳小工具給官方,也當作一次學習。 1. 準備好程式碼與各項設定 chrome 擴充功能的設定都會寫在 manifest.json 裡面,包含擴充功能的名稱、簡單描述、各種檔案的路徑。 { "manifest_version": 3, "name": "__MSG_extensionName__", "version": "1.0.0", "description": "__MSG_extensionDescription__", "default_locale": "en", "permissions": [ "storage", "activeTab", "tabs" ], "host_permissions": [ "<all_urls>" ], "background": { "service_worker": "background.js" }, "action": { "default_popup": "popup.html", "default_icon": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon12...
是否有在第一次串接 Firebase 的database時,明明照著步驟做卻一直失敗的經驗?這裡提供簡易的範例。 這裡的寫法是在 script module type 引入 CDN。並且使用的是 Realtime Database Realtime Database 這個資料庫的格式就跟JSON一樣,你可以在存進資料時設定欄位路徑,像是 all/member 。 資料上傳 除了要注意Firebase免費的Spark方案的每日流量和同時訪問數的限制之外,還要注意write和read的權限設定。 // Import the functions you need from the SDKs import { initializeApp } from "https://www.gstatic.com/firebasejs/11.1.0/firebase-app.js"; import { getDatabase, ref, push, set } from "https://www.gstatic.com/firebasejs/11.1.0/firebase-database.js"; // Your web app's Firebase configuration const firebaseConfig = { apiKey: "*********", authDomain: "專案id.firebaseapp.com", projectId: "專案id", storageBucket: "專案id.firebasestorage.app", messagingSenderId: "123456789", appId: "123456789:web:f96e474ce78f56f78a509f" }; // Initialize Firebase const app = initializeApp(firebaseConfig); const database = getDatabase(app); // Function to write data ...
圖片
matchMedia 的易用性 JavaScript 語法中的 matchMedia 瀏覽器相容性很高,IE10 以上都支援,所以基本上不用擔心什麼 polyfill。 而 matchMedia 的用法就像寫 CSS 的 media query 或 HTML 的 picture 一樣: var xsMQ = window.matchMedia('(min-width: 480px)'); var mdMQ = window.matchMedia('(min-width: 992px)'); 變數 xsMQ 和 mdMQ 是 boolean 值 ,因此,可以用判斷式來做想做的事。 matchMedia 與 change 事件結合 我們都知道 resize 有效能問題,需要用 debounce 或 throttle 解決,而如果把上述的 matchMedia 判斷式寫成 function,更可以用 change 事件 做到 resize 可以做的事! function mediaQueryStep(){ if(xsMQ.matches){ if(mdMQ.matches){ // big size:do something... }else{ // between big and small size:do something... } }else{ // small size:do something... } } xsMQ.addEventListener('change', mediaQueryStep); mdMQ.addEventListener('change', mediaQueryStep); mediaQueryStep(); 所以,在一些media query 沒有每改變 1 pixel 就會觸發某事件的情況下,matchMedia 也不失為一個好選擇。
圖片
之前在寫JavaScript的時候,不知為何很奇耙地搞出一個怪異的選取目標,並偶然發現它會因點擊目標父層的層數而觸發相對應的次數。 $(document).on('click',':not(#nav *)',function(e){ console.log('trigger'); }); 為了把這問題解決用很多種方法嘗試,結果只要多一行就解決了… $(document).on('click',':not(#nav *)',function(e){ if (e.target !== this) return; console.log('trigger'); }); click 和 touchend 一起用 另外一個寫不好的情況是 click 和 touch 事件一起用,造成兩個事件一起觸發。 $(document).on('click touchend','.toggle-button',function(e){ e.preventDefault(); $('#element').toggleClass('--active'); }); 解決的方法是增加一個 flag: var triggerFlag = false; var triggerThreshold = 200; $(document).on('click touchend','.toggle-button',function(e){ e.preventDefault(); if (!triggerFlag) { triggerFlag = true; setTimeout(()=>{ triggerFlag = false; }, triggerThreshold); $('#element').toggleClass('--active'); } }); 類似的頻繁觸發 另外在 scroll 和 resize 時,常常會因為頻繁觸發而導致效能爆...
圖片
GTM(Google Tag Manager;網頁代碼管理工具)最大的好處在於讓頁面較整潔、許多系統已經寫好的事件與變數可以直接拿來用,非常方便。但現實往往沒那麼簡單,通常都會遇到一堆 ajax、history 或 cache 要處理,這時候就要請到自訂事件出馬了。 若只用 GA,沒有使用 GTM 的話,自訂事件要這樣寫: gtag('event', ..., { 'event_category' : ..., 'event_label' : ... }); 那如果用 GTM 的話該如何操作呢? 將資料 push 進 dataLayer 首先我們要知道什麼是 dataLayer:一個 GTM 內部的全域變數,它能動態存取網頁的使用者行為數據。 更進一步探索它的   JSON   架構,可以在網頁上使用開發者工具(F12)的 console 欄,輸入 dataLayer,其實跟它的名字一樣就是層層分明的資料層 (Data Layer)。 在 F12 中查看 dataLayer 你會發現 GTM 測試欄位左邊有幾個事件,dataLayer 裡面就有幾個 object。 再把 object 點開(這裡以 Window Loaded 為例),會有 event 和 GTM 給的事件 id,而這個事件(gtm_load)其實就是「觸發條件」裡的「視窗已載入」。 因此,我們可以依樣畫葫蘆用 dataLayer.push 自己寫一段觸發條件把資料送進 dataLayer ,就可以在 GTM 裡面操作了: 開始設定自訂事件 先宣告 dataLayer,避免發生 undefined error 再設定你要 push 進去的數據 這裡我們用「 nav a 點擊偵測」和 ajax 事件舉例(jQuery): var dataLayer = window.dataLayer || []; // example 1: detect nav a tag click $('nav a').click(function(){ var $text = $(this).text(); dataLayer.push({ 'event'...
常常遇到上司想在網頁上方便觀看一些搜集而來的數據,之後又說想要把網頁上的表格匯出成 csv 檔案,方便他們用 excel 計算一些東西。這時候該怎麼辦才好? 以下為使用 jQuery 的解決方案 function exportTableToCSV($table, filename) { var $rows = $table.find('tr:has(td)'), // Temporary delimiter characters unlikely to be typed by keyboard // This is to avoid accidentally splitting the actual contents tmpColDelim = String.fromCharCode(11), // vertical tab character tmpRowDelim = String.fromCharCode(0), // null character // actual delimiter characters for CSV format colDelim = '","', rowDelim = '"\r\n"', // Grab text from table into CSV formatted string csv = '"' + $rows.map(function(i, row) { var $row = $(row), $cols = $row.find('td'); return $cols.map(function(j, col) { var $col = $(col), text = $col.text(); if(/^(0)([0-9]){9}/.test(text)){ return '=""'+text...
HTML如下: <section class= "box" >      <div class= "boALL" > </div> <!-- all borders --> <div class= "boTop" ></div> <!-- 上 --> <div class= "boRight" ></div> <!-- 右 --> <div class= "boBottom" ></div> <!-- 下 --> <div class= "boLeft" ></div> <!-- 左 -->      <a href= "javascript: void(0)" class= "txt" > HOVER HERE </a> </section> css setting: .box {    display: inline-block ;    width: 200px ;    height: 60px ; background-color: #999 ;    position: absolute ;    left: 0 ;    right: 0 ;    top: 0 ;    bottom: 0 ;    margin: auto ; text-align: center ; line-height: 60px ;    transition: background-color .7s ; } .box:hover { background-color: #...
HTML架構如下(html structure): <div class=" clock ">     <div class=" centerDot "></div>     <div class=" secHand "></div>     <div class=" minuHand "></div>     <div class=" hourHand "></div>     <div class=" number1 number ">1</div>     <div class=" number2 number ">2</div>     <div class=" number3 number ">3</div>     <div class=" number4 number ">4</div>     <div class=" number5 number ">5</div>     <div class=" number6 number ">6</div>     <div class=" number7 number ">7</div>     <div class=" number8 number ">8</div>     <div class=" number9 number ">9</div>     <div class=" number10 number ">10</div>     <div class=" number11 number "...