Поделитесь некоторыми общими коллекциями интерфейсных функций

JavaScript

описывать

Все функции родныеjavascriptРеализация без каких-либо зависимостей, мой последовательный подход заключается в использовании наименьшего количества кода для создания наиболее эффективных вещей.

я делаю кое-чтоH5Когда дело доходит до одной страницы (активной страницы), такие люди, как я, хотят получить максимальную скорость загрузки и не любят использовать сторонние библиотеки, поэтому я обычно делаю некоторые из них сам.无依赖,精简高效а затем применить его к реальному проекту по мере необходимости и поделиться им со старым нуждающимся железом, чтобы найти код лучше, чем Baidu.

Здесь рекомендуется использовать интерфейсvs codeЭто редактор кода, причина в том, чтобы написать стандарт во время объявленияJSDocКомментарии, при звонке будут очень исчерпывающие подсказки по коду, так что слабо типизированныйjavascriptТакже есть подсказки по типам

1. HTTP-запрос

Основные навыки работы с интерфейсом также являются наиболее часто используемыми функциями. я лично не люблю использоватьaxiosЭта штука (лень читать документацию, и чувствую себя очень безвкусно), почти всеwebВсе проекты используют это колесо.

Первый:fetch

/**
 * 基于`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Делать вторичную упаковку

/**
 * `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|FormData|string} params.data 传参对象,json、formdata、普通表单字符串
 * @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 (checkType(params) !== "object") return console.error("ajax 请求参数类型有误");
    if (!params.method) return console.error("ajax 缺少请求方法");
    if (!params.url) return console.error("ajax 缺少请求 url");
    
    const XHR = new XMLHttpRequest();
    /** 请求方法 */
    const method = params.method;
    /** 超时检测 */
    const overtime = checkType(params.overtime) === "number" ? params.overtime : 0;
    /** 请求链接 */
    let url = params.url;
    /** 非`GET`请求传参 */
    let body = null;
    /** `GET`请求传参 */
    let query = "";
    /** 传参数据类型 */
    const dataType = checkType(params.data);

    // 传参处理
    if (method === "GET") {
        // 解析对象传参
        if (dataType === "object") {
            for (const key in params.data) {
                query += "&" + key + "=" + params.data[key];
            }
        } else {
            console.warn("ajax 传参处理 GET 传参有误,需要的请求参数应为 object 类型");
        }
        if (query) {
            query = "?" + query.slice(1);
            url += query;
        }
    } else {
        body = dataType === "object" ? JSON.stringify(params.data) : params.data;
    }

    // 监听请求变化;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);

    // 设置对应的传参请求头,GET 方法不需要
    if (params.method !== "GET") {
        switch (dataType) {
            case "object":
                XHR.setRequestHeader("Content-Type", "application/json"); // `json`请求
                break;
            
            case "string":
                XHR.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // 表单请求,非`new FormData`
                break;
    
            default:
                break;
        }
    }

    // 判断设置配置头信息
    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);
}

Адрес источника

Демонстрация фактического использования проекта

2. компонент карусели swiper

Это первый, который я написал.webфункциональные компоненты,拖拽,回弹Физические эффекты относятся к проектам с открытым исходным кодом.Swiper.jsСделайте это, функция эффекта непротиворечива, а реализация кода выполняется самостоятельно.

