發表文章

目前顯示的是有「js」標籤的文章
圖片
為什麼 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 看到 Permission was denied for this request to access the ... address space 關鍵字,即代表觸發區域網路存取限制。瀏覽器判定來源(如 www.example.com )位於公網,而目標解析後位於私有網路或無法判定的位址空間,直接封鎖。 技術債的償還:API 呼叫的正規做法 遇到此問題時,有些網站管理者會教使用者如何解除封鎖,並叫他們點擊「允許」: 點擊網址左邊的icon。 開啟存取權。 重新整理頁面。 跳出提示視窗時點擊允許。 使用者手動解除封鎖的方法 但從架構安全的角度來看, 前端直接呼叫私有 API 或第三方服務,本質上就是一種「技術債」 ,就算寫在 .env 裡面有心人也有辦法翻出來。API 呼叫的最佳實踐本來就該是 S...
圖片
許多行銷人員都會依賴 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 "...