分享一些前端經常使用功能集合

描述

  1. 如下功能主要是以移動端爲主
  1. 使用到的ES6在移動端中沒有不兼容狀況,這裏我基本應用在微信端,手機瀏覽器的話也不用擔憂
  1. 全部功能均由原生JavaScript實現,沒有任何依賴,我一向的作法是用最少的代碼,造最高效的事情

我在作一些H5單頁(活動頁)的時候,像我這種最求極致加載速度,且不喜歡用第三方庫的人,因此決定本身動手作一些無依賴精簡高效的東西,而後按需應用在實際項目中,同時爲了比百度上搜到更好用的代碼分享給你們。javascript

這裏推薦前端使用vs code這個代碼編輯器,理由是在聲明的時候寫好標準的JSDoc註釋,在調用時會有很全面的代碼提示,讓弱類型的javascript也有類型提示php

1. http請求

前端必備技能,也是使用最多的功能。我我的不喜歡用axios這個東西(懶得去看文檔,並且以爲很雞肋),幾乎全部的web項目都是用的這個輪子。css

第一種:fetchhtml

/** * 基於`fetch`請求 [MDN文檔](https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API) * @param {"GET"|"POST"|"PUT"|"DELETE"} method 請求方法 * @param {string} url 請求路徑 * @param {object} data 請求參數對象 * @param {number} timeout 超時毫秒 */
function fetchRequest(method, url, data = {}, timeout = 5000) {
    let body = null;
    let query = "";
    if (method === "GET") {
        // 解析對象傳參
        for (const key in data) {
            query += `&${key}=${data[key]}`;
        }
        if (query) {
            query = "?" + query.slice(1);
        }
    } else {
        // 若後臺沒設置接收 JSON 則不行 須要跟 GET 同樣的解析對象傳參
        body = JSON.stringify(data);
    }
    return new Promise((resolve, reject) => {
        fetch(url + query, {
            // credentials: "include", // 攜帶cookie配合後臺用
            // mode: "cors", // 貌似也是配合後臺設置用的跨域模式
            method: method,
            headers: {
                "Content-Type": "application/json"
                // "Content-Type": "application/x-www-form-urlencoded"
            },
            body: body
        }).then(response => {
            // 把響應的信息轉爲`json`
            return response.json();
        }).then(res => {
            resolve(res);
        }).catch(error => {
            reject(error);
        });
        setTimeout(reject.bind(this, "fetch is timeout"), timeout);
    });
}
複製代碼

特別說明一下:我在H5單頁的一些簡單GET請求時一般用得最多,由於代碼極少,就像下面這樣前端

fetch("http://xxx.com/api/get").then(response => response.text()).then(res => {
    console.log("請求成功", res);
})
複製代碼

第二種:XMLHttpRequest,須要Promise用法在外面包多一層function作二次封裝便可vue

/** * `XMLHttpRequest`請求 [MDN文檔](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) * @param {object} params 傳參對象 * @param {string} params.url 請求路徑 * @param {"GET"|"POST"|"PUT"|"DELETE"} params.method 請求方法 * @param {object} params.data 傳參對象(json) * @param {FormData|string} params.formData `form`表單式傳參:上傳圖片就是使用這種傳參方式;使用`formData`時將覆蓋`data` * @param {{ [key: string]: string }} params.headers `XMLHttpRequest.header`設置對象 * @param {number?} params.overtime 超時檢測毫秒數 * @param {(result?: any, response: XMLHttpRequest) => void} params.success 成功回調 * @param {(error?: XMLHttpRequest) => void} params.fail 失敗回調 * @param {(info?: XMLHttpRequest) => void} params.timeout 超時回調 * @param {(res?: ProgressEvent<XMLHttpRequestEventTarget>) => void} params.progress 進度回調(暫時沒用到) */
function ajax(params) {
    if (typeof params !== "object") return console.error("ajax 缺乏請求傳參");
    if (!params.method) return console.error("ajax 缺乏請求類型 GET 或者 POST");
    if (!params.url) return console.error("ajax 缺乏請求 url");
    if (typeof params.data !== "object") return console.error("請求參數類型必須爲 object");

    const XHR = new XMLHttpRequest();
    /** 請求方法 */
    const method = params.method;
    /** 超時檢測 */
    const overtime = typeof params.overtime === "number" ? params.overtime : 0;
    /** 請求連接 */
    let url = params.url;
    /** 非`GET`請求傳參 */
    let body = null;
    /** `GET`請求傳參 */
    let query = "";

    // 傳參處理
    if (method === "GET") {
        // 解析對象傳參
        for (const key in params.data) {
            query += "&" + key + "=" + params.data[key];
        }
        if (query) {
            query = "?" + query.slice(1);
            url += query;
        }
    } else {
        body = JSON.stringify(params.data); // 若後臺沒設置接收 JSON 則不行,須要使用`params.formData`方式傳參
    }

    // 監聽請求變化;XHR.status learn: http://tool.oschina.net/commons?type=5
    XHR.onreadystatechange = function () {
        if (XHR.readyState !== 4) return;
        if (XHR.status === 200 || XHR.status === 304) {
            typeof params.success === "function" && params.success(JSON.parse(XHR.response), XHR);
        } else {
            typeof params.fail === "function" && params.fail(XHR);
        }
    }

    // 判斷請求進度
    if (params.progress) {
        XHR.addEventListener("progress", params.progress);
    }

    // XHR.responseType = "json"; // 設置響應結果爲`json`這個通常由後臺返回指定格式,前端無配置
    // XHR.withCredentials = true; // 是否Access-Control應使用cookie或受權標頭等憑據進行跨站點請求。
    XHR.open(method, url, true);

    // 判斷傳參類型,`json`或者`form`表單
    if (params.formData) {
        body = params.formData;
    } else {
        // 配置一個默認 json 傳參頭設置
        XHR.setRequestHeader("Content-Type", "application/json");
    }

    // 判斷設置配置頭信息
    if (params.headers) {
        for (const key in params.headers) {
            const value = params.headers[key];
            XHR.setRequestHeader(key, value);
        }
    }

    // 在IE中,超時屬性只能在調用 open() 方法以後且在調用 send() 方法以前設置。
    if (overtime > 0) {
        XHR.timeout = overtime;
        XHR.ontimeout = function () {
            console.warn("XMLHttpRequest 請求超時 !!!");
            XHR.abort();
            typeof params.timeout === "function" && params.timeout(XHR);
        }
    }

    XHR.send(body);
}
複製代碼