/**
 * 轮播组件
 * @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();
}

Адрес исходного кода и отображение использования

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,
        overtime: 8000,
        success(res) {
            console.log("上传成功", res);
        },
        fail(err) {
            console.log("上传失败", err);
        },
        timeout() {
            console.warn("XMLHttpRequest 请求超时 !!!");
        }
    });
}

преобразование base64 и статический предварительный просмотр

Сотрудничайте с интерфейсом для загрузки в фоновом режимеВозможно, для этого придется установить среду, потому что это проект на стороне сервера.

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, и звук движка (создатель кокосов), который я использовал, когда делал мини-игру доAPIэто то же самое. После долгих поисков я нашел этоAPIОчень мало материалов по использованию и руководствH5Игровой движок люди будут использовать на нем, более подробно только на официальном сайте MDN, остальные основаны на этомAPIизJavaScript库, но функция, которую мне нужно использовать, относительно проста, то есть нажмите, чтобы воспроизвести без промедления. Таким образом, чтобы реализовать один на основе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,iVBORw0KGgoAAAANSUhEUgAAAJoAAACACAYAAADzsnDqAAANXElEQVR4Xu2dDYxcVRXHz3mz0FgjICBSgkZAQD5CwQ+Qz4BBBUEkUJpK0FAUYmms2O47d7oSmYbYnXfeLIXl01ojBAnSokRQQIWAIvEjkfBhUZGPEkMFDajgbjdu5x1zYVpmd2d25r1335uZzrkJIenec869//ubO/Pux3kIWlSBHBTAHGJoCFUAFDSFIBcFFLRcZNYgCpoykIsCClouMmsQBU0ZyEUBBS0XmTWIgqYM5KKAgpaLzBpEQVMGclFAQctFZg2ioCkDuSigoOUiswbJHLTR0dE5W7ZsWQAAB6rcnVMAETcj4vO+7z/QiVZkCtrIyMhB1Wr12wBwcic6pzFnKiAiq4wxpby1yRS0IAhuR8RFeXdK47VUYCERbWhZy2GFzEBbs2bNvMnJyc0O26qu3CnwJBHNd+eutafMQBseHj65UCg81LoJWqMTChBRZmPfqD+ZBVPQOoFP+zEVtPa10popFFDQUoinpu0r0E+gPVytVle1L43WjKNAoVC4YrZlpb4CjYhOiSOe1m1fAWa2D2JN1y8VtPa11JqzKKCgvS3OwzqjZfdZUdAUtOzoqvOsoCloCppLBdpYsNWvTpeCT/OlM5rOaBni9bZrBU1BU9BcKqBfnS7VjO9LZzSd0eJTk8BCQVPQEmAT30RBU9DiU5PAQkFT0BJgE99EQVPQ4lOTwEJBU9ASYBPfREFT0OJTk8BCQVPQEmAT30RBU9DiU5PAQkFT0BJgE99EQesgaMy8NxG9HH/Yes9CQesQaOVy+f2e560koiW9h038FitoHQKNmb8LABeJSNEYE8Qfut6yUNA6ABozfwoAfmZDi8gEIl5IRHf0FjrxWqugdQC0MAwfFZHj6obqT4i42Pf938Ubvt6praDlDFoQBMsQ8ZrpiCDifWNjYwtKpdJ47+DTfksVtBxBC8NwLxH5MwC8u8kQ3UhEl7Y/fL1TU0HLF7TrRaQVSIaIuHcQaq+lClpOoJXL5ZM8z/tlG8MyHkXR4mKxuL6Nuj1TRUHLCTRmfhAAPtEmGU9v3br13KGhIfs1u0MUBS0H0Jj5EgCwSZrjlHuJ6Iw4Bt1cV0HLGLTR0dFdtmzZ8gwivjcuCCJygzFmaVy7bqyvoGUMWhiGIyKyPMXg+0RUSWHfFabMfJ6IHIaIhwKA/e+w+oZp2qoUwxQEwdGImHYRdgwAziGin6doSteZhmF4eBRFi2rp+A9Q0FIMETP/FAA+k8LFNtONY2Njx61atep1B766zgUzn0tEP8yzYZ3Myu00yQszfxEAbnEo3k+I6LMO/fW1qx0CtFKptPPcuXOfBYD3uRxNRLzO9/2vuvTZr752CNCY+VsAMJTRIF5MROsy8h3b7erVq/fwPG8fz/PmAcA+iDhPRP4LAPZA58sDAwN/nzNnzstLly61/9Y1pedBC8PwCBF5IitF7SB6nnes7/t/zCrGbH5rs7X9Cj8TAOz/92izHRtF5E7P8+7rhlMqPQ8aM98FAGe3KX7Sak8R0RFJjZPYMfNpAHABAJwOALsn8VFnsxEAHkDEdZ36wPQ0aOVyeaHneXkdYLyHiM5KOeAtzZn5wwDwNQCwDzeui126ucYem/J9/x+unc/mr6dBY2b7AHBAXoKJyJXGmG9mES8Ign0R0QK2DAB2ziJGnc8XLHBENOOcXlZxexY0ZrYD3ok3r5xBRPe6HJBKpXJSFEVrAeBgl37b8PXAwMDAkuXLl9sPbKalJ0FbvXr1QQMDA3/JVJkmzhHx9UKhsN/y5ctfcxE/DMOFIpLX13+jJr8aRdEXisXifS7608xHT4IWBMEdiLgwS2Fa+HbyYlVmvgoAvt7BftSHXkZE12bVlp4DLQiCsxDxx1kJ0q5fRLzF9/0L260/vR4z311brkjqwrkdIn7e9/0fOHcMAD0HGjM/DQCHZCFGXJ8issQYc1NcuyAIRhAxzQmTuCHbrl8oFI5csWKF83XJngKNmQkAuu3y71FE9Hi7I8nMdhb8Xrv1O1DvbwBwtOvUET0DWi2lwYsdEL5VyP8Q0W6tKtm/l8vl+Z7n2eNHe7VTv0Udg4j3FwqFvyLibpOTk/sDQAgAxzrw7XzNsGdAY+ZbayvlDnR07uIhImp5P4GZ7ekSFwuxixrdtC+VSnPnzp17m6OdkstcrrP1BGj1KQ2cI+LIISIO+77fdGO/tqWUegkBEa/2fb/pkyozHwkA9qWwbc2ys3T/BUT8uKsdhJ4ALQiCxxFxviMmMnMjIp8zxtinyRmFmS1kdv8yVfE875DBwcFZb2sxs/0NmPiJuK6Bq4noG6kaXDPuetDCMLxMRNa46GzWPkTkVGOMveY3pTg8lPkcEX2wVT+Y+csA8J1W9dr4+1htVkt9cqWrQbMpDQBgs4gU2hCl01VeJ6Jdm8xmvwWAYxw0cBMR7dfKj+Mn22uJyO6/pipdDdq2nGapepif8c1EtHh6OHspRESectUMEdnFGPPGbP7CMFwrIhe7iCkizxpjDkzrq2tBi5HSIK0GTuyb/T5jZnsi42onQd7K79bw67nefxiGj4uIs9+01Wr1lJUrVz6cpg9dCxoz/x4APpamcznaVolooMnXpn0CPNlVW0RkyBgz3Mzf6OjonImJiQlX8Wp+RohoMI3PrgQtCIJLEfH6NB3L01ZEfmGMsVklp5Ra2qxXHLflLiI6p5nPSqVyQhRFj7iMKSLPGGNSHWHqOtBsSoOJiQl70eIdLsXK0peI3GaMsceupxRmtuf873Ec+yUi2reZzzAMl4vIiOOY9iu75W/D2WJ2HWjMfCMAfMW1UFn6E5GrjDErGoCWJNlMy6Yi4v6+79tTsjNKEAS3126jt/QTp0KhUDh4xYoVz8Sxqa/bVaCFYXiMiNilgJ4qzTJ9h2F4hYiUXHdGRM4zxtzZyG9Wp1vSPhB0G2iPiMgJrgcma38icpExZsaJDGa2qbPsrOa0NNvuqlQqe0ZR9E+nwd52dj4R3Z7Ud9eA5nA1O6kWie2iKDqzWCzavB9TShiGd4tIFmkVHiSiUxt8Vdu8IzPakbhjdYYiMmiMSfzbrytAq12Stde/Gq6suxAqYx8NL6wws30QsA8Erssb4+Pju5dKpa31jpn5cgC40nUw62+HAC0IAnvXMPU2RxYCt+lzMRHd3GCGsfuNdt/ReRGRY4wxdq1xewmCYAMiLnAe7C3QLjDG2CNIiUrHZ7SRkZGjqtXqY4la3yVGIkLGGHvocEphZju72Fkmi3IpEdkn9O2Fme1TYertokaNbWdHYrZOdhy0MAwfFJGWhwazGClXPhEx9H3fHjOfUjJeeF5HRNv3M8Mw3E9EnnfVp+l+PM87bHBw0N7XSFQ6Clrt7LzLnGaJRHBg1HBDPQiCcxAxq4R3jxHRR7a1vVKpnB1Fkc1DkkkZGBjYI81d1k6DZvOqvicTZfJ12jCjd6VSOTSKIptgJZNSv7YVBEEJEa/IJBDAq0S0ZxrfnQbN2WZzGhHS2orIK8aYvRv5YWZ7aHBKouK08ers38yaOTIyclC1WrW33e0x7ixKwxk7TqBOghannV1fN4qiE4vF4q+nNzTjmcaG2wQANtV9lnvDC9LmvFXQ3CFcISJ/ujtmPhEAfuUuTO6eXhsfH59XKpX+lyaygpZGvam2Tc/zM7O9TJLqmI27Zsb2dBsRzTiZEteLghZXsVnqR1F0WrFYfPNNx/UlCAJ72bfsMFSerj7t4p0LCprbIbuJiJZMd7l+/frCpk2bHnV0QWWbe3u0+nIReRIR7SWeczNIF9GwP0kkU9CSqNbERkTGd9ppp/mNEtsFQbAAETc4CreRiA6f7mt4ePgDhUKh4Tm1uHHtkzQiHk9Ez8W1bVRfQXOhYp2P2dKPMrPdKzw/bUhEPM73/d808sPMNqWXi1y7Tt+JpaClHfWZ9i+Oj48fWSqV/j39T66SvMz2HidHyyk7VJIX90PcPR6bZk90cbnXpofwff/JJjOafZPyeSmk2OHSVqXQortNEfGJycnJTw4NDTU87eogEd8GIpqRWjUMw3eKyGYA2CWpQr2YiG//QqHg5IdkUtE6bDfrtk3a1KIissoYs/0+QrlcPh0RRxGxZW6OZrr0ZGpR2xlmtlsyx3d4wDsWXkSWGmNuaNaAIAjWIeKXUjTwXwDwvIjsmgYwG7/ZBZsUbZtimtnDgI1SS2z8fQB4l6sG95ifLQBwAhE1PdjJzPbV2td1sl+IeInv+y6yDzXtRqag1WD7aO1T+6FOitnJ2PaExWzxXTwgJOzfSyKyzBjzo4T2bZtlDlrbLenzivZWu51ZMro11UjdtTbtRLOnV9fDoaC5VjSlvxyAW+t53trBwcE/pGxqLHMFLZZc+VWu5e1Y5PI1ip7n3Zo3YNsUU9DyYydRpDQvhq3trd6/Q78YNpGqatRSAX3VdUuJtEI/K6Bfnf08+jn2XUHLUex+DqWg9fPo59h3BS1Hsfs5lILWz6OfY98VtBzF7udQClo/j36OfVfQchS7n0MpaP08+jn2XUHLUex+DqWg9fPo59h3BS1Hsfs5lILWz6OfY98VtBzF7udQClo/j36OfVfQchS7n0P9H/gjHdvP/Qy/AAAAAElFTkSuQmCC';
    /**
     * @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), во всех функциях копирования, найденных на Baidu, отсутствует эта операция, поэтому найденный текстовый код копирования находится вiosа такжеIEЕго нельзя скопировать в некоторых браузерах.

/**
 * 复制文本
 * @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. Таргетинг на веб-страницу

Здесь используется позиционирование Baidu независимо от инкапсуляции кода, метода вызова или точности определения местоположения.微信sdkЕго так легко использовать, в том числе на любой веб-странице;

Документация

Получить ключ карты Baidu

/**
 * 插入脚本
 * @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">будут ошибки стиля

/**
 * 输入只能是数字
 * @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

Выше приведены некоторые часто используемые функции, которыми можно поделиться, и они будут обновлены в будущем.Есть также некоторые другие функции, которые я считаю не важными, поэтому я не буду их публиковать.Если вам интересно, вы можете взглянуть.Адрес складаЕсли полезно, поставьте звезду