页面加载时间
//在页面头部以同步的方式加载 js 代码
var start_time = new Date();
var end_time = "";
window.onload = function () {
end_time = new Date();
// do something
};
页面停留时间
-
监听
pageshow
let start = Date.now();
function loadHandler(e) {
start = Date.now();
}
function unloadHandler(e) {
// 如果用户一直不关闭页面,可能出现超大值,可以根据业务需要处理,例如设置一个上限
const duration = Date.now() - start;
// do something
}
if ("onpageshow" in window) {
addEvent(window, "pageshow", loadHandler);
addEvent(window, "pagehide", unloadHandler);
} else {
addEvent(window, "load", loadHandler);
addEvent(window, "unload", unloadHandler);
}
function addEvent(target, type, listener) {
if (window.addEventListener) {
target.addEventListener(type, listener, false);
} else {
target.attachEvent("on" + type, listener);
}
}
图片转 base64
加载图片
html2canvas 图片如果不显示,可以先将图片转成 base64
- 加载图片
/** * 加载图片 * @param {String} src 资源地址 * @return Promise(Image) */ function loadImg(src) { return new Promise((resolve, reject) => { const img = new Image(); // listen event img.onload = () => { // do something // example Image to base64 resolve(img); }; img.onerror = reject; // attr img.setAttribute("crossOrigin", "anonymous"); img.src = src; }); }
-
使用
canvas.toDataURL()/** * 图片转 base64 格式 * @param {Image} img * @returns Data URLs */ function imgToBase64(img) { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = img.width canvas.height = img.height ctx.drawImage(img, 0, 0, img.width, img.height) // 获取 dataURL const dataURL = canvas.toDataURL() // canvas.toBlob() 可以获取 blob 数据 // 无法直接作为<img>的src属性值呈现的,需要URL.createObjectURL()方法处理下 return dataURL } -
通过
request/** * 图片 src 转 base64 格式 * @param {String} src 资源地址 * @returns Promise(Data URLs) */ function loadBase64Img(src) { const data = await fetch(src); const blob = await data.blob(); return new Promise(resolve => { const reader = new FileReader(); reader.onloadend = function (e) { resolve(e.target.result); }; reader.readAsDataURL(blob); }); }
下载文件
参考链接
-
单个文件
如果图片返回头是 Content-Type: application/octet-stream可以使用
window.open()直接下载图片/** * 下载单个图片 * @param {dataURL | URL} img * @returns */ async function downloadImg(img) { // base64 转 Blob const base64ToBlob = code => { const parts = code.split(";base64,"); const contentType = parts[0].split(":")[1]; const raw = window.atob(parts[1]); const rawLength = raw.length; const uInt8Array = new Uint8Array(rawLength); for (let i = 0; i < rawLength; i++) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array], { type: contentType }); }; // http 获取 Blob const httpToBlob = async url => { const data = await fetch(url); return data.blob(); }; // 创建文件流 let blob = null; if (/^https?/.test(img)) { blob = await httpToBlob(img); } else { blob = base64ToBlob(img); } // create download const a = document.createElement("a"); a.download = "test"; // filename a.href = URL.createObjectURL(blob); a.click(); } -
多个文件 zip 下载
import JSZip from "jszip"; import { saveAs } from "file-saver"; /** * 下载多个图片 zip * @param {Array[dataURL]} imgs base64 图片列表 */ function downloadMultipleImg(imgs) { const zip = new JSZip(); const folder = zip.folder("images"); imgs.forEach(img => { // base64 格式 const base64 = img.split(",")[1]; folder.file(`filename.png`, base64, { base64: true, }); }); zip.generateAsync({ type: "blob" }).then(content => { // see FileSaver.js saveAs(content, `folderName.zip`); }); }
复制到剪切板
iframe 嵌入需要添加属性
allow="clipboard-read;clipboard-write"
safari会报没有权限错误
-
使用第三方插件 clipboard.js
-
文字
-
游览器全局属性 Navigator.clipboard
/** * 复制文字到剪贴板 * @param {String} newClipText */ function copyTextToClipboard(newClipText) { return navigator.clipboard.writeText(newClipText); } -
/** * 复制文字到剪贴板 * @param {String} text */ function copyTextToClipboard(text) { const el = document.createElement("textarea"); el.value = text; document.body.appendChild(el); el.select(); try { document.execCommand("Copy"); } catch (error) { // do something } document.body.removeChild(el); }
-
-
图片
/** * 复制图片到剪贴板 * @param {DOM} element Event.target * @returns */ async function copyImgToClipboard(element) { // not image dom if (element.nodeName.toLowerCase() !== "img" || !element.src) return; // Browser does not support if (!navigator?.clipboard?.write || !window.ClipboardItem) { // do something return; } const data = await fetch(element.src); const blob = await data.blob(); try { await navigator.clipboard.write([ new window.ClipboardItem({ [blob.type]: blob, }), ]); // success // do something } catch (error) { // do something console.log("copyImgToClipboard error", error); } }
判断文本是否溢出
参考链接
-
元素的
clientWidth < scrollWidth代表文本溢出// 某些浏览器会出现,文本溢出了,但 clientWidth 与 scrollWidth 相等 const isOverflow = el => el.clientWidth < el.scrollWidth- 元素 display 需要设置为
inline-block-
inline元素的 clientWidth & scrollWidth 为 0 -
block宽度不是内容撑开
-
canvas 的
ctx.measureText().width可以获取文本宽度 - 元素 display 需要设置为
-
// width + leftPadding + rightPadding < offsetWidth // scrollWidth > offsetWidth
文本溢出(兼容)
重要 css 属性
white-spaceoverflow-wrapword-break
-
单行文本
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; -
多行文本溢出
overflow: hidden; text-overflow: ellipsis; display: -webkit-box; /* 文本的行数 */ -webkit-line-clamp: 3; /* 排列方式 */ /* -webkit-box-orient: vertical; */ -
多行文本溢出(纯css兼容)
实现防篡改水印
参考链接:
防君子不防小人
F12 禁用 javascript 将失效
-
canvas实现水印import { computed } from 'vue' export default function useWatermarkBg(props) { return computed(() => { const canvas = document.createElement('canvas') const devicePixelRatio = window.devicePixelRatio || 1 // 设置字体大小 const fontSize = props.fontSize * devicePixelRatio const font = fontSize + 'px serif' const ctx = canvas.getContext('2d') ctx.font = font const { width } = ctx.measureText(props.text) // 获取文字宽度 const canvasSize = Math.max(100, width) + props.gap * devicePixelRatio canvas.width = canvasSize canvas.height = canvasSize ctx.translate(canvas.width / 2, canvas.height / 2) // 旋转 45 度让文字变倾斜 ctx.rotate((Math.PI / 180) * -45) ctx.fillStyle = 'rgba(0, 0, 0, 0.3)' ctx.font = font ctx.textAlign = 'center' ctx.textBaseline = 'middle' ctx.fillText(props.text, 0, 0) return { base64: canvas.toDataURL(), size: canvasSize, styleSize: canvasSize / devicePixelRatio, } }) } -
挂载 dom
{ zIndex: props.zIndex, position: 'absolute', left: 0, top: 0, width: '100%', height: '100%', pointerEvents: 'none', // 元素永远不会成为鼠标事件的target backgroundRepeat: 'repeat', // 重复绘制图片 } -
防篡改
let ob onMounted(() => { ob = new MutationObserver(records => { // 循环节点的动作 for (const record of records) { // 如果有节点被删除,循环一下判断是否有水印的节点 for (const dom of record.removedNodes) { if (dom === div) { console.log('水印被删除') // ... return } } // 如果有节点被修改,判断一下是否是水印的节点 if (record.target === div) { console.log('属性被修改') // ... return } } }) ob.observe(parentRef.value, { childList: true, attributes: true, subtree: true, }) }) // 在组件卸载的时候取消监听 onUnmounted(() => { ob && ob.disconnect() // 取消监听 div = null // 因为 div 是全局变量在写在的时候值为空 })