工具函数

2022/8/16

# 滚动

# 全屏显示

全屏显示只能由用户发起,可以添加EventListener由用户出发

# 获取数据类型

# 跨平台停止事件冒泡

const stopPropagation = (event) => {
  event = event || window.event;
  if (event.stopPropagation) {
    event.stopPropagation();
  } else {
    event.cancelBubble = true;
  }
};
1
2
3
4
5
6
7
8

# 深拷贝

一种思路,但是数组打印出来会是Array { '0': '你好' }的形式

const deepClone = (obj, hash = new WeakMap()) => {
  if (obj instanceof Date) {
    return new Date(obj);
  }
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  let allDesc = Object.getOwnPropertyDescriptors(obj);
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);
  hash.set(obj, cloneObj);
  for (let key of Reflect.ownKeys(obj)) {
    if (obj[key] && typeof obj[key] === "object") {
      cloneObj[key] = deepClone(obj[key], hash);
    } else {
      cloneObj[key] = obj[key];
    }
  }
  return cloneObj;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 判断平台

# 操作cookie

需要注意的是,document.cookie方法一次只能对一个 cookie 进行设置或更新。

# 随机函数

# 首字母大写

# shuffle洗牌函数

const shuffle = (arr) => Array.from(arr).sort(() => Math.random() - 0.5);
1

# 格式化数字

const num = 1234567890;

// 1,234,567,890
console.log(num.toLocaleString("en-US"));

// $1,234,567,890.00
console.log(num.toLocaleString('en-US', {
    style: 'currency',
    currency: 'USD'
}));

// 一二三四五六七八九〇
console.log(num.toLocaleString('zh-Hans-CN-u-nu-hanidec', {
    useGrouping: false
}));

// ¥1,234,567,890.00
console.log(num.toLocaleString('zh-CN', {
    style: 'currency',
    currency: 'CNY'
}));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 日期

# 颜色

# 复制粘贴

# 暗黑模式

const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches

console.log(isDarkMode); // true
1
2
3

# 检测是否激活本网页

切换浏览器tab、最小化浏览器都算未激活

const isTabInView = () => !document.hidden; 
1

# touchmove事件打平

  1. 鼠标mousemovetouch事件取x和y方法不一样
  2. mouse有移出屏幕的风险,会导致监听触发不符合预期

# url解析query