源碼地址html5

實際項目使用展現java

2. swiper輪播圖組件

這是我寫的第一個web功能組件,拖拽回彈物理效果是參照開源項目Swiper.js作的,效果功能保持一致,代碼實現均由本身完成node

/** * 輪播組件 * @param {object} params 配置傳參 * @param {string} params.el 組件節點 class|id|<label> * @param {number} params.moveTime 過渡時間(毫秒)默認 300 * @param {number} params.interval 自動播放間隔(毫秒)默認 3000 * @param {boolean} params.loop 是否須要迴路 * @param {boolean} params.vertical 是否垂直滾動 * @param {boolean} params.autoPaly 是否須要自動播放 * @param {boolean} params.pagination 是否須要底部圓點 * @param {(index: number) => void} params.slideCallback 滑動/切換結束回調 * @author https://github.com/Hansen-hjs * @description  * 移動端`swiper`組件,若是須要兼容`pc`自行修改對應的`touch`到`mouse`事件便可。現成效果預覽:https://huangjingsheng.gitee.io/hjs/cv/demo/face/ */
function swiper(params) {
    /** * css class 命名列表 * @dec ["滑動列表","滑動item","圓點容器","底部圓點","圓點高亮"] */
    const classNames = [".swiper_list", ".swiper_item", ".swiper_pagination", ".swiper_dot", ".swiper_dot_active"];
    /** 滑動結束函數 */
    const slideEnd = params.slideCallback || function() {};
    /** * 組件節點 * @type {HTMLElement} */
    let node = null;
    /** * item列表容器 * @type {HTMLElement} */
    let nodeItem = null;
    /** * item節點列表 * @type {Array<HTMLElement>} */
    let nodeItems = [];
    /** * 圓點容器 * @type {HTMLElement} */
    let nodePagination = null;
    /** * 圓點節點列表 * @type {Array<HTMLElement>} */
    let nodePaginationItems = [];
    /** 是否須要底部圓點 */
    let pagination = false;
    /** 是否須要迴路 */
    let isLoop = false;
    /** 方向 `X => true` | `Y => false` */
    let direction = false;
    /** 是否須要自動播放 */
    let autoPaly = false;
    /** 自動播放間隔(毫秒)默認 3000 */
    let interval = 3000;
    /** 過渡時間(毫秒)默認 300 */
    let moveTime = 300;

    /** 設置動畫 */
    function startAnimation() {
        nodeItem.style.transition = `${moveTime / 1000}s all`; 
    }

    /** 關閉動畫 */
    function stopAnimation() {
        nodeItem.style.transition = "0s all";
    }

    /** * 屬性樣式滑動 * @param {number} n 移動的距離 */
    function slideStyle(n) {
        let x = 0, y = 0;
        if (direction) {
            y = n;
        } else {
            x = n;
        }
        nodeItem.style.transform = `translate3d(${x}px, ${y}px, 0px)`;
    }

    /** * 事件開始 * @param {number} width 滾動容器的寬度 * @param {number} height 滾動容器的高度 */
    function main(width, height) {
        /** * 動畫幀 * @type {requestAnimationFrame} */
        const animation = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
        /** 觸摸開始時間 */
        let startTime = 0;
        /** 觸摸結束時間 */
        let endTime = 0;
        /** 開始的距離 */
        let startDistance = 0;
        /** 結束的距離 */
        let endDistance = 0;
        /** 結束距離狀態 */
        let endState = 0;
        /** 移動的距離 */
        let moveDistance = 0;
        /** 圓點位置 && 當前 item 索引 */
        let index = 0;
        /** 動畫幀計數 */
        let count = 0;
        /** loop 幀計數 */
        let loopCount = 0;
        /** 移動範圍 */
        let range = direction ? height : width;

        /** 獲取拖動距離 */
        function getDragDistance() {
            /** 拖動距離 */
            let dragDistance = 0;
            // 默認這個公式
            dragDistance = moveDistance + (endDistance - startDistance);
            // 判斷最大正負值
            if ((endDistance - startDistance) >= range) {
                dragDistance = moveDistance + range;
            } else if ((endDistance - startDistance) <= -range) {
                dragDistance = moveDistance - range;
            }
            // 沒有 loop 的時候慣性拖拽
            if (!isLoop) {
                if ((endDistance - startDistance) > 0 && index === 0) {
                    // console.log("到達最初");
                    dragDistance = moveDistance + ((endDistance - startDistance) - ((endDistance - startDistance) * 0.6));
                } else if ((endDistance - startDistance) < 0 && index === nodeItems.length - 1) {
                    // console.log("到達最後");
                    dragDistance = moveDistance + ((endDistance - startDistance) - ((endDistance - startDistance) * 0.6));
                }
            }
            return dragDistance;
        }

        /** * 判斷觸摸處理函數 * @param {number} slideDistance 滑動的距離 */
        function judgeTouch(slideDistance) {
            // 這裏我設置了200毫秒的有效拖拽間隔
            if ((endTime - startTime) < 200) return true;
            // 這裏判斷方向(正值和負值)
            if (slideDistance < 0) {
                if ((endDistance - startDistance) < (slideDistance / 2)) return true;
                return false;
            } else {
                if ((endDistance - startDistance) > (slideDistance / 2)) return true;
                return false;
            }
        }

        /** 返回原來位置 */
        function backLocation() {
            startAnimation();
            slideStyle(moveDistance);
        }

        /** * 滑動 * @param {number} slideDistance 滑動的距離 */
        function slideMove(slideDistance) {
            startAnimation();
            slideStyle(slideDistance);
            loopCount = 0;
            // 判斷 loop 時回到第一張或最後一張
            if (isLoop && index < 0) {
                // 我這裏是想讓滑塊過渡完以後再重置位置因此加的延遲 (以前用setTimeout,快速滑動有問題,而後換成 requestAnimationFrame解決了這類問題)
                function loopMoveMin() {
                    loopCount += 1;
                    if (loopCount < moveTime / 1000 * 60) return animation(loopMoveMin);
                    stopAnimation();
                    slideStyle(range * -(nodeItems.length - 3));
                    // 重置一下位置
                    moveDistance = range * -(nodeItems.length - 3);
                }
                loopMoveMin();
                index = nodeItems.length - 3;
            } else if (isLoop && index > nodeItems.length - 3) {
                function loopMoveMax() {
                    loopCount += 1;
                    if (loopCount < moveTime / 1000 * 60) return animation(loopMoveMax);
                    stopAnimation();
                    slideStyle(0);
                    moveDistance = 0;
                }
                loopMoveMax();
                index = 0;
            }
            // console.log(`第${ index+1 }張`); // 這裏能夠作滑動結束回調
            if (pagination) {
                nodePagination.querySelector(classNames[4]).className = classNames[3].slice(1);
                nodePaginationItems[index].classList.add(classNames[4].slice(1));
            }
        }

        /** 判斷移動 */
        function judgeMove() {
            // 判斷是否須要執行過渡
            if (endDistance < startDistance) {
                // 往上滑動 or 向左滑動
                if (judgeTouch(-range)) {
                    // 判斷有loop的時候不須要執行下面的事件
                    if (!isLoop && moveDistance === (-(nodeItems.length - 1) * range)) return backLocation();
                    index += 1;
                    slideMove(moveDistance - range);
                    moveDistance -= range;
                    slideEnd(index);
                } else {
                    backLocation();
                }
            } else {
                // 往下滑動 or 向右滑動
                if (judgeTouch(range)) {
                    if (!isLoop && moveDistance === 0) return backLocation();
                    index -= 1;
                    slideMove(moveDistance + range);
                    moveDistance += range;
                    slideEnd(index)
                } else {
                    backLocation();
                }
            }
        }

        /** 自動播放移動 */
        function autoMove() {
            // 這裏判斷 loop 的自動播放
            if (isLoop) {
                index += 1;
                slideMove(moveDistance - range);
                moveDistance -= range;
            } else {
                if (index >= nodeItems.length - 1) {
                    index = 0;
                    slideMove(0);
                    moveDistance = 0;
                } else {
                    index += 1;
                    slideMove(moveDistance - range);
                    moveDistance -= range;
                }
            }
            slideEnd(index);
        }

        /** 開始自動播放 */
        function startAuto() {
            count += 1;
            if (count < interval / 1000 * 60) return animation(startAuto);
            count = 0;
            autoMove();
            startAuto();
        }

        // 判斷是否須要開啓自動播放
        if (autoPaly && nodeItems.length > 1) startAuto();

        // 開始觸摸
        nodeItem.addEventListener("touchstart", ev => {
            startTime = Date.now();
            count = 0;
            loopCount = moveTime / 1000 * 60;
            stopAnimation();
            startDistance = direction ? ev.touches[0].clientY : ev.touches[0].clientX;
        });

        // 觸摸移動
        nodeItem.addEventListener("touchmove", ev => {
            ev.preventDefault();
            count = 0;
            endDistance = direction ? ev.touches[0].clientY : ev.touches[0].clientX;
            slideStyle(getDragDistance());
        });

        // 觸摸離開
        nodeItem.addEventListener("touchend", () => {
            endTime = Date.now();
            // 判斷是否點擊
            if (endState !== endDistance) {
                judgeMove();
            } else {
                backLocation();
            }
            // 更新位置 
            endState = endDistance;
            // 從新打開自動播
            count = 0;
        });
    }

    /** * 輸出迴路:若是要回路的話先後增長元素 * @param {number} width 滾動容器的寬度 * @param {number} height 滾動容器的高度 */
    function outputLoop(width, height) {
        const first = nodeItems[0].cloneNode(true), last = nodeItems[nodeItems.length - 1].cloneNode(true);
        nodeItem.insertBefore(last, nodeItems[0]);
        nodeItem.appendChild(first);
        nodeItems.unshift(last);
        nodeItems.push(first);
        if (direction) {
            nodeItem.style.top = `${-height}px`;
        } else {
            nodeItem.style.left = `${-width}px`;
        }
    }

    /** * 輸出動態佈局 * @param {number} width 滾動容器的寬度 * @param {number} height 滾動容器的高度 */
    function outputLayout(width, height) {
        if (direction) {
            for (let i = 0; i < nodeItems.length; i++) {
                nodeItems[i].style.height = `${height}px`;
            }
        } else {
            nodeItem.style.width = `${width * nodeItems.length}px`;
            for (let i = 0; i < nodeItems.length; i++) {
                nodeItems[i].style.width = `${width}px`;
            }
        }
    }

    /** 輸出底部圓點 */
    function outputPagination() {
        let paginations = "";
        nodePagination = node.querySelector(classNames[2]);
        // 若是沒有找到對應節點則建立一個
        if (!nodePagination) {
            nodePagination = document.createElement("div");
            nodePagination.className = classNames[2].slice(1);
            node.appendChild(nodePagination);
        }
        for (let i = 0; i < nodeItems.length; i++) {
            paginations += `<div class="${classNames[3].slice(1)}"></div>`;
        }
        nodePagination.innerHTML = paginations;
        nodePaginationItems = [...nodePagination.querySelectorAll(classNames[3])];
        nodePagination.querySelector(classNames[3]).classList.add(classNames[4].slice(1));
    }

    /** 初始化動態佈局 */
    function initLayout() {
        node = document.querySelector(params.el);
        if (!node) return console.warn("沒有可執行的節點!");
        nodeItem = node.querySelector(classNames[0]);
        if (!nodeItem) return console.warn(`缺乏"${classNames[0]}"節點!`);
        nodeItems = [...node.querySelectorAll(classNames[1])];
        if (nodeItems.length == 0) return console.warn("滑動節點個數必須大於0!");
        const moveWidth = node.offsetWidth, moveHeight = node.offsetHeight;
        if (pagination) outputPagination();
        if (isLoop) outputLoop(moveWidth, moveHeight);
        outputLayout(moveWidth, moveHeight);
        main(moveWidth, moveHeight);
    }

    /** 初始化參數 */
    function initParams() {
        if (typeof params !== "object") return console.warn("傳參有誤");
        pagination = params.pagination || false;
        direction = params.vertical || false;
        autoPaly = params.autoPaly || false;
        isLoop = params.loop || false;
        moveTime = params.moveTime || 300;
        interval = params.interval || 3000;
        initLayout();
    }
    initParams();
}
複製代碼

