估計平時大夥兒使用 JS 框架寫大型代碼
比較多,效率是高;不過作爲前端從業者,多掌握原生 JS 的能力對專業技能的培養是非常有益的。
本文收集了 6 個用簡短的原生 JS 來實現一些常見的功能,既簡單又高效,有些還意外地酷炫。
以下的代碼片段都可以 “拿走即用”~,也可以根據自己的需要稍微修改。
1、下載功能
在網站上我們想要下載網站的內容,基本是靠瀏覽器自帶的下載功能。那能否我們自定義下載的內容和行爲呢?!
當然可以,而且也不難,不到 10 行代碼就能搞定。
比如你想讓用戶一串 “Hello World” 的字符串(每當開始表演的時候,總少不了經典的 “Hello World”…),就可以這麼寫:
/** 將 Hello World 塞給 Blob 對象,同時設置 MIME 類型爲 文本文件 **/
const blob = new Blob(["Hello World"], { type: 'text/plain' });
/** 根據 blob 內容創建對象 URL(不瞭解該知識點,就可以理解成類似音視頻這樣的 url ) **/
const url = window.URL.createObjectURL(blob);
/** 創建 a 表情 **/
const link = document.createElement('a');
/** 下載時顯示的文件名 **/
link.download = '下載文件名';
/** 將對象 URL 賦值鏈接給 href 屬性 **/
link.href = url;
/** 加載到文檔末尾 **/
document.body.appendChild(link);
/** 模擬點擊剛動態創建是 a 標籤,觸發瀏覽器下載動作 **/
link.click();
/** 此時 a 標籤失去利用價值,從 body 中移除 **/
document.body.removeChild(link);
/** 同樣 URL 對象也失去價值,從內存中釋放~ **/
window.URL.revokeObjectURL(url);
這幾行代碼完成自定義下載功能了,注意下載的時候設置 Blob 的 MIME 類型,這樣會給你下載的文件自動加上後綴,常用的有 ‘text/plain’(.txt 後綴)、‘application/json’(.json 後綴)、‘image/jpeg’(.jpg 後綴)等等
完整類型列表請前往:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
2、用 emoji 做爲網站圖標
一般我們網站圖標都是通過 <link rel="icon" src='xxx'/>
來指定我們的網站圖標。
使用 JS 後,我們可以做一點兒有意思的事兒,比如可以用 emoji 表情來做你網站的圖標,先看一下效果:
首先,我們定義一個 setFavicon
函數,可以用指定的 url 來做當前網站的 favicon :
const setFavicon = function(url) {
/** 找到 favicon 元素,有些網站的圖標的 rel 值是 'shortcut icon' **/
const favicon = document.querySelector('link[rel="shortcut icon"]') || document.querySelector('link[rel="icon"]');
if (favicon) {
/** 如果能找到元素,將其值更新入參 url **/
favicon.href = url;
} else {
/** 如果找不到元素,自己創建 favicon 的 link 元素 **/
const link = document.createElement('link');
link.rel = 'icon';
link.href = url;
/** 將創建 link 元素加到文檔中 **/
document.head.appendChild(link);
}
};
現在如果你想動態更新你的網站圖標,就能直接調用該方法,傳入圖片 url 去更新即可。
有了這個方法,我們就可以用 emoji 表情作爲你網站的圖標:
- 將 emoji 表情轉換成 URL (使用 canvas 畫布中轉)
- 調用 setFavicon 方法即可
具體代碼如下:
const emojiFavicon = function(emoji) {
/** 將創建 canvas 元素,尺寸 64x64 **/
const canvas = document.createElement('canvas');
canvas.height = 64;
canvas.width = 64;
/** 獲取 canvas 元素的 context 對象 **/
const context = canvas.getContext('2d');
context.font = '64px serif';
/** 將 emoji 元素放到畫布中 **/
context.fillText(emoji, 0, 64);
/** 調用 canvas.toDataURL 獲取 base64 格式的 URL **/
const url = canvas.toDataURL();
/** 更新網站 favicon **/
setFavicon(url);
};
最終你前往網站,打開控制檯 -> 粘貼上述代碼 -> 調用 emojiFavicon(‘😂’) 就能達到本節開頭 gif 所展示的效果了
3、只允許輸入指定字符
很多場景下,我們只允許用戶輸入指定的字符,比如以下的輸入框,用來保存手機號碼只能要求輸入數字和空格。
基礎版
<input type="text" id="input" />
藉助 input 事件,寫個兩三行代碼就能實現:
/** 保留當前值 **/
const ele = document.getElementById('input');
let currentValue = ele.value || '';
ele.addEventListener('input', function(e) {
const target = e.target;
/** 如果用戶輸入可選字符(字符或者空格) **/
/^[0-9\s]*$/.test(target.value)
/** 就將值存儲起來 **/
? currentValue = target.value
/** 否則還是保留原來的值 **/
: target.value = currentValue;
});
別小看這幾行代碼,除了用戶常規的鍵盤輸入,對於用戶通過複製(Ctrl+V)、右鍵菜單或者將字符拖入輸入框 都會進入上面的事件監聽,阻止了一部分心思靈活用戶的騷操作~
細節版
如果你不追求極致體驗,到上面爲止就行了。想要追求細節的讀者,會發現上述代碼會有一點瑕疵:當上述調用 target.value = currentValue
時,鼠標位置會總是放在輸入框的末尾。
如果要追求完美,對光標位置得處理一下,兩步走搞定~
第一步:保存用戶當前光標位置
/** 該變量保存用戶的當前光標位置 **/
const selection = {};
/** 監聽 keydown 事件 **/
ele.addEventListener('keydown', function(e) {
const target = e.target;
selection = {
selectionStart: target.selectionStart,
selectionEnd: target.selectionEnd,
};
});
第二步:當恢復用戶內容時,也同時恢復其光標位置
let currentValue = '';
ele.addEventListener('input', function(e) {
const target = e.target;
if (/^[0-9s]*$/.test(target.value)) {
currentValue = target.value;
} else {
/** 恢復原 input 內容 **/
target.value = currentValue;
/** 恢復光標位置 **/
target.setSelectionRange(
selection.selectionStart,
selection.selectionEnd
);
}
});
偷懶版
如果你覺得上述 JS 寫起來比較煩,想怎麼簡單怎麼來,那 HTML5 也幫你考慮到了,用特定的 type 的 input 輸入框就行:
還有很多類型 type,就不一一列舉,可以去 MDN 文檔查閱:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#%3Cinput%3E_types
4、粘貼預覽圖片
如果你想製作一個 圖片上傳 功能,爲了提高用戶體驗,希望能從粘貼板中獲取用戶已經複製的圖片,這樣用戶直接在你的界面上進行 Ctrl + V 進行預覽操作:
核心代碼如下(大概 10 行左右,不算註釋啊…):
/** 關鍵是監聽 'paste' 事件 **/
document.addEventListener('paste', function(evt) {
/** 獲取粘貼板上的數據 **/
const clipboardItems = evt.clipboardData.items;
/** 過濾篩選獲得圖片列表 **/
const items = [].slice
.call(clipboardItems)
.filter(function(item) {
/** 過濾條件是 type 爲 image **/
return item.type.indexOf('image') !== -1;
});
if (items.length === 0) {
return;
}
/** “弱水三千,只取一瓢”,咱們只用第一個 **/
const item = items[0];
/** 將圖片數據轉換成 blob 對象 **/
const blob = item.getAsFile();
/** 動態創建 image 標籤(假設 id 爲 preivew) **/
const imageEle = document.getElementById('preview');
/** 將圖片 blob 對象轉換成 URL 對象,賦值 **/
/** 打完!收工! **/
imageEle.src = URL.createObjectURL(blob);
});
幾行代碼代碼就能獲得良好的用戶體驗,一定會獲得交互師、業務方的稱讚,晚飯加個雞腿犒勞一下自己~~(估計殘酷的現實是設計師讓你按設計稿左移幾 px 像素…淚奔…)
5、按順序動態加載 JS 文件
這其實是一個常問的面試題,“給你一個 js 路徑數組,如何按順序動態加載這些腳本”?
平時面試官還會基於這個命題分散出更多的面試題,比如 如何按優先級加載 js 文件?、如何同時並行 & 串行加載 js 文件?
等等,可自行發散思維
首先定義加載單個 js 文件的方法:
/** 根據給定的 url ,加載 js 文件 **/
const loadScript = function(url) {
/** 返回 promise 對象,當加載完畢後纔會 resolve **/
return new Promise(function(resolve, reject) {
/** 動態創建 script 標籤 **/
const script = document.createElement('script');
/** 給標籤 src 屬性賦值 **/
script.src = url;
/** 監聽 load 事件 **/
script.addEventListener('load', function() {
/** 完畢後調用 resolve 方法 **/
resolve(true);
});
document.head.appendChild(script);
});
};
然後定義按順序處理 promise 數組的方法:
/** 根據給定的 promise 數組,按順序執行這些 promise **/
const waterfall = function(promises) {
/** 調用數組的 reduce 方法 **/
return promises.reduce(
function(p, c) {
/** 等前一個 promise 執行完 **/
return p.then(function() {
/** 然後執行當前 promise **/
return c().then(function(result) {
return true;
});
});
},
/** promise 初始值,立即執行 **/
Promise.resolve([])
);
};
上述的 loadScript 和 waterfall 方法,平時都可以單獨拿來使用,也非常方便。
我們現在結合這兩個方法,就能實現 按順序動態加載 js 文件 的功能:
/** 按順序動態加載 js 文件 **/
const loadScriptsInOrder = function(arrayOfJs) {
/** 將 string 數組轉換成 promise 數組 **/
const promises = arrayOfJs.map(function(url) {
return loadScript(url);
});
/** 按順序串接 promise 數組內元素 **/
return waterfall(promises);
};
舉個調用例子:
loadScriptsInOrder([
'/path/to/file.js',
'/path/to/another-file.js',
'/yet/another/file.js',
]).then(function() {
/** 等上述 3 個 js 都加載完,再做一些操作 **/
})
代碼看上去比較多,不過條理清晰,記憶起來不算費勁~
6、判斷容器是否可滾動
這個需求比較少見,不過既然收集到了就羅列在這裏,只用 4 行代碼就能完成這項判斷:
const isScrollable = function(ele) {
/** 對比元素的 scrollHeight 和 clientHeight 數值(如果容器不可滾動,這兩個值相等) **/
const hasScrollableContent = ele.scrollHeight > ele.clientHeight;
/** 以上操作還不充分,還得判斷 `overflow-y` 樣式沒有被用戶設置成 `hidden` 或 `hidden !important` **/
const overflowYStyle = window.getComputedStyle(ele).overflowY;
const isOverflowHidden = overflowYStyle.indexOf('hidden') !== -1;
return hasScrollableContent && !isOverflowHidden;
};
這個方法我們可以做一下簡單的擴展,就能實現 獲取當前元素的首個可滾動父容器 能力,用遞歸方法:
const getFirstScrollableParent = function(ele) {
return (!ele || ele === document.body)
? document.body
: (isScrollable(ele) ? ele : getScrollableParent(ele.parentNode));
};
7、小結
掌握原生 JS 的優勢在於,讓你具備從 JS 框架的使用者轉換成 JS 框架的製造者的基礎,因此掌握牢固的 JS 原生基礎知識,多多益善。