const queryParser = (url) => {
    const queryReg = /\?([^#]+)/;
    try {
        const queryStr = url.match(queryReg);
        if (queryStr === null) return {};

        const queryParams = Object.fromEntries(queryStr[1].split("&").map(item => item.split("=")).map(([k, v]) => ([k, decodeURIComponent(v)])));
        return queryParams;
    } catch (e) {
        return {}
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 输入法半角转全角

  • 涉及到日文输入法可以直接转化为对应英文字母
const jp2en = (s: Vowels) => {
  const map = {: "a",: "i",: "u",: "e",: "o",: "a",: "i",: "u",: "e",: "o",
  };
  return Object.keys(map).includes(s) ? map[s] : "";
};

const toHalfWidth = (str: string) => {
  let result = "";
  const reg = /[a-z0-90-9]/i;
  for (let i = 0; i < str.length; i++) {
    if (/[あ-おア-エ]/.test(str[i])) {
      result += jp2en(str[i] as Vowels);
      continue;
    }
    if (!reg.test(str[i])) {
      continue;
    }
    const c = str.charCodeAt(i);
    if (c >= 0xff01 && c <= 0xff5e) {
      // full string
      result += String.fromCharCode(c - 0xfee0);
    } else {
      result += str.charAt(i);
    }
  }
  return result;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 防抖节流

# 手势

# 检测

  • ie检测
function isIE() {
  if (!!window.ActiveXObject || "ActiveXObject" in window) {
    return true;
  }
  return false;
}
1
2
3
4
5
6
  • 触控设备检测
const canHover = matchMedia("(hover: hover)").matches || isIE();
1

# vh手机适配

  • 手机safari的100vh会算上底边工具栏
const setVhUnit = () => {
  let vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
}

setVhUnit();
window.addEventListener('resize', setVhUnit);
1
2
3
4
5
6
7
  • 使用
height: calc(var(--vh, 1vh) * 100);
1

# 阻止页面关闭

window.addEventListener("beforeunload", function (event) {
  // 取消关闭网页
  event.preventDefault();
  // 提示用户是否确认关闭
  event.returnValue = "";
});
1
2
3
4
5
6

# 检测前端版本变化

class UpdateTip {
  lastVersion = "";
  updatedCb = [() => console.log("updated")];
  constructor() {
    this.lastVersion = this.getCurrentVersion();
  }
  getCurrentVersion = () => {
    const script = document.querySelector("script[src^=\\/static]");
    const src = script.getAttribute("src");
    const reg = /main\.(.*?)\.js/i;
    const version = src.match(reg)[1];
    return version;
  };
  fetchVersion = async () => {
    try {
      const ans = await (await fetch("/")).text();
      const reg = /src=['"]\/static\/js\/main\.(.*?)\.js['"]/;
      const newVersion = ans.match(reg)[1];
      return newVersion;
    } catch {
      return this.lastVersion;
    }
  };
  versionCompare = async () => {
    const newVersion = await this.fetchVersion();
    return newVersion === this.lastVersion;
  };
  checkVersion = () => {
    setInterval(async () => {
      const updated = (await this.versionCompare()) !== true;
      if (updated) {
        this.updatedCb.forEach((cb) => cb());
      }
    }, 10000);
  };
  addCallBack = (cb) => {
    if (typeof cb === "function") {
      this.updatedCb.push(cb);
    }
  };
}

const versionChecker = new UpdateTip();

export default versionChecker;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 检测输入法输入事件

<body>
  <input id="inputRef">

  <script>
    const onCompositionStart = () => console.log("-----  开始输入法输入   -----")
    const onCompositionEnd = () => console.log("-----  结束输入法输入   -----")
    inputRef.addEventListener("compositionstart", onCompositionStart);
    inputRef.addEventListener("compositionend", onCompositionEnd);
  </script>
</body>
1
2
3
4
5
6
7
8
9
10

# 字数计算/截断

防颜文字占两位或多位

  • 判断1M字符
const checkSizeOver1M = (str: string) => {
  let totalBytes = 0;
  for (let i = 0; i < str.length; i++) {
    const charCode = str.charCodeAt(i);
    if (charCode > 0x7f) {
      totalBytes += 2; // 2byte
    } else {
      totalBytes += 1; // 1byte
    }
    if (totalBytes > 1024 * 1024) {
      return true;
    }
  }
  return false;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 字符截断8位
const str = "👨‍👩‍👧‍👧🥟Hello🍜⚽️🏀🎱🥵💣🎆Hello World!";
 
const sliceStr = (str, length) => {
  const reg =
    /\ud83c[\udffb-\udfff](?=\ud83c[\udffb-\udfff])|(?:[^\ud800-\udfff][\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]?|[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?)*/g;
  const arr = str.match(reg);
  return arr.slice(0, length).join("");
};
 
console.log(sliceStr(str, 8));
 
1
2
3
4
5
6
7
8
9
10
11

# DOM链接转化

  • 将DOM的链接转化成a标签链接
const url2ATag = (el) => {
  const urlReg =
    /(http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+/g;
  const transUrl = (txt) => {
    return txt.replace(urlReg, (url, https, s) => {
      return `<a href='${https ? "" : "https://"}${url}' target='_blank'>${url}</a>`;
    });
  };
  const hasUrl = (txt) => {
    return urlReg.test(txt);
  };
  const txtNode2spanNode = (node) => {
    const newHtml = transUrl(node.nodeValue);
    const span = document.createElement("span");
    span.innerHTML = newHtml;
    const parent = node.parentNode;
    parent.replaceChild(span, node);
  };

  const elLinkTrans = (el) => {
    const childNodes = el.childNodes;
    for (let node of childNodes) {
      if (node.nodeType === 3 && hasUrl(node.nodeValue)) {
        txtNode2spanNode(node);
      } else {
        elLinkTrans(node);
      }
    }
  };

  elLinkTrans(div);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 文件下载

  • 准备文件
|- video.mp4
|- server.js
|- index.html
1
2
3
  • server.js
const express = require("express");
const app = express();
const path = require("path");
const fs = require("fs");
app.listen("8888", () => console.log("listen 8888"));
const allowCors = function (req, res, next) {
  res.header("Access-Control-Allow-Origin", req.headers.origin);
  res.header("Access-Control-Allow-Methods", "*");
  res.header("Access-Control-Allow-Headers", "Content-Type,responseType");
  res.header("Access-Control-Allow-Credentials", "true");
  next();
};
app.use(allowCors); // 使用跨域中间件

app.get("/file", (req, res) => {
  // 正常
  try {
    // 读取一个文件
    const file = fs.readFileSync(path.join(__dirname, "./video.mp4"));
    res.send(file);
  } catch (e) {
    // 异常
    res.status(500);
    res.send({ msg: "不想给你看~" });
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • index.html
<body>
  <button id="downloadBtn">下载</button>

  <script>
    // 下载buffer文件函数
    const bufferFileDownload = (fileBuffer, fileName, mimeType) => {
      const fileData = new Blob([fileBuffer], {
        type: mimeType ?? "application/octet-stream",
      });
      // for IE
      if (window.navigator && window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveOrOpenBlob(fileData, fileName);
      }
      // for Non-IE (chrome, firefox, safari etc.)
      else {
        const a = document.createElement("a");
        document.body.appendChild(a);
        a.style.display = "none";
        const url = window.URL.createObjectURL(fileData);
        a.href = url;
        a.download = fileName;
        a.click();
        a.remove();
        window.URL.revokeObjectURL(url);
      }
    };

    const download = async () => {
      try {
        const response = await fetch("http://127.0.0.1:8888/file", {
          method: "GET",
          headers: {
            responseType: "arraybuffer",
          },
        });
        if (response.status !== 200) {
          throw response;
        }
        // 以 buffer 形式读取
        const file = await response.arrayBuffer();
        const fileName = "video.mp4";
        bufferFileDownload(file, fileName);
      } catch (e) {
        const errData = await e.json();
        console.warn(errData);
      }
    };
    downloadBtn.addEventListener("click", download);
  </script>
</body>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# time

  • 普通interval会根据代码执行量有时间差,并且会越积累越多,逐渐不精准
const startTime = Date.now();
let diff = 0;
let timer = 1000;
let num = 0;

setInterval(() => {
  num++;
  const current = Date.now();
  diff = current - (startTime + num * 1000);
  console.log(diff);
}, timer);
1
2
3
4
5
6
7
8
9
10
11
  • 解法
// 解法
const interval = () => {
  const startTime = Date.now();
  let diff = 0;
  let timer = 1000;
  let num = 0;

  const timeout = () => {
    num++;
    let current = Date.now();
    diff = current - (num * 1000 + startTime);
    timer = 1000 - diff;
    console.log(diff);
    setTimeout(timeout, timer);
  };
  setTimeout(timeout, timer);
};

interval();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# DOM尺寸变化

  • 只有opera不支持,可以用polyfill进行支持
useEffect(() => {
  if (!containerRef.current) {
    return;
  }
  const resizeObserver = new ResizeObserver((entries) => {
    const height = entries[0].contentRect.height;
    setContainerHeight(height);
  });
  resizeObserver.observe(containerRef.current);
  return () => {
    resizeObserver.disconnect();
  };
}, [containerRef.current]);
1
2
3
4
5
6
7
8
9
10
11
12
13

# 清除鼠标选中项

const removeDOMSelect = () => {
  window.getSelection()?.removeAllRanges();
};
1
2
3
上次更新: 11/1/2024