源碼地址及使用展現ios

3. 圖片懶加載

非傳統實現方式,性能最優

/** * 懶加載 * @description 可加載`<img>`、`<video>`、`<audio>`等一些引用資源路徑的標籤 * @param {object} params 傳參對象 * @param {string?} params.lazyAttr 自定義加載的屬性(可選) * @param {"src"|"background"} params.loadType 加載的類型(默認爲`src`) * @param {string?} params.errorPath 加載失敗時顯示的資源路徑,僅在`loadType`設置爲`src`中可用(可選) */
function lazyLoad(params) {
    const attr = params.lazyAttr || "lazy";
    const type = params.loadType || "src";

    /** 更新整個文檔的懶加載節點 */
    function update() {
        const els = document.querySelectorAll(`[${attr}]`);
        for (let i = 0; i < els.length; i++) {
            const el = els[i];
            observer.observe(el);
        }
    }

    /** * 加載圖片 * @param {HTMLImageElement} el 圖片節點 */
    function loadImage(el) {
        const cache = el.src; // 緩存當前`src`加載失敗時候用
        el.src = el.getAttribute(attr);
        el.onerror = function () {
            el.src = params.errorPath || cache;
        }
    }

    /** * 加載單個節點 * @param {HTMLElement} el */
    function loadElement(el) {
        switch (type) {
            case "src":
                loadImage(el);
                break;
            case "background":
                el.style.backgroundImage = `url(${el.getAttribute(attr)})`;
                break;
        }
        el.removeAttribute(attr);
        observer.unobserve(el);
    }

    /** * 監聽器 * [MDN說明](https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver) */
    const observer = new IntersectionObserver(function(entries) {
        for (let i = 0; i < entries.length; i++) {
            const item = entries[i];
            if (item.isIntersecting) {
                loadElement(item.target);
            }
        }
    })

    update();

    return {
        observer,
        update
    }
}
複製代碼

