不失逼格地使用原生 JS

估計平時大夥兒使用 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 原生基礎知識,多多益善。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章