在網站上載入圖片是需要消耗時間的,因為圖片的傳遞和文字比起來實在要大太多。因為在載入網站時常常會看到一個個的空窗,然後就突然填充了圖片。如果想要令到整件事件可以順暢一點,我們就需要用到 Placeholder 和 Shimmer 技巧了。 ### 什麼是 Image placeholder Image placeholder 是指為圖片預留空的一個填充物,一般在整網站時為了排版的可預測性 (predictability),我們都會加入 `width` / `height` / `min-widht` / `min-height` / `max-width` / `max-height` 設定圖片的大細,確保在載入後都在可控制的範圍來,使排版不會出現不能預期的效果。 這個時候我們可以用一個 `div` 來作為圖片在載入時的填補。可以看看下面代碼 : ```css /* 設定為 relative 及背景色彩 */ div.image { position: relative; background-color: #DDDDDD; } ``` ```html <!-- 建立一個 div 作為 placeholder 並設定 width / height --> <div class='image' style='width: 300px; height: 100px;'></div> ``` 這樣就可以得一個灰色的 DIV 作為圖片載入的 placeholder 了。 ### 設定 Shimmer 效果 Shimmer 就是在灰色的 div 上加入白色的閃光,令使用者知道這是一個正在載入中的提示。 我們可以通過使用 CSS 的 animation 去達成。 ```css /* 圖片的 placeholder */ div.image { position: relative; background: linear-gradient(-45deg, #eee 40%, #fafafa 50%, #eee 60%); background-size: 300%; background-position-x: 100%; animation: shimmer 1s infinite linear; } /* 圖片的內容 */ div.image .image-src { position: absolute; inset: 0px; display: block; } /* 動畫 */ @keyframes shimmer { to { background-position-x: 0% } } ``` 然後就可以在 div 但放入你想要載入的東西了。 ```html <!-- 建立一個 div 作為 placeholder 並設定 width / height --> <div class='image' style='width: 300px; height: 100px;'> <!-- 加入圖片的 element --> <div class='image-src' style='background: url(https://source.unsplash.com/1024x600) center center / cover no-repeat;'></div> </div> ```
今日講下點樣可以用 CSS 來選取你想要的 DOM 元件。 ### TLDR 直接去下面兩個網站睇用法 :first-child 用法 : https://developer.mozilla.org/en-US/docs/Web/CSS/:first-child :last-child 用法 : https://developer.mozilla.org/en-US/docs/Web/CSS/:last-child ### CSS Selector 透過 CSS 的特定語法來選取 DOM 元件,名稱叫 Selector。 Selector 是一串文字用來指定某一個 / 一些 DOM 物件,例如我們有以下一個 html : ```html <div> <table> <tbody> <tr> <td> Hello world A </td> <td> Hello world B </td> </tr> <tr> <td> Hello world C </td> <td> Hello world D </td> </tr> <tr> <td> Hello world F </td> <td> Hello world G </td> </tr> </tbody> </table> </div> ``` 如果你想要取出 Hello world A 的 `td`,就可以用以下的 Selector : ```css // 取出 td div table tbody tr:first-child td:first-child { // 變更背景色彩 background-color: rgb(0, 0, 0); } ``` 如果你想要取出 Hello world F 的 `td`,就可以用以下的 Selector : ```css // 取出 td div table tbody tr:last-child td:first-child { // 變更背景色彩 background-color: rgb(0, 0, 0); } ``` 如果你想要取出 Hello world G 的 `td`,就可以用以下的 Selector : ```css // 取出 td div table tbody tr:last-child td:last-child { // 變更背景色彩 background-color: rgb(0, 0, 0); } ``` 很方便的呢 !
IIFE 的全寫是 Immediately Invoked Function Expression,這是一個當你建立後就會立即執行的 function。通常都會是一次情而且是 anonymous 的。 ### 例子 IIFE 會是下面這樣 : ```js (() => console.log(‘Hello world’))(); ``` 這段 code 執行時,會立即畫出 Hello world 到你的 console 上。 ### 使用原因 使用 IIFE 的原因是為了保護變數的可用性。IIFE 中定義的變數不能從外部存取。這是編寫方法可以保護程式碼並減少出錯的方法。 ### 配入 function 的等性使用 另外你可能需要留位一個變數旳值,用來供日後存取用 (即類似於 factory pattern),可以看看以下例子 : ```js // 進行 10 for( let i=0; i<10; ++i ) { // 使用 setTimeout 來推遲執行 setTimeout(() =>{ // 記錄到 console console.log(i); }, 1000); } ``` 以上的代碼你可能會想看到 : 0,1,2,3,4,5,6,7,8,9 但是實際上你應該會看到是 : 9,9,9,9,9,9,9,9,9,9 這是因為經過 setTimeout 的 delay 後,`i` 的值已經由 0 數去到 9 了,所以當第一個 `setTimeout` 進行 `console.log(i)` 是,只會提取到 `i` 是 9 的結果。 `setTimeout` 只是一個例子,同樣的情況也會發生到 event handler 上,例如 : ```js // 取出所有 .button 的 div const elements = document.querySelectorAll('div.button'); // 每一個 element 也做一次 for( let i=0; i<elements.length; ++i ) { // 取出 element const element = elements[i]; // 加入 ‵click` 事件 element.addEventListener('click', function(evt) { // 記錄到 console console.log(i); }) } ``` 你按下任可的 `div.button` element 時,都只會在 console 出現 9。 #### 解決方法 你可以透過使用 IIFE 和變數傳入 function 的特性來避免這個事情。 ```js // 取出所有 .button 的 div const elements = document.querySelectorAll('div.button'); // 每一個 element 也做一次 for( let i=0; i<elements.length; ++i ) { // 取出 element const element = elements[i]; // IIFE (i2 => { // 加入 ‵click` 事件 element.addEventListener('click', function(evt) { // 記錄到 console console.log(i2); }) })(i); } ``` 通過把變數 `i` 傳入到 function 內,因為數值及文字是 [pass by value](https://www.google.com/search?q=js+pass+by+value) 的關係,所以在 function 內收到的變數 `i2` 是一個值,而不是 `i` 的參照 (reference),所以在之後應用 `i2` 時也只會得到當時 `i2` 值的結果。
在 Web Development 上我們常常會儲存小量資料在 Client Side 上,有可能是使用者的使用習慣,或者對上一次動作遇原資料等等。 雖然 Browser 自帶的 `localstorage` API 已經好方便使用,因為再提升可用性,現在就介紹一個 library 用來管理 `localstorage` 的。 ### store2 Github : https://github.com/nbubna/store 佢提供左簡單的方法來存取 `localstorage`,可以另你更加專心你 Business logic 的開法。 #### 簡單應用方法 我們可以通過 `store()` 來進行簡單的基本的應用 ```js // 設定 key value store(key, data); // 使用 key 讀取 value store(key); // 運用 transaction 來寫入 value 到 key store(key, fn[, alt]); // 一次過寫入多個 key value store({key: data, key2: data2}); // 讀所 store 內的所有 key value store(); // 一個一個咁讀出 key 及 value, 可以通過 `return false` 來停止 store((key, data) => { }); // 清除所有的資料 store(false); ``` #### 進階應用 如果要進行更多的功能,可以通過以下方式來達成。 ```js // 相等於 store(key, data) store.set(key, data[, overwrite]); // 相等於 store({key: data, key2: data}) store.setAll(data[, overwrite]); // 相等於 store(key) store.get(key[, alt]); // 相等於 store() store.getAll([fillObj]); // 相等於 store(key, fn[, alt]) store.transact(key, fn[, alt]); // 相等於 store(false) store.clear(); // 檢查 store 內有沒有提升的 key 值 store.has(key); // 清除 key 的值及回傳原有資料,如果沒有資料則回傳 alt store.remove(key[, alt]); // 相等於 store(fn) store.each(fn[, fill]); // 加入資料到 key store.add(key, data[, replacer]); // 回傳所有的 keys store.keys([fillList]); // 取得 key 的 lenght store.size(); // 清除所有的 key store.clearAll(); ```
在取得 Dom 時我們常常會用到 `document.querySelector()` 用來取得向下的子元件。有時候我們也會可能需要向上取得母系元件,這時候我們就需要用到 `closest()` 了。 ### 用法 在使用 `closest()` 時我們需要由一個 Dom 出發,例如有以下這個 html : ```html <div id='div1'> <div id='div2'> <div id='div3'></div> <div id='div4'></div> </div> <div id='div5'></div> </div> ``` 我們先用 `querySelector()` 取得 `#div3` 這個 Element 先 : ```js const div3 = document.querySelector('#div3'); ``` 如果需要由 `#div3` 出發,使用 `closest()` 取得 `#div1` 的話,可以這樣 : ```js const div3 = document.querySelector('#div3'); const div1 = div3.closest('#div1'); ``` 那麼問大家一個問題,如果使用以下的語法會取得那一個 `div` 呢? ```js // 問題 1 div3.closest('div'); // 問題 2 div3.closest('div div'); ``` 答案時間,問題 1 的答案是 `#div3` 自已本身,而問題 2 的答案也是 `#div3` 自已本身 ! 因為 `closest()` 在搜索時也會包含自己在內,這點一定要注意。 ### 使用 `closest()` 但是排除自己 如果我們只想要向上取得 Element 就先需要排除自身 Element 在外,可以通過以下方法達成 : ```js // 取得 div3 const div3 = document.querySelector('#div3'); // 通過使用 parentNode 來向上取得 parent,再在 parent 上使用 closest() 就可以了 const div2 = div3.parentNode.closest('div'); ```
以下要講下魔改 BlogArchive Widget,今次應該真係魔改,暫時在網站上找不到任何相關的文章和 API 講有關事情。 話說係 BlogArchive Widget 入面如果用 `HIERARCHY` 模式的話,可以得到一個 `data` 參數來畫出你需要的畫面。 因為是 `HIERARCHY` 的關係,所以 `data` 變數也是樹狀型態的。 依照[官方 Documentation](https://support.google.com/blogger/answer/47270) 所講,你可以得到以下的資料: - title: The title of the widget. - style: One of 'MENU', 'FLAT', or 'HIERARCHY'. - data: A list of each archive unit, each of which contains: - name: The name of this archive interval, e.g. "August 2006." - url: The link to the page containing posts from this interval. - post-count: How many posts there are in this interval. ### 限制 但是如果你玩過呢個 Widget 之後,發覺佢只會提供現時所在位置 Post 的相關月份內容,而不是提全部的內容給你去畫出 UI。 例如你現在是正在看 2023 年 6 月的文章,Widget 只會給你以下資料: - [2023 年](https://example.blogspot.com/2023/) - [7 月](https://example.blogspot.com/2023/07/) - [6 月](https://example.blogspot.com/2023/06/) - [你的文章 1](https://example.blogspot.com/2023/06/example-post1.html) - [你的文章 2](https://example.blogspot.com/2023/06/example-post2.html) - [4 月](https://example.blogspot.com/2023/04/) - [1 月](https://example.blogspot.com/2023/01/) - [2022 年](https://example.blogspot.com/2022/) - [5 月](https://example.blogspot.com/2022/05/) - [4 月](https://example.blogspot.com/2022/04/) 你只能夠按下 2023 年 7 月的連結,是不能夠取得 2023 年 7 月的文章列表。 ### 開始 Reverse Engineering 如果大家有玩過 Blogger 預設的 Theme,有可能會發現有一些好舊版的 Theme (千禧年主題)入面個 Blog Archive 是可以做到 Ajax 載入不同月份的文章列表。 呢個時間你可以打開 Chrome development tool 來看看到底叫了一個什麼的 Ajax 出去。你應該會看到類似下以的 Url: ```js https://example.blogspot.com/?action=getTitles&widgetId=BlogArchive1&widgetType=BlogArchive&responseType=js&path=https://example.blogspot.com/2023/06/&xssi_token=TOKEN ``` 然後你可以試下去不同的頁面去開一開個 Blog Archive 上面個箭咀去觸發個 Ajax 載入: ```js https://example.blogspot.com/2023/02/?action=getTitles&widgetId=BlogArchive1&widgetType=BlogArchive&responseType=js&path=https://example.blogspot.com/2023/06/&xssi_token=TOKEN ``` 會發現到無論你在任何的 URL 上 Request 去 server 都好,只要你 Url 上 Query 上以下的參數就可以 Call `text/javascript` 的 Response 回來。 - action 暫時只發現 'getTitles' 這個值能用 - widgetId 大約是填返你 BlogArchive 個 id,試過填第二啲野會出 HTTP 400 Error - widgetType 暫時只發現 'BlogArchive' 這個值能用 - responseType 暫時只發現 'js' 這個值能用 - path 這個必需要填對才能通過,如果你要取得 2023 年 6 月的文章列表,就必需要填入 'https://example.blogspot.com/2023/06/' 這個值,填錯出會 Error - xssi_token 可以用 JS 在 runtime 時找到 xssi token ### 如何找到 XSSI Token 你可以在你的 Blogger 網站上用 Browser 按 `檢視源始碼` 來看看由 Blogger Server 最終 Generate 出來的 HTML 到底是怎樣。 我們先 Scroll 到最底,會發現到類似的 JS Code : ```js window['__wavt'] = 'ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD'; ``` 就是這個 `window['__wavt']` 變數裝住的,就是你需要的 XSSI Token。你可以在 Event `DOMContentLoaded` 後任何時間取出來使用。 ```js // DOMContentLoaded 事件處理 document.addEventListener('DOMContentLoaded', function() { // 儲起 XSSI TOKEN 備用 const XSSI_TOKEN = window['__wavt']; }); ``` ### 開始 Request 有齊上面資料後,就可以用 `axios` 出個 GET Request 比伺服器。 ```js // prepare url const url = 'https://example.blogspot.com/?action=getTitles&widgetId=BlogArchive1&widgetType=BlogArchive&responseType=js&path=https://example.blogspot.com/2023/06/&xssi_token=TOKEN'; // axios get request and extract 'data' const { data } = axios.get(url); ``` 如果成功沒有出 Error 的話,大約會得到以下的 Response : ```js try { _WidgetManager._HandleControllerResult('BlogArchive1', 'getTitles',{'path': 'https://example.blogspot.com/2023/06/', 'posts': [{'title': 'Your post title', 'url': 'https://example.blogspot.com/2023/06/your-post.html'}]}); } catch (e) { if (typeof log != 'undefined') { log('HandleControllerResult failed: ' + e); } } ``` 但是問題來了,因為取得的不是 Json 格式,不能夠直接使用,必需要使用神器 `eval()` 來加工一下。 ### JsonP 還記得先前有文章講解過 JsonP 嗎? 如果未睇過可以去睇下。 我們先整個 random function attach 去 window object 度先 : ```js // 整條 Random String 作為 Function 名 const fncName = 'fnc_' + Math.random().toString(16).slice(2, 12); // 用上面個 Random String 作為 window 的 Property, 指向一個 Function window[fncName] = (widgetId, action, data) => { // log 去 console 度 console.log(data); // 用完要 delete 返 (一次性 function) delete window[fncName]; }; ``` 然後然要搞搞個 Response 令到佢 `eval()` 時改為執後你個自訂 function。 ```js // 將 '_WidgetManager._HandleControllerResult' 改為你自訂的 function 名稱, 即係上面個 Random function 名 const jsCode = data.replace(/_WidgetManager\._HandleControllerResult/g, fncName); // 執行 eval eval(jsCode); ``` 如果成功的話,就可以在 development console 上看到 print 左條 json 出來。 ```js {'path': 'https://example.blogspot.com/2023/06/', 'posts': [{'title': 'Your post title', 'url': 'https://example.blogspot.com/2023/06/your-post.html'} ``` 有左呢條 json 的資料,就可以用佢來更新返相應的 UI 位置。 搞掂 ! 食碗麵 ! ### 總結 呢個係魔改,不知道某一日會唔會突然用唔到,不過機會都好細,因為已經有成千上萬個 Blog 都用緊,所以不會輕易就會出改動。 同埋可能有更多更多的魔改有待發掘 !! 等大家繼續去發現 !!
到現在先發現原來 Google 係出過呢一種公開的 REST API 公開比人使用過。 ### GData API Directory Documentation : https://developers.google.com/gdata/docs/directory 之前幾個講 Blogger Live Search 的文章其實就是在用 GData API 去完成,所有的 URL Params 都有詳細的說明。 不過現在已經 Fadeout 左一大部份了,因為公開的 API 好難管理,現在大多都搬到 Cloud Platform 最少也要一條 API Key 才能使用。 這樣也比較安全,管理起來也比較容易。只要把 API Key revoke 就不用再回傳 API 了,能有部減少資源運用或者誤殺 IP 的情況。 ### Blogger API v3 這個大約是十多年前的產物,是次搬 Blog 主要也是靠它來完成。 Documentation : https://developers.google.com/blogger/docs/3.0/getting_started 當中有大量 Endpoint 可以使用,如果只在 Client Side 使用者只要到 [Cloud Platform](https://cloud.google.com/) 開個 Project 申請一條 API Key 就可以使用了。 如果要是 `Create`, `Delete`, `Put`, `Patch` 操作就需要 OAuth 取得 Token 後,使用 Bearer token 方法來 Call API 才能做用。 好像是要實作 Search 功能的話,可以使用以下呢個 API 就行了: ```js GET https://www.googleapis.com/blogger/v3/blogs/YOUR-BLOG-ID/posts/search?q=KEYWORD&key=YOUR-API-KEY ``` 如果成功的話,就會收到 HTTP 200 及以下的 Response body: ```json { "kind": "blogger#postList", "nextPageToken": "CgkIChiAj86CpB8QzJTEAQ", "items": [ { "kind": "blogger#post", "id": "1387873546480002228", "blog": { "id": "3213900" }, "published": "2012-03-23T01:58:00-07:00", "updated": "2012-03-23T01:58:12-07:00", "url": "http://code.blogger.com/2012/03/blogger-documentation-has-moved-to.html", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/3213900/posts/1387873546480002228", "title": "Blogger Documentation has moved to developers.google.com", "content": "content elided for readability", "author": { "id": "16258312240222542576", "displayName": "Brett Morgan", "url": "http://www.blogger.com/profile/16258312240222542576", "image": { "url": "https://resources.blogblog.com/img/b16-rounded.gif" } }, "replies": { "totalItems": "0", "selfLink": "https://www.googleapis.com/blogger/v3/blogs/3213900/posts/1387873546480002228/comments" } }, ... ] } ``` ### 點樣 Call? 用 Axios 啦! Git hub : https://github.com/axios/axios 有需要的話再寫其他文章講解。
今時今日 QR Code 的用途很多,而很多時我們都要 Program 去 Generate 一條 Token 比個使用者去通過某啲場境。 現在就講下點樣係 Javascript 度 Generate QR Code。 ### 搵 Library 已經有大神整好左呢啲惠及人民既野,咩都唔洗講去一去呢度先。 直接去呢度 : https://github.com/davidshimjs/qrcodejs 佢係可以係 Browser 上面 Generate QR Code 的 Library,原理係通過 `canvas` 畫出 QR Code 的樣子,再取出 data url 放到 `img` 上達成。 ### 使用方法 可以去佢個 Git hub clone 落來用又得 : Git hub 即係呢個 : https://github.com/davidshimjs/qrcodejs 可以去 CDN 拎來直接用都得 : CDN 網站 : https://cdnjs.com/libraries/qrcodejs #### 簡單使用方法 簡單用法: ```html <!-- 需要你留返個 DIV 比佢用來畫出個 QR Code --> <div id='qrcode'></div> <script type='text/javascript'> // 傳個 div 的 element 入去就可以了 new QRCode(document.getElementById('qrcode'), 'https://github.com/davidshimjs/qrcodejs'); </script> ``` #### 更多的設定 簡單用法: ```html <!-- 需要你留返個 DIV 比佢用來畫出個 QR Code --> <div id='qrcode'></div> <script type='text/javascript'> // 都係 new 一個 QRCode 實體出來 const qrcode = new QRCode(document.getElementById('qrcode'), { // 設定文字 text: 'https://github.com/davidshimjs/qrcodejs', // 寬度 width: 128, // 長度 height: 128, // 深色部份 colorDark : '#000000', // 淺色部份 colorLight : '#ffffff', // 修正度 correctLevel : QRCode.CorrectLevel.H, }); </script> ``` 如果之後要刷新 (refresh) 個 QR Code 時,可以用以下方法就得。 ```js // 清除原本的 Code qrcode.clear(); // 重新畫另一個文字 qrcode.makeCode('https://cdnjs.com/libraries/qrcodejs'); ```
之前個 Post 話有找條 Link 可以用來做 Ajax Search,今日有時間就做好左了。 呢個就係條 Link 個樣 : [/feeds/posts/summary?callback=callcall&max-results=50&q=react&start-index=1&alt=json&orderby=updated](/feeds/posts/summary?callback=callcall&max-results=50&q=react&start-index=1&alt=json&orderby=updated) 原本佢係用來做 RSS 的 (我估),不過佢有支援可以用 Json 輸出,除此之外仲有得 Support JsonP 添。 ### 參數 (估估下) - JsonP 係上面條 URL 度有個 `callback` 的 params,只要你比個名佢,佢就會以 JsonP 的方適去 Response 返來。 - Json 係上面條 URL 度有個 `alt` 的 params,填個 'json' 比佢,就會以 json 的方式 Response 返來。 - Query 係上面條 URL 度有個 `q` 的 params,填你想要搜索的關鍵字比佢,就可以搵返相應的 Result 返來。 ### 實作時遇到的事情 記錄一下在實作時遇到的事情 #### Custom Event 可以為 Dom element 加入 / 觸發自訂的事件。 ```js // select a single dom element const element = document.querySelector('#mydiv'); // your custom event handler element.addEventListener('yourcustomevent', function(evt) { console.log('hi, this is your custom event'); }); // prepare event object const evt = new Event('yourcustomevent'); // dispatch the event. element.dispatchEvent(evt); ``` #### 取得 Dom element (static position) 的 position (relative to window) 如果淨係用 `getBoundingClientRect()` 只可以取得未加入 window.scroll 的 (X,Y) 值,所以實際用起來時一定要加返 (window.scrollX, window.scrollY) 的值才能使用。 ```js // select a single dom element const element = document.querySelector('#mydiv'); // get position element.getBoundingClientRect().top + window.scrollY; ``` #### text-overflow: ellipsis; 用唔到 解決 : https://stackoverflow.com/questions/17779293/css-text-overflow-ellipsis-not-working 就係要必需設定對上一個 block element 的以下 CSS: ```css div.text-overflow { overflow: hidden; white-space: nowrap; } ``` #### encodeURIComponent 記得由 User input 的資料要放入去 URL 時,都一定要做好 `encodeURIComponent()` 先得。 ```js // 由使用者自己輸入 const userInputValue = 'hello world'; // 必定要做好 encodeURIComponent 才能放心 const url = `https://example.com/api/v1/users?name=${encodeURIComponent(userInputValue)}`; ``` #### 記住用 : DOMContentLoaded 呢度 : https://stackoverflow.com/questions/9899372/vanilla-javascript-equivalent-of-jquerys-ready-how-to-call-a-function-whe 如果想安全啲,記得要係呢個 event 入面先好操作 Dom elements。 以前 IE6 年代件用 `window.onload = function() { ... }`; 到到 jQuery 年度用 `$(function() { ... })`; 到翻 Vanilla Javascript 返璞歸真,用下面呢個例子 : ```html // fire when dom content loaded document.addEventListener('DOMContentLoaded', function(evt) { // Your code to run since DOM is loaded and ready }); ``` ### 總結 唔寫低真係會唔記得,上面操作做過無數次,不過每次都係要 Search 下 Google 有無更好的做法。 而都係必需既,可能過幾篇文章又會再寫過呢啲一樣既野。
JsonP 其實即係用 `script` Tag 來載入第三方 (不同 Domain) 的 javascript 檔案來取得資料,好處就係可以通過 Browser 的 CORS 的封鎖。 原理係通過由載入的 Javascript 檔案內容來直接 Call 你預先設定好的 Function。因為用 `script` Tag 載入的 Javascript 會直接執行。 ### 例子 你有一個 Html ```html <!-- 預備工作 --> <script type='text/javascript'> // 預先定義一個 function 來處理收到的資料 const myfnc = data => console.log(data); </script> <!-- 載入第三方 JS --> <script type='text/javascript' src='https://example.com/json.php?callback=myfnc'></script> ``` 而 Server 上有這個 PHP 檔案來 Handle 你個 Request ```php <?php // get callback name from url params $fncName = $_GET['callback']; // data $data = ["foo" => "bar", "apple" => "pen"]; // set javascript response header header('Content-Type: text/javascript; charset=utf-8'); // print result echo $fncName . "(" . json_encode($data) . ")"; ?> ``` 當你去 Request 呢個 PHP 時,大約就會得到以下的 Response body ```js myfnc({"foo":"bar","apple":"pen"}) ``` 咁當第三方 JS 載入完成後,就會立即自動 call 埋你預先寫好的 `myfnc` function 了。 ### 實際應用 當然實際上應用唔會好似上面咁揼死個 `script` tag 去載入,而一定係用 js 去插入一個 `script` tag 先有意義。 ```js // 設定一個 function function requestData() { // 開一個 script tag const s = document.createElement('script'); // 設定好路徑 s.src = 'https://example.com/json.php?callback=myfnc'; // 加入到 body 中 document.body.appendChild(s); } ``` 這樣就可以了。
一連都整左幾日時間,不過其實整來都係自 high 多,沒有什麼商業價值。 之前其實都有整過幾次,但係每次都覺得好複雜同埋做唔到自己想要的東西,所以之後都係搬左去其他唔同既地方。 不過每次搬定之前,都會來到 Blogger 呢度做中轉站,可能因為唔洗錢的關係掛。 印像中係咁: Blogger > PHP (Codeigniter) > Blogger > PHP (Laravel) > Blogger > Node (Koa) + React > Blogger (現在) 始終維護少一樣野就已經真係差好遠,人大左無咩時間。同埋認真研究左 Blogger 之後,發覺其實設計得好好,有好多你想要的東西其實都已經可以用現成的 `tag` 去完成到。 如果日後有時間再詳細寫一篇有關 Blogger template 的 customization guide 分享下今次的經驗。 現在就記錄返少少有關 Blogger template 的主要要素。 ### Data Tags 就是用來提取資料的標籤,全部都係由 `data:` 開始。例如: ```html <!-- 用來提取 view.isError 這個變數 --> <data:view.isError /> <!-- 用來提取 blog.url 這個變數 --> <data:blog.url /> ``` 問 : 但係有咁多唔同變數,邊度可以睇到呢啲 Dcoumentation 呢? 答 : 可以去呢度睇到 https://support.google.com/blogger/answer/47270 但係有點一定要注意,就係如果你想要拎到 `data:posts` 既內容的話,你就一定要係 `b:widget type='Blog'` 入面先可以提取得到,如果唔係就只會拎到吉的 variable 或者會有可能出現 Error。 ```html <!-- 定義一個 widget, 而個 type='Blog' --> <b:widget id='Blog1' locked='true' title='文章' type='Blog' visible='true'> <!-- 每一個定義的 widget, 都一定會有個 includable id='main' 作為 widget 的進入點 --> <b:includable id='main' var='this'> <!-- 去到呢度已經可以用到 data:posts 呢個變數, 呢度係要 loop 下佢, loop 出來個 variable 叫做 post --> <b:loop var='post' values='data:posts'> <!-- 要係呢度先可以拎到 post.title 出來用 --> <data:post.title /> </b:loop> </b:includable> </b:widget> ``` 大致上就係咁,只要砌啱晒呢個 Structure 就咩 widget 都係可以用呢個方法提取到資料。 ### Workflow Tags 就是用來控制流程的標籤,全部都係由 `b:` 開始。例如: ```html <!-- 用來提取 view.isError 這個變數 --> <b:if /> <!-- 用來提取 blog.url 這個變數 --> <b:else /> ``` 佢地通常係要夾埋 Condition 一齊來決定條路點行。例如: ```html <!-- 對比一下 data:blog.pageType 的值來決定畫出什麼到畫面 --> <b:if cond='data:blog.pageType == "index"'> <span>是 index 類頁面</span> <b:else /> <span>不是 index 類頁面</span> </b:if> ``` 其實同寫 Program 一樣,只不過係語法有少少唔同。 去呢度可以有更詳細的講解: https://support.google.com/blogger/answer/47270 ### 總結 其實 Blogger 好好用,又唔洗錢,無論你係初手 / 中手 / 高手,都一樣好適合使用。 佢係專門用來寫 Blog 的 CMS,而且做得好好。 ### 隱藏技能 下面呢條 link 好似幾好玩,可以用來做 Ajax search post,遲啲有時間先研究下。 [https://example.com/feeds/posts/summary?callback=callcall&max-results=50&q=react&start-index=1&alt=json&orderby=updated](/feeds/posts/summary?callback=callcall&max-results=50&q=react&start-index=1&alt=json&orderby=updated)
要令到網站體驗去到唔同 Browser 都差唔多的話,咁用 Web font 就一定係個唔少得的步驟。 咁當中最簡單易用就係用 Google web font,一來唔洗錢,二來大家都用佢的話就可能已經有 Proxy 幫你 Cache 左,咁 Client Side 就唔洗再重新載入了。 ### 用法 首先去呢度選返個你鍾意既字形先 : https://fonts.google.com/?noto.query=noto+ 而我見到有 `Noto Sans Hong Kong` 用,咁就當然係諗都唔法諗就用呢個啦。 然後 embed 下面個 css 入去你個 template file 度。 ```html <link rel='preconnect' href='https://fonts.googleapis.com' /> <link rel='preconnect' href='https://fonts.gstatic.com' crossorigin='' /> <link href='https://fonts.googleapis.com/css2?family=Berkshire+Swash&family=Noto+Sans+HK:wght@100..900&family=Noto+Serif:ital,wght@0,100..900;1,100..900&display=swap' rel='stylesheet' /> ``` 再係 `style` 入面設定返字形套用的地方 : ```css html, body, div, p, table, th, td { font-family: 'Noto Sans HK', sans-serif; font-optical-sizing: auto; font-style: normal; } ``` 咁就設定好了,再 Reload 一下你個網站,當堂唔同晒 !
要顯示圖片就一定唔少得用 Lightbox 之類的東西,所以係 Blogger 呢度都係要加入返個先似返啲樣。 比較個咁多款易用程度同埋功能來講,Fslightbox 都係最方便同埋好用的表表者。 官方網站 : https://fslightbox.com/ ### 用法 因為 Blogger 係 Vanilla JS,所以最直接就係去搵人地啲 CDN 返來用就最方便。 CDN 地址 : https://cdnjs.com/libraries/fslightbox/3.4.1 將下面呢個直接貼入去你個 Blogger `</body>` 上面一行就搞掂。 ```html <script src="https://cdnjs.cloudflare.com/ajax/libs/fslightbox/3.4.1/index.min.js" integrity="sha512-BD6B4tou7H7CWpfaXthxgqwk7kHs/rPDEk9GD/Rvbx0sCOUdqtrvlBP/o64EmdP5lnGOS4j4FTcn0iPJx6bVCw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> ``` 如果係純 Js 用就好簡單,用返 `document.querySelectorAll()` 抽返晒你文中的所有 `<img>` 出來,然後用 `getAttribute('src')` 抽出圖片的來源網址備用。 ```js // 建立 Fslightbox 的實體 const lightbox = new FsLightbox(); // 設定來源的類別 lightbox.props.type = 'image'; // 設定來源 lightbox.props.sources = imagesData.map(image => image.src); // 開啟 lightbox lightbox.open(); ``` ### 如何抽出文中的所有 `<img>` 來源網址 上面講到要抽出文中的所有 `<img>` 來源網址,其實都好簡單,今日今日 (2024年) Vanilla Js 先係皇道,所以就用 Vanilla Js 去做。 ```js // get all target elements const imageElements = document.querySelectorAll(`img`); // pluck all src const sources = imageElements.map(imageElement => imageElement.getAttribute('src')); ``` 咁樣你個 `sources` 就已經有晒所文中所有的 `<img>` 來源網址了。
最近個 Project 諗起有野想做,係需要用到 OCR 技術,所以睇了一下關於用 NodeJS 去 Extract 圖片入面的文字出來。 ### node-tesseract-ocr https://github.com/zapolnoch/node-tesseract-ocr#readme 呢個係有大神整好左現成的 nodejs lib,已經可以直接使用。而佢背後其實係 Call 緊 Linux 個 tesseract-ocr (都係要裝)。 要先裝好左 tesseract-ocr ```sh apt-get install tesseract-ocr ``` 然後再裝呢個 npm ```sh npm install node-tesseract-ocr ``` ### 使用法方 好簡單 ```js const tesseract = require("node-tesseract-ocr") const config = { lang: "eng", // default oem: 3, psm: 3, } async function main() { try { const text = await tesseract.recognize("image.jpg", config) console.log("Result:", text) } catch (error) { console.log(error.message) } } main() ``` ### 效果 實際用起上來比起我要的效果仲係差好遠,可能因為我輸入的圖片太多唔係文字的東西,所以認得唔係咁好。 但係如果只係 Crop 淨返文字的部份,佢可以可認得出 99% 的文字內容出來。 睇來要再試試其他方向先得。
其實 Google 對 Blogger 呢個 project 都真係認真唔錯的,到今日今日都仲有新 API 出比人用來搬屋,都算係咁。 如果要手動一個一個咁搬真會係搬到 2046 都未完。 使用方法都係例排野,如果係公開資料的話只需要有條 API Key 就可以 Call 到。 但係要 Create Post 就要有 OAuth 或者 Service Account 個 Token 先可以做到。 ### API Doc 下面呢個係 API Overview : https://developers.google.com/blogger/docs/3.0/using?hl=zh-tw 呢個就係講個 Create Post 點用既 : https://developers.google.com/blogger/docs/3.0/reference/posts/insert?hl=zh-tw ### File Objects 處理 其實 Blogger 真係好方便,不過都有唔好處,真係佢對 Resource 的處理唔係好好。而我呢度所指的 Resources 係指 Objects (Photos, Videos, Files...)。 不過呢個其實唔關佢事,因為要管理 Objects 其實都係複雜的事情,之前 19Site 都係放係自已 Server 上面 Host。 現在搬機就自然要搬埋啲 Files 走,但係要一個一個 Upload 上去 Blogger 然之後 Extract 返個 URL 出來是何奇痛苦的事... 所以今真係將啲 Files 開左個 Object storage 放晒上去,再開個 cdn subdomain 指返去個 bucket cdn 度,咁就指需要係每篇文章改返少少個 URL 後,所有圖片都可以正常顯示了。 ### Rate limit 當然每一個 API 為左保護返 Server 都會有 Rate limit 設定,防止使用者係咁 POST 野去塞滿晒個 Server。 而呢個都成為左搬定的最大阻力,實用體驗係每日大約 80 篇文章左右,如果大家搬的網站係好多文章的話,就可以要搬好多日了。 最後經過幾日的時間,終於都搬好晒了 !
講真,要搞到 Blogger 可以好好睇睇真係要花好多時間去睇 DOC。 而下面呢兩條 link 係好緊要,無左佢真係咩都搞唔掂。 Widget Tags for Layouts https://support.google.com/blogger/answer/46995 Layouts Data Tags https://support.google.com/blogger/answer/47270 仲有呢篇教人可以整到啲內容用 Markdown 出都好正,唔洗再用 html editor 搞到啲內容走晒位 !! https://github.com/cs905s/md-in-blogger 呢度有個 `data:view` 的 cheatsheet https://www.share-collections.com/2020/09/blogger-cheatsheet-dataview.html ### 隱藏的 TAG 用用下其實會發覺到有好多(隱藏的 TAG),而係 Documentation 中係搵唔到的,例如: ```html <div> <b:class name='mt-3' cond='data.blog.pageType == "item"' /> </div> ``` 上面呢個 Tag 的意思是,如果 `data.blog.pageType == "item"` 時,就會將 `mt-3` 呢個 class 加入去 parent element (div) 度。
記錄返呢幾日玩既野先,有關 Facebook Graph API 如果想要拎到 adv accounts,可以用呢個 api ```js /me/adaccounts ``` 但係要在 oauth 時設定 ads_read 同埋 ads_management 呢兩個 scopes ```js { data : [{ account_id: [numeric string], id: [string]; }] } ``` 之後就可以用呢個 id 去 call ```js /[id]/adcreatives?fields=id,account_id,actor_id,title,name,instagram_permalink_url,effective_instagram_story_id,instagram_actor_id,status,thumbnail_url&thumbnail_width=300&thumbnail_height=300&limit=50``` 咁就可以返到廣告了 另外 effective_instagram_story_id 即係 ad creative link 去 ig post 既內容 可以用呢個 api 去拎返啲 comments 出來 ```js /[effective_instagram_story_id]/comments?fields=id,username,text,timestamp ```
最後都係搬返去 Blogger,因為無咩時間去維護個 Blog。 之前 React 寫個陣係用 Class Component 去寫,要轉返晒去做 Function Component 都要花唔少時間。所以都係唔改了,直接用返最原始的 Blogger 就算。 搬來搬去最後都係搬返去 Blogger haha