vue中使用指令去使用

import Vue from "vue";

/** 添加一個加載`src`的指令 */
const lazySrc = lazyLoad({
    lazyAttr: "vlazy",
    errorPath: "./img/error.jpg"
})

Vue.directive("v-lazy", {
    inserted(el, binding) {
        el.setAttribute("vlazy", binding.value); // 跟上面的對應
        lazySrc.observer.observe(el);
    }
})

/** 添加一個加載`background`的指令 */
const lazyBg = lazyLoad({
    lazyAttr: "vlazybg",
    loadType: "background"
})

Vue.directive("v-lazybg", {
    inserted(el, binding) {
        el.setAttribute("vlazybg", binding.value); // 跟上面的對應
        lazyBg.observer.observe(el);
    }
})

複製代碼

源碼地址及使用展現

4. 上傳圖片

這個超簡單,沒啥好說的

<!-- 先準備好一個input標籤,而後設置type="file",最後掛載一個onchange事件 -->
<input class="upload-input" type="file" name="picture" onchange="upLoadImage(this)">
複製代碼
/** * input上傳圖片 * @param {HTMLInputElement} el */
function upLoadImage(el) {
    /** 上傳文件 */
    const file = el.files[0];
    /** 上傳類型數組 */
    const types = ["image/jpg", "image/png", "image/jpeg", "image/gif"];
    // 判斷文件類型
    if (types.indexOf(file.type) < 0) {
        file.value = null; // 這裏必定要清空當前錯誤的內容
        return alert("文件格式只支持:jpg 和 png");
    }
    // 判斷大小
    if (file.size > 2 * 1024 * 1024) {
        file.value = null;
        return alert("上傳的文件不能大於2M");
    }
    
    const formData = new FormData();    // 這個是傳給後臺的數據
    formData.append("img", file);       // 這裏`img`是跟後臺約定好的`key`字段
    console.log(formData, file);
    // 最後POST給後臺,這裏我用上面的方法
    ajax({
        url: "http://xxx.com/uploadImg",
        method: "POST",
        data: {},
        formData: formData,
        overtime: 5000,
        success(res) {
            console.log("上傳成功", res);
        },
        fail(err) {
            console.log("上傳失敗", err);
        },
        timeout() {
            console.warn("XMLHttpRequest 請求超時 !!!");
        }
    });
}
複製代碼

