滚动
const scrollToTop = (el = window) => {
el.scrollTo({ top: 0, left: 0, behavior: "smooth" });
};
1
2
3
const scrollToBottom = (el = window) => {
el.scrollTo({
top: document.documentElement.offsetHeight,
left: 0,
behavior: "smooth",
});
};
1
2
3
4
5
6
7
const smoothScroll = (el) => {
el.scrollIntoView({
behavior: "smooth",
});
};
1
2
3
4
5
全屏显示
全屏显示只能由用户发起,可以添加EventListener由用户出发
const goToFullScreen = (element = document.body) => {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullScreen();
}
};
1
2
3
4
5
6
7
8
9
10
11
const goExitFullscreen = () => {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
};
1
2
3
4
5
6
7
8
9
10
11
<body>
<img src="https://coderhdy.com/assets/img/bg.svg" alt="">
<script>
const goToFullScreen = (element = document.body) => {
console.log(element);
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullScreen();
}
};
const goExitFullscreen = () => {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
};
const wrapper = document.querySelector(".wrapper");
const img = document.querySelector("img");
img.addEventListener("click", () => {
goToFullScreen(img);
});
</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
获取数据类型
const getType = (obj) => {
const ty = Object.prototype.toString.call(obj);
return ty.match(/\w+/g)[1];
}
1
2
3
4
跨平台停止事件冒泡
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
判断平台
const isMobile = () => {
const reg = /(iPhone|iPod|Android|ios|iOS|iPad|Backerry|WebOS|Symbian|Windows Phone|Phone)/i
return !!navigator.userAgent.match(reg);
};
1
2
3
4
const isAndroid = () => {
return /android/i.test(navigator.userAgent.toLowerCase());
};
1
2
3
const isIOS = () => {
let reg = /iPhone|iPad|iPod|iOS|Macintosh/i;
return reg.test(navigator.userAgent.toLowerCase());
};
1
2
3
4
const getExplorerInfo = () => {
let t = navigator.userAgent.toLowerCase();
return 0 <= t.indexOf("msie")
? {
type: "IE",
version: Number(t.match(/msie ([\d]+)/)[1]),
}
: !!t.match(/trident\/.+?rv:(([\d.]+))/)
? {
type: "IE",
version: 11,
}
: 0 <= t.indexOf("edge")
? {
type: "Edge",
version: Number(t.match(/edge\/([\d]+)/)[1]),
}
: 0 <= t.indexOf("firefox")
? {
type: "Firefox",
version: Number(t.match(/firefox\/([\d]+)/)[1]),
}
: 0 <= t.indexOf("chrome")
? {
type: "Chrome",
version: Number(t.match(/chrome\/([\d]+)/)[1]),
}
: 0 <= t.indexOf("opera")
? {
type: "Opera",
version: Number(t.match(/opera.([\d]+)/)[1]),
}
: 0 <= t.indexOf("Safari")
? {
type: "Safari",
version: Number(t.match(/version\/([\d]+)/)[1]),
}
: {
type: t,
version: -1,
};
};
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
const canHover = matchMedia("(hover: hover)").matches;
1
操作cookie
需要注意的是,document.cookie
方法一次只能对一个 cookie 进行设置或更新。
const parseCookie = (cookie = document.cookie) => {
if (cookie.trim().length === 0) return {};
return Object.fromEntries(cookie.split('; ').map(item => {
const [key, ...val] = item.split('=');
return [key, val.join("=")];
}));
}
1
2
3
4
5
6
7
const setCookieItem = (key, val) => {
return document.cookie = `${key}=${val}`;
}
1
2
3
const getCookieItem = (key, cookie = document.cookie) => {
if (cookie.trim().length === 0) return undefined;
const reg = new RegExp(`(?<=^${key}=|; ${key}=)[^\;]+`);
return cookie.match(reg)[0];
}
1
2
3
4
5
const delCookie = (key) => {
document.cookie = `${encodeURIComponent(key)}=;expires=${new Date()}`;
};
1
2
3
随机函数
const getRandomStr = (len) => {
len = len ?? Math.floor(Math.random() * 100) + 10;
const dir = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz123456789";
const dirLen = dir.length;
let ans = "";
for (let i = 0; i < len; i++) {
const s = dir[Math.floor(Math.random() * dirLen)];
ans += s;
}
return ans;
}
1
2
3
4
5
6
7
8
9
10
11
const getRandomNum = (min = 0, max = 100) => {
return Math.floor(Math.random() * (max - min)) + min;
}
1
2
3
const getRandomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
1
首字母大写
const upperCaseFirst = ([first, ...rest]) => first ? first.toUpperCase() + rest?.join("") : ""
1
span {
text-transform: capitalize;
}
1
2
3
shuffle洗牌函数
const shuffle = (arr) => Array.from(arr).sort(() => Math.random() - 0.5);
1
格式化数字
const num = 1234567890;
console.log(num.toLocaleString("en-US"));
console.log(num.toLocaleString('en-US', {
style: 'currency',
currency: 'USD'
}));
console.log(num.toLocaleString('zh-Hans-CN-u-nu-hanidec', {
useGrouping: false
}));
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 isValiDate = (...date) => !Number.isNaN(new Date(...date).valueOf());
1
- 测试
const isValiDate = (...date) => !Number.isNaN(new Date(...date).valueOf());
const d1 = new Date("20202asdf-1");
const d2 = new Date("2020-12-10");
console.log(isValiDate(d1));
console.log(isValiDate(d2));
1
2
3
4
5
const timeDif = (d1, d2) => Math.floor(Math.abs(+new Date(d1) - +new Date(d2)) / (1000 * 60 * 60 * 24));
1
- 测试
const timeDif = (d1, d2) => Math.floor(Math.abs(+new Date(d1) - +new Date(d2)) / (1000 * 60 * 60 * 24));
const d1 = new Date("2020-12-1");
const d2 = new Date("2020-12-10");
console.log(timeDif(d1, d2));
1
2
3
4
const first2today = today => Math.floor((+new Date(today) - +new Date(new Date(today).getFullYear().toString())) / (1000 * 60 * 60 * 24)) + 1;
1
- 测试
const first2today = today => Math.floor((+new Date(today) - +new Date(new Date(today).getFullYear().toString())) / (1000 * 60 * 60 * 24)) + 1;
console.log(first2today("2019-12-31"));
console.log(first2today("2020-12-31"));
1
2
3
4
- 格式化日期
const formatDate = (date, mark = "-") => new Date(date).toISOString().slice(0, 10).split("-").join(mark);
1
- 测试
const formatDate = (date, mark = "-") => new Date(date).toISOString().slice(0, 10).split("-").join(mark);
console.log(formatDate("2019-12-31"));
console.log(formatDate("2020-12-31", "/"));
1
2
3
4
- 格式化时间
const formatTime = (date, mark = ":") => new Date(date).toTimeString().slice(0, 8).split(":").join(mark);
1
- 测试
const formatTime = (date, mark = ":") => new Date(date).toTimeString().slice(0, 8).split(":").join(mark);
console.log(formatTime(Date.now()));
1
2
3
颜色
const rgb2Hex = (r, g, b, a) => {
let ans = "#" + ((r << 16) + (g << 8) + b).toString(16);
if (a !== undefined) {
ans += Math.floor(a * 255).toString(16).padStart(2, "0");
}
return ans;
};
1
2
3
4
5
6
7
const hex2Rgb = (hex) => {
if (hex.startsWith("#")) hex = hex.slice(1);
hex = hex.padEnd(6, "f");
const [r, g, b, a] = hex.match(/\w{2}/g);
return a === undefined ? `rgb(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)})` : `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${(parseInt(a, 16) / 255).toFixed(1)})`;
}
1
2
3
4
5
6
- 测试
const hex2Rgb = (hex) => {
if (hex.startsWith("#")) hex = hex.slice(1);
hex = hex.padEnd(6, "f");
const [r, g, b, a] = hex.match(/\w{2}/g);
return a === undefined ? `rgb(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)})` : `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${(parseInt(a, 16) / 255).toFixed(1)})`;
}
console.log(hex2Rgb('#fff'));
console.log(hex2Rgb('#ffffffff'));
1
2
3
4
5
6
7
8
9
const hex2rgba = (hex, opacity) => {
if (hex[0] === "#") {
hex = hex.slice(1);
}
const rgbArr = hex.padEnd(6, "f").match(/\w{2}/g);
return rgbArr.reduce((pre, item) => pre.replace("{color}", parseInt(`0x${item}`)), `rgba({color},{color},{color},${opacity})`);
}
console.log(hex2rgba("#fc5531", 0.2));
1
2
3
4
5
6
7
8
9
const randomRgb = () => `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`;
1
- 测试
const randomRgb = () => `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`;
for (let i = 0; i < 10; i++) {
console.log(randomRgb());
}
1
2
3
4
- rgba
const randomRgba = () => `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.random().toFixed(1)})`;
1
const randomHex = () => `#${Math.floor(Math.random() * 256).toString(16).padStart(2, "0")}`
+ `${Math.floor(Math.random() * 256).toString(16).padStart(2, "0")}`
+ `${Math.floor(Math.random() * 256).toString(16).padStart(2, "0")}`;
1
2
3
const randomHex = () => `#${Math.floor(Math.random() * 256).toString(16).padStart(2, "0")}`
+ `${Math.floor(Math.random() * 256).toString(16).padStart(2, "0")}`
+ `${Math.floor(Math.random() * 256).toString(16).padStart(2, "0")}`
+ `${Math.floor(Math.random() * 256).toString(16).padStart(2, "0")}`;
1
2
3
4
.gray {
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
filter: grayscale(100%);
-webkit-filter: gray;
filter: gray;
-webkit-filter: progid:dximagetransform.microsoft.basicimage(grayscale=1);
filter: progid:dximagetransform.microsoft.basicimage(grayscale=1);
}
1
2
3
4
5
6
7
8
9
10
11
复制粘贴
const copyToClipboard = (text) => navigator.clipboard.writeText(text);
1
const readText = () => navigator.clipboard.readText().then(res => console.log(res));
1
el.addEventListener('copy', e => {
console.log(e);
const clipboardData = e.clipboardData || window.clipboardData;
e.preventDefault();
})
1
2
3
4
5
6
7
8
9
const getSelectedText = () => window.getSelection().toString();
getSelectedText();
1
2
3
<body>
<div id="div">123234213423423家阿克琉斯的积分啦4</div>
<script>
div.addEventListener("click", function () {
const val = this.innerText;
copyToClipboard(val);
});
const copyToClipboard = (text) => {
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.top = "200vh";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand("copy");
if (!successful) {
navigator.clipboard.writeText(virtualCardNumber.value);
}
} catch (err) {
console.log("Oops, unable to copy");
}
document.body.removeChild(textArea);
}
</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
暗黑模式
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
console.log(isDarkMode);
1
2
3
检测是否激活本网页
const isTabInView = () => !document.hidden;
1
touchmove事件打平
- 鼠标
mousemove
和touch
事件取x和y方法不一样 - mouse有移出屏幕的风险,会导致监听触发不符合预期
const moveListener = (el, callback) => {
let mouseDown = false;
el.addEventListener("touchmove", e => callback(e.targetTouches[0].clientX, e.targetTouches[0].clientY));
el.addEventListener("mousedown", () => mouseDown = true);
el.addEventListener("mouseup", () => mouseDown = false);
el.addEventListener("mouseout", () => mouseDown = false);
document.addEventListener("mouseout", () => mouseDown = false);
el.addEventListener("mousemove", (e) => {
if (!mouseDown) return;
callback(e.clientX, e.clientY);
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./index.css">
<title>Document</title>
</head>
<body>
<div id="position">x: 0 y: 0</div>
<style>
* {
margin: 0;
padding: 0;
}
body {
width: 100vw;
height: 100vh;
}
</style>
<script>
const body = document.body;
const position = document.querySelector("#position");
const moveListener = (el, callback) => {
let mouseDown = false;
el.addEventListener("touchmove", e => callback(e.targetTouches[0].clientX, e.targetTouches[0].clientY));
el.addEventListener("mousedown", () => mouseDown = true);
el.addEventListener("mouseup", () => mouseDown = false);
el.addEventListener("mouseout", () => mouseDown = false);
document.addEventListener("mouseout", () => mouseDown = false);
el.addEventListener("mousemove", (e) => {
if (!mouseDown) return;
callback(e.clientX, e.clientY);
})
}
const handleMove = (x, y) => {
position.innerText = `x: ${x} y: ${y} `;
}
moveListener(body, handleMove);
</script>
</body>
</html>
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
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) {
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
防抖节流
const throttle: Throttle = (fn, sleep = 200) => {
let timer: ReturnType<typeof setTimeout> | null;
return () => {
if (timer) return;
timer = setTimeout(() => {
fn();
timer = null;
}, sleep);
return () => {
timer = null;
};
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
- react hook
- 目的:先更新UI,再发请求更新数据
type UseUIDebounce = <T>(
asyncFn: (p: T) => any,
genKeyFn?: (p: T) => string,
time?: number
) => (p: T) => any;
const useUIDebounce: UseUIDebounce = (asyncFn, genKeyFn, time = 1000) => {
const singleTimer = useRef<any>();
const multiTimer = useRef(new Map());
return (p) => {
if (typeof genKeyFn === "function") {
const key = genKeyFn(p);
if (multiTimer.current.has(key)) {
clearTimeout(multiTimer.current.get(key));
}
multiTimer.current.set(
key,
setTimeout(() => {
asyncFn(p);
multiTimer.current.delete(key);
}, time)
);
} else {
clearTimeout(singleTimer.current);
singleTimer.current = setTimeout(() => {
asyncFn(p);
singleTimer.current = null;
}, time);
}
};
};
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
const updateFn = async (param) => {
try {
await post("api/xxxxx", param)
} catch {
return "failed";
}
};
const debounceUpdate = useUIDebounce(
updateFn,
({ key }) => key
);
const doSomeThing = () => {
setList(newList);
void debounceUpdate(newList);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const throttle: Throttle = (fn, sleep = 200) => {
let timer: ReturnType<typeof setTimeout> | null;
return () => {
if (timer) return;
timer = setTimeout(() => {
fn();
timer = null;
}, sleep);
return () => {
timer = null;
};
};
};
interface UseWindowResize {
(callback: () => void, throttleTime?: number): void;
}
const useWindowResize: UseWindowResize = (callback, throttleTime = 200) => {
const throttledUpdateWidth = throttle(callback, throttleTime);
useEffect(() => {
window.addEventListener("resize", throttledUpdateWidth);
return () => window.removeEventListener("resize", throttledUpdateWidth);
}, [throttledUpdateWidth]);
useEffect(() => {
callback();
}, []);
};
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
手势
import React, { useRef, useEffect, useState } from "react";
const throttle = (fn, sleep = 200) => {
let timer = null;
return () => {
if (timer) return;
timer = setTimeout(() => {
fn();
timer = null;
}, sleep);
return () => {
timer = null;
};
};
};
const useWindowResize = (callback, throttleTime = 200) => {
const throttledUpdateWidth = throttle(callback, throttleTime);
useEffect(() => {
window.addEventListener("orientationchange", throttledUpdateWidth);
window.addEventListener("resize", throttledUpdateWidth);
return () => {
window.removeEventListener("resize", throttledUpdateWidth);
window.removeEventListener("orientationchange", throttledUpdateWidth);
};
}, [throttledUpdateWidth]);
useEffect(() => {
callback();
}, []);
};
const init = ({ onStart, onMove, onEnd }, target = document.body) => {
let listen = false;
const domWidth = document.body.clientWidth;
const domHeight = document.body.clientHeight;
const initStarInfo = {
time: new Date(),
x: 0,
y: 0,
xPercent: 0,
yPercent: 0,
};
let starInfo = initStarInfo;
let moveInfo = {
x: 0,
y: 0,
xMoved: 0,
yMoved: 0,
xMovePercent: 0,
yMovePercent: 0,
xSpeed: 0,
ySpeed: 0,
};
const onTouchStart = (e) => {
const touch = e.changedTouches[0];
starInfo = {
time: +new Date(),
x: touch.clientX,
y: touch.clientY,
xPercent: touch.clientX / domWidth,
yPercent: touch.clientY / domHeight,
};
if (onStart) {
const newListen = onStart(starInfo);
listen = onStart && newListen === false ? false : true;
}
};
const onTouchMove = (e) => {
if (!listen || !onMove) return;
const touch = e.changedTouches[0];
const x = touch.clientX;
const y = touch.clientY;
const xMoved = touch.clientX - starInfo.x;
const yMoved = touch.clientY - starInfo.y;
const xMovePercent = Math.abs(touch.clientX - starInfo.x) / domWidth;
const yMovePercent = Math.abs(touch.clientY - starInfo.y) / domHeight;
const movedTime = +new Date() - starInfo.time;
moveInfo = {
x,
y,
xMoved,
yMoved,
xMovePercent,
yMovePercent,
xSpeed: xMoved / movedTime,
ySpeed: yMoved / movedTime,
};
onMove?.(moveInfo);
};
const onTouchEnd = (e) => {
if (!listen || !onEnd) return;
const touch = e.changedTouches[0];
const x = touch.clientX;
const y = touch.clientY;
const xMoved = touch.clientX - starInfo.x;
const yMoved = touch.clientY - starInfo.y;
const xMovePercent = Math.abs(touch.clientX - starInfo.x) / domWidth;
const yMovePercent = Math.abs(touch.clientY - starInfo.y) / domHeight;
const movedTime = +new Date() - starInfo.time;
moveInfo = {
x,
y,
xMoved,
yMoved,
xMovePercent,
yMovePercent,
xSpeed: xMoved / movedTime,
ySpeed: yMoved / movedTime,
};
starInfo = initStarInfo;
onEnd?.(moveInfo);
};
const onTouchCancel = (e) => {
if (!listen || !onEnd) return;
const touch = e.changedTouches[0];
const x = touch.clientX;
const y = touch.clientY;
const xMoved = touch.clientX - starInfo.x;
const yMoved = touch.clientY - starInfo.y;
const xMovePercent = Math.abs(touch.clientX - starInfo.x) / domWidth;
const yMovePercent = Math.abs(touch.clientY - starInfo.y) / domHeight;
const movedTime = +new Date() - starInfo.time;
moveInfo = {
x,
y,
xMoved,
yMoved,
xMovePercent,
yMovePercent,
xSpeed: xMoved / movedTime,
ySpeed: yMoved / movedTime,
};
starInfo = initStarInfo;
onEnd?.(moveInfo);
};
target.addEventListener("touchstart", onTouchStart);
target.addEventListener("touchmove", onTouchMove);
target.addEventListener("touchend", onTouchEnd);
target.addEventListener("touchcancel", onTouchCancel);
const destroy = () => {
target.removeEventListener("touchstart", onTouchStart);
target.removeEventListener("touchmove", onTouchMove);
target.removeEventListener("touchend", onTouchEnd);
target.removeEventListener("touchcancel", onTouchCancel);
};
return destroy;
};
const useSwipe = ({ onStart, onMove, onEnd }, target) => {
const [changeTime, setCurrentTime] = useState(new Date());
const first = useRef(true);
useWindowResize(() => {
if (first.current) {
first.current = false;
return;
}
setCurrentTime(new Date());
});
useEffect(() => {
const destroy = init({ onStart, onMove, onEnd }, target);
return destroy;
}, [target, changeTime]);
};
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
const fn = () => {
const swipeRef = useRef();
const onStart = (startInfo) => {
return startInfo.x < 100;
};
const onEnd = (info) => {
if (info.xSpeed > 0.5 || (info.xMovePercent > 0.4 && info.yMoved < 100)) {
console.log("触发侧边栏展开");
}
};
useSwipe({ onStart, onEnd }, swipeRef.current);
return <></>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 只需要管开始和结束(
touchstart
,touchend
),touchmove
可以不要 - 还要监听手机横屏影响
import React, { useRef, useEffect, useState } from "react";
import { useWindowResize } from "./useWindowResize";
interface UseWindowResize {
(callback: () => void, throttleTime?: number): void;
}
const useWindowResize: UseWindowResize = (callback, throttleTime = 200) => {
const throttledUpdateWidth = throttle(callback, throttleTime);
useEffect(() => {
window.addEventListener("resize", throttledUpdateWidth);
return () => window.removeEventListener("resize", throttledUpdateWidth);
}, [throttledUpdateWidth]);
useEffect(() => {
callback();
}, []);
};
const throttle: Throttle = (fn, sleep = 200) => {
let timer: ReturnType<typeof setTimeout> | null;
return () => {
if (timer) return;
timer = setTimeout(() => {
fn();
timer = null;
}, sleep);
return () => {
timer = null;
};
};
};
type StarInfo = {
time: number;
x: number;
y: number;
xPercent: number;
yPercent: number;
};
type MoveInfo = {
x: number;
y: number;
xMoved: number;
yMoved: number;
xMovePercent: number;
yMovePercent: number;
xSpeed: number;
ySpeed: number;
};
type UseGestures = (
events: {
onStart?: (startInfo: StarInfo) => boolean | undefined;
onEnd?: (moveInfo: MoveInfo) => void;
},
target?: HTMLElement
) => (() => void) | void;
const init: UseGestures = ({ onStart, onEnd }, target = document.body) => {
let listen = true;
const domWidth = document.body.clientWidth;
const domHeight = document.body.clientHeight;
const initStarInfo = {
time: +new Date(),
x: 0,
y: 0,
xPercent: 0,
yPercent: 0,
};
let starInfo: StarInfo = initStarInfo;
let moveInfo: MoveInfo = {
x: 0,
y: 0,
xMoved: 0,
yMoved: 0,
xMovePercent: 0,
yMovePercent: 0,
xSpeed: 0,
ySpeed: 0,
};
const onTouchStart = (e: TouchEvent) => {
const touch = e.changedTouches[0];
starInfo = {
time: +new Date(),
x: touch.clientX,
y: touch.clientY,
xPercent: touch.clientX / domWidth,
yPercent: touch.clientY / domHeight,
};
if (onStart) {
const newListen = onStart(starInfo);
listen = onStart !== undefined && newListen === false ? false : true;
}
};
const onTouchEnd = (e: TouchEvent) => {
if (!listen || !onEnd) return;
const touch = e.changedTouches[0];
const x = touch.clientX;
const y = touch.clientY;
const xMoved = touch.clientX - starInfo.x;
const yMoved = touch.clientY - starInfo.y;
const xMovePercent = Math.abs(touch.clientX - starInfo.x) / domWidth;
const yMovePercent = Math.abs(touch.clientY - starInfo.y) / domHeight;
const movedTime = +new Date() - starInfo.time;
moveInfo = {
x,
y,
xMoved,
yMoved,
xMovePercent,
yMovePercent,
xSpeed: xMoved / movedTime,
ySpeed: yMoved / movedTime,
};
starInfo = initStarInfo;
onEnd?.(moveInfo);
};
const onTouchCancel = (e: TouchEvent) => {
if (!listen || !onEnd) return;
const touch = e.changedTouches[0];
const x = touch.clientX;
const y = touch.clientY;
const xMoved = touch.clientX - starInfo.x;
const yMoved = touch.clientY - starInfo.y;
const xMovePercent = Math.abs(touch.clientX - starInfo.x) / domWidth;
const yMovePercent = Math.abs(touch.clientY - starInfo.y) / domHeight;
const movedTime = +new Date() - starInfo.time;
moveInfo = {
x,
y,
xMoved,
yMoved,
xMovePercent,
yMovePercent,
xSpeed: xMoved / movedTime,
ySpeed: yMoved / movedTime,
};
starInfo = initStarInfo;
onEnd?.(moveInfo);
};
target.addEventListener("touchstart", onTouchStart);
target.addEventListener("touchend", onTouchEnd);
target.addEventListener("touchcancel", onTouchCancel);
const destroy = () => {
target.removeEventListener("touchstart", onTouchStart);
target.removeEventListener("touchend", onTouchEnd);
target.removeEventListener("touchcancel", onTouchCancel);
};
return destroy;
};
const useGestures: UseGestures = ({ onStart, onEnd }, target) => {
useEffect(() => {
const destroy = init({ onStart, onEnd }, target);
return destroy;
}, [target, onStart, onEnd]);
};
export { useGestures };
export type { StarInfo, MoveInfo };
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
const onOpenStart = (startInfo: StarInfo) => {
return startInfo.x < 100;
};
const onOpenEnd = (info: MoveInfo) => {
const inHomePath = location.pathname === "/";
const opCondition = info.xSpeed > 0.5 || (info.xMovePercent > 0.5 && info.yMoved < 100);
if (inHomePath && opCondition) {
setMobileSide(true);
}
};
useGestures({ onStart: onOpenStart, onEnd: onOpenEnd });
1
2
3
4
5
6
7
8
9
10
11
12
13
检测
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手机适配
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
字数计算/截断
防颜文字占两位或多位
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;
} else {
totalBytes += 1;
}
if (totalBytes > 1024 * 1024) {
return true;
}
}
return false;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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链接转化
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
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
<body>
<button id="downloadBtn">下载</button>
<script>
const bufferFileDownload = (fileBuffer, fileName, mimeType) => {
const fileData = new Blob([fileBuffer], {
type: mimeType ?? "application/octet-stream",
});
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(fileData, fileName);
}
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;
}
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