base64轉換和靜態預覽

配合接口上傳到後臺 這個可能要安裝環境,由於是serve項目

5. 下拉刷新組件

拖拽效果參考上面swiper的實現方式,下拉中的效果是能夠本身定義的

// 這裏我作的不是用 window 的滾動事件,而是用最外層的綁定觸摸下拉事件去實現
// 好處是我用在Vue這類單頁應用的時候,組件銷燬時不用去解綁 window 的 scroll 事件
// 可是滑動到底部事件就必需要用 window 的 scroll 事件,這點須要注意

/** * 下拉刷新組件 * @param {object} option 配置 * @param {HTMLElement} option.el 下拉元素(必選) * @param {number} option.distance 下拉距離[px](可選) * @param {number} option.deviation 頂部往下偏移量[px](可選) * @param {string} option.loadIcon 下拉中的 icon html(可選) */
function dropDownRefresh(option) {
    const doc = document;
    /** 總體節點 */
    const page = option.el;
    /** 下拉距離 */
    const distance = option.distance || 88;
    /** 頂部往下偏移量 */
    const deviation = option.deviation || 0;
    /** 頂層節點 */
    const topNode = doc.createElement("div");
    /** 下拉時遮罩 */
    const maskNode = doc.createElement("div");

    topNode.innerHTML = `<div refresh-icon style="transition: .2s all;"><svg style="transform: rotate(90deg); display: block;" t="1570593064555" viewBox="0 0 1575 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="26089" width="48" height="48"><path d="M1013.76 0v339.968H484.115692V679.778462h529.644308v339.968l529.644308-485.612308v-48.600616L1013.76 0zM243.396923 679.857231h144.462769V339.968H243.396923V679.778462z m-240.797538 0h144.462769V339.968H2.599385V679.778462z" fill="#000000" fill-opacity=".203" p-id="26090"></path></svg></div><div refresh-loading style="display: none; animation: refresh-loading 1s linear infinite;">${option.loadIcon || '<p style="font-size: 15px; color: #666;">loading...</p>'}</div>`;
    topNode.style.cssText = `width: 100%; height: ${distance}px; position: fixed; top: ${-distance + deviation}px; left: 0; z-index: 10; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; box-sizing: border-box; margin: 0; padding: 0;`;
    maskNode.style.cssText = "position: fixed; top: 0; left: 0; width: 100%; height: 100vh; box-sizing: border-box; margin: 0; padding: 0; background-color: rgba(0,0,0,0); z-index: 999;";
    page.parentNode.insertBefore(topNode, page);

    /** * 設置動畫時間 * @param {number} n 秒數 */
    function setAnimation(n) {
        page.style.transition = topNode.style.transition = n + "s all";
    }

    /** * 設置滑動距離 * @param {number} n 滑動的距離(像素) */
    function setSlide(n) {
        page.style.transform = topNode.style.transform = `translate3d(0px, ${n}px, 0px)`;
    }
    
    /** 下拉提示 icon */
    const icon = topNode.querySelector("[refresh-icon]");
    /** 下拉 loading 動畫 */
    const loading = topNode.querySelector("[refresh-loading]");

    return {
        /** * 監聽開始刷新 * @param {Function} callback 下拉結束回調 * @param {(n: number) => void} rangeCallback 下拉狀態回調 */
        onRefresh(callback, rangeCallback = null) {
            /** 頂部距離 */
            let scrollTop = 0;
            /** 開始距離 */
            let startDistance = 0;
            /** 結束距離 */
            let endDistance = 0;
            /** 最後移動的距離 */
            let range = 0;

            // 觸摸開始
            page.addEventListener("touchstart", function (e) {
                startDistance = e.touches[0].pageY;
                scrollTop = 1;
                setAnimation(0);
            });

            // 觸摸移動
            page.addEventListener("touchmove", function (e) {
                scrollTop = doc.documentElement.scrollTop === 0 ? doc.body.scrollTop : doc.documentElement.scrollTop;
                // 沒到達頂部就中止
                if (scrollTop != 0) return;
                endDistance = e.touches[0].pageY;
                range = Math.floor(endDistance - startDistance);
                // 判斷若是是下滑才執行
                if (range > 0) {
                    // 阻止瀏覽自帶的下拉效果
                    e.preventDefault();
                    // 物理回彈公式計算距離
                    range = range - (range * 0.5);
                    // 下拉時icon旋轉
                    if (range > distance) {
                        icon.style.transform = "rotate(180deg)";
                    } else {
                        icon.style.transform = "rotate(0deg)";
                    }
                    setSlide(range);
                    // 回調距離函數 若是有須要
                    if (typeof rangeCallback === "function") rangeCallback(range);
                }
            });

            // 觸摸結束
            page.addEventListener("touchend", function () {
                setAnimation(0.3);
                // console.log(`移動的距離:${range}, 最大距離:${distance}`);
                if (range > distance && range > 1 && scrollTop === 0) {
                    setSlide(distance);
                    doc.body.appendChild(maskNode);
                    // 阻止往上滑動
                    maskNode.ontouchmove = e => e.preventDefault();
                    // 回調成功下拉到最大距離並鬆開函數
                    if (typeof callback === "function") callback();
                    icon.style.display = "none";
                    loading.style.display = "block";
                } else {
                    setSlide(0);
                }
            });

        },
        /** 結束下拉 */
        end() {
            maskNode.parentNode.removeChild(maskNode);
            setAnimation(0.3);
            setSlide(0);
            icon.style.display = "block";
            loading.style.display = "none";
        }
    }
}
複製代碼

源碼地址及使用展現

6. 監聽滾動到底部

就幾行代碼的一個方法,另外監聽元素滾動到底部能夠參考代碼筆記

/** * 監聽滾動到底部 * @param {object} options 傳參對象 * @param {number} options.distance 距離底部多少像素觸發(px) * @param {boolean} options.once 是否爲一次性(防止重複用) * @param {() => void} options.callback 到達底部回調函數 */
function onScrollToBottom(options) {
    const { distance = 0, once = false, callback = null } = options;
    const doc = document;
    /** 滾動事件 */
    function onScroll() {
        /** 滾動的高度 */
        let scrollTop = doc.documentElement.scrollTop === 0 ? doc.body.scrollTop : doc.documentElement.scrollTop;
        /** 滾動條高度 */
        let scrollHeight = doc.documentElement.scrollTop === 0 ? doc.body.scrollHeight : doc.documentElement.scrollHeight;
        if (scrollHeight - scrollTop - distance <= window.innerHeight) {
            if (typeof callback === "function") callback();
            if (once) window.removeEventListener("scroll", onScroll);
        }
    }
    window.addEventListener("scroll", onScroll);
    // 必要時先執行一次
    // onScroll(); 
}
複製代碼

源碼地址及使用展現

7. 音頻播放組件

這裏須要說明一下應用場景:我先前作H5活動頁(紅包雨)的時候遇到一個問題,就是在移動端快速點擊節點並播放音頻的時候,aduio標籤播放的速度會有很嚴重的延遲。後來搜了下相關資料發現一個音頻API:new AudioContext,和我以前作小遊戲時用到的引擎(cocos creator)音頻API是同樣的。而後找了挺久發現這個API的使用資料、教程仍是挺少的多是除了作H5遊戲引擎的人會用到吧,比較詳細的也只有MDN官網,剩下的就是一些基於這個APIJavaScript庫,可是我須要用到的功能比較簡單,就是點擊播放無延遲。因此本身去實現一個基於new AudioContext經常使用的音頻組件。

/** * `AudioContext`音頻組件 * [資料參考](https://www.cnblogs.com/Wayou/p/html5_audio_api_visualizer.html) * @description 解決在移動端網頁上標籤播放音頻延遲的方案 貌似`H5`遊戲引擎也是使用這個實現 */
function audioComponent() {
    /** * 音頻上下文 * @type {AudioContext} */
    const context = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext)();
    /** * @type {AnalyserNode}  */
    const analyser = context.createAnalyser();;
    /** * @type {AudioBufferSourceNode} */
    let bufferNode = null;
    /** * @type {AudioBuffer} */
    let buffer = null;
    /** 是否加載完成 */
    let loaded = false;

    analyser.fftSize = 256;

    return {
        /** * 加載路徑音頻文件 * @param {string} url 音頻路徑 * @param {(res: AnalyserNode) => void} callback 加載完成回調 */
        loadPath(url, callback) {
            const XHR = new XMLHttpRequest(); 
            XHR.open("GET", url, true); 
            XHR.responseType = "arraybuffer"; 
            // 先加載音頻文件
            XHR.onload = () => {
                context.decodeAudioData(XHR.response, audioBuffer => {
                    // 最後緩存音頻資源
                    buffer = audioBuffer;
                    loaded = true;
                    typeof callback === "function" && callback(analyser);
                });
            }
            XHR.send(null);
        },

        /** * 加載 input 音頻文件 * @param {File} file 音頻文件 * @param {(res: AnalyserNode) => void} callback 加載完成回調 */
        loadFile(file, callback) {
            const FR = new FileReader();
            // 先加載音頻文件
            FR.onload = e => {
                const res = e.target.result;
                // 而後解碼
                context.decodeAudioData(res, audioBuffer => {
                    // 最後緩存音頻資源
                    buffer = audioBuffer;
                    loaded = true;
                    typeof callback === "function" && callback(analyser);
                });
            }
            FR.readAsArrayBuffer(file);
        },

        /** 播放音頻 */
        play() {
            if (!loaded) return console.warn("音頻未加載完成 !!!");
            // 這裏有個問題,就是建立的音頻對象不能緩存下來而後屢次執行 start , 因此每次都要建立而後 start()
            bufferNode = context.createBufferSource();
            bufferNode.connect(analyser);
            analyser.connect(context.destination);
            bufferNode.buffer = buffer;
            bufferNode.start(0);
        },

        /** 中止播放 */
        stop() {
            if (!bufferNode) return console.warn("音頻未播放 !!!");
            bufferNode.stop();
        }
    }
}
複製代碼

源碼地址及使用展現

8. 全局監聽圖片錯誤並替換到默認圖片

window.addEventListener("error", e => {
    /** 默認`base64`圖片 */
    const defaultImg = 'data:image/png;base64,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';
    /** * @type {HTMLImageElement} */
    const node = e.target;
    if (node.nodeName && node.nodeName.toLocaleLowerCase() === "img") {     
        node.style.objectFit = "cover";
        node.src = defaultImg;
    }
}, true);
複製代碼

9. 複製功能

我在翻 Clipboard.js 這個插件庫源碼的時候找到核心代碼 setSelectionRange(start: number, end: number),百度上搜到的複製功能所有都少了這個操做,因此搜到的複製文本代碼在 iosIE 等一些瀏覽器上覆制不了。

/** * 複製文本 * @param {string} text 複製的內容 * @param {() => void} success 成功回調 * @param {(tip: string) => void} fail 出錯回調 */
function copyText(text, success = null, fail = null) {
    text = text.replace(/(^\s*)|(\s*$)/g, "");
    if (!text) {
        typeof fail === "function" && fail("複製的內容不能爲空!");
        return;
    }
    const id = "the-clipboard";
    /** * 粘貼板節點 * @type {HTMLTextAreaElement} */
    let clipboard = document.getElementById(id);
    if (!clipboard) {
        clipboard = document.createElement("textarea");
        clipboard.id = id;
        clipboard.readOnly = true
        clipboard.style.cssText = "font-size: 15px; position: fixed; top: -1000%; left: -1000%;";
        document.body.appendChild(clipboard);
    }
    clipboard.value = text;
    clipboard.select();
    clipboard.setSelectionRange(0, text.length);
    const state = document.execCommand("copy");
    if (state) {
        typeof success === "function" && success();
    } else {
        typeof fail === "function" && fail("複製失敗");
    }
}
複製代碼

10. 檢測類型

可檢測全部類型

/** * 檢測類型 * @param {any} target 檢測的目標 * @returns {"string"|"number"|"array"|"object"|"function"|"null"|"undefined"|"regexp"} 只枚舉一些經常使用的類型 */
function checkType(target) {
    /** @type {string} */
    const value = Object.prototype.toString.call(target);
    const result = value.match(/\[object (\S*)\]/)[1];
    return result.toLocaleLowerCase();
}
複製代碼

11. 格式化日期(代碼極少版)

/** * 獲取指定日期時間戳 * @param {number} time 毫秒數 */
function getDateFormat(time = Date.now()) {
    const date = new Date(time);
    return `${date.toLocaleDateString()} ${date.toTimeString().slice(0, 8)}`;
}
複製代碼

12. JavaScript小數精度計算

/** * 數字運算(主要用於小數點精度問題) * @param {number} a 前面的值 * @param {"+"|"-"|"*"|"/"} type 計算方式 * @param {number} b 後面的值 * @example  * ```js * // 可鏈式調用 * const res = computeNumber(1.3, "-", 1.2).next("+", 1.5).next("*", 2.3).next("/", 0.2).result; * console.log(res); * ``` */
function computeNumber(a, type, b) {
    /** * 獲取數字小數點的長度 * @param {number} n 數字 */
    function getDecimalLength(n) {
        const decimal = n.toString().split(".")[1];
        return decimal ? decimal.length : 0;
    }
    /** * 修正小數點 * @description 防止出現 `33.33333*100000 = 3333332.9999999995` && `33.33*10 = 333.29999999999995` 這類狀況作的處理 * @param {number} n */
    const amend = (n, precision = 15) => parseFloat(Number(n).toPrecision(precision));
    const power = Math.pow(10, Math.max(getDecimalLength(a), getDecimalLength(b)));
    let result = 0;

    a = amend(a * power);
    b = amend(b * power);

    switch (type) {
        case "+":
            result = (a + b) / power;
            break;
        case "-":
            result = (a - b) / power;
            break;
        case "*":
            result = (a * b) / (power * power);
            break;
        case "/":
            result = a / b;
            break;
    }

    result = amend(result);

    return {
        /** 計算結果 */
        result,
        /** * 繼續計算 * @param {"+"|"-"|"*"|"/"} nextType 繼續計算方式 * @param {number} nextValue 繼續計算的值 */
        next(nextType, nextValue) {
            return computeNumber(result, nextType, nextValue);
        }
    };
}
複製代碼

13. 一行css適配rem

750是設計稿的寬度:以後的單位直接1:1使用設計稿的大小,單位是rem

html{ font-size: calc(100vw / 750); }
複製代碼

14. 好用的格式化日期方法

/** * 格式化日期 * @param {string | number | Date} value 指定日期 * @param {string} format 格式化的規則 * @example * ```js * formatDate(); * formatDate(1603264465956); * formatDate(1603264465956, "h:m:s"); * formatDate(1603264465956, "Y年M月D日"); * ``` */
function formatDate(value = Date.now(), format = "Y-M-D h:m:s") {
    const formatNumber = n => `0${n}`.slice(-2);
    const date = new Date(value);
    const formatList = ["Y", "M", "D", "h", "m", "s"];
    const resultList = [];
    resultList.push(date.getFullYear().toString());
    resultList.push(formatNumber(date.getMonth() + 1));
    resultList.push(formatNumber(date.getDate()));
    resultList.push(formatNumber(date.getHours()));
    resultList.push(formatNumber(date.getMinutes()));
    resultList.push(formatNumber(date.getSeconds()));
    for (let i = 0; i < resultList.length; i++) {
        format = format.replace(formatList[i], resultList[i]);
    }
    return format;
}
複製代碼

15. 網頁定位

這裏使用百度定位,不管代碼封裝、調用方式仍是位置準確性都比微信sdk那個好用太多了,包括在任何網頁端;

文檔說明

獲取百度地圖key

/** * 插入腳本 * @param {string} link 腳本路徑 * @param {Function} callback 腳本加載完成回調 */
function insertScript(link, callback) {
    const label = document.createElement("script");
    label.src = link;
    label.onload = function () {
        if (label.parentNode) label.parentNode.removeChild(label);
        if (typeof callback === "function") callback();
    }
    document.body.appendChild(label);
}

/** * 獲取定位信息 * @returns {Promise<{ city: string, districtName: string, province: string, longitude: number, latitude: number }>} */
function getLocationInfo() {
    /** * 使用百度定位 * @param {(value: any) => void} callback */
    function useBaiduLocation(callback) {
        const geolocation = new BMap.Geolocation({
            maximumAge: 10
        })
        geolocation.getCurrentPosition(function(res) {
            console.log("%c 使用百度定位 >>", "background-color: #4e6ef2; padding: 2px 6px; color: #fff; border-radius: 2px", res);
            callback({
                city: res.address.city,
                districtName: res.address.district,
                province: res.address.province,
                longitude: Number(res.longitude),
                latitude: Number(res.latitude)
            })
        })
    }

    return new Promise(function (resolve, reject) {
        if (!window._baiduLocation) {
            window._baiduLocation = function () {
                useBaiduLocation(resolve);
            }
            // ak=你本身的key
            insertScript("https://api.map.baidu.com/api?v=2.0&ak=66vCKv7PtNlOprFEe9kneTHEHl8DY1mR&callback=_baiduLocation");
        } else {
            useBaiduLocation(resolve);
        }
    })
}
複製代碼

16. 輸入保留數字 <input type="text">

使用場景:用戶在輸入框輸入內容時,實時過濾保持數字值顯示;

tips:在Firefox中設置 <input type="number"> 會有樣式 bug

/** * 輸入只能是數字 * @param {string | number} value 輸入的值 * @param {boolean} decimal 是否要保留小數 * @param {boolean} negative 是否能夠爲負數 */
function inputOnlyNumber(value, decimal, negative) {
    let result = value.toString().trim();
    if (result.length === 0) return "";
    const minus = (negative && result[0] == "-") ? "-" : "";
    if (decimal) {
        result = result.replace(/[^0-9.]+/ig, "");
        let array = result.split(".");
        if (array.length > 1) {
            result = array[0] + "." + array[1];
        }
    } else {
        result = result.replace(/[^0-9]+/ig, "");
    }
    return minus + result;
}
複製代碼

END

以上就是就是一些經常使用到的功能分享,後續有也會更新 另外還有一些其餘功能我以爲不重要因此不貼出來了,有興趣能夠看看 倉庫地址 有用的話,不妨給個star

相關文章
相關標籤/搜索