lazy-js-utils
Version:
A collection of lazy-loaded JavaScript utilities for efficient development
1,548 lines (1,514 loc) • 198 kB
JavaScript
"use strict";
const require_chunk = require('./chunk-BCwAaXi7.cjs');
const require_parallel = require('./parallel-BSxIacyY.cjs');
const require_isBool = require('./isBool-B9jCuCG_.cjs');
const require_createElement = require('./createElement-BuKczUzZ.cjs');
const require_createChunk = require('./createChunk-CbS_Z8ta.cjs');
const require_toAbsolutePath = require('./toAbsolutePath-DwpNBNau.cjs');
const node_process = require_chunk.__toESM(require("node:process"));
const htmlparser2 = require_chunk.__toESM(require("htmlparser2"));
const qrcode = require_chunk.__toESM(require("qrcode"));
//#region src/array/diff.ts
/**
*
* @param { any[] } array1 数组1
* @param { any[] } array2 数组2
* @param { Options } [options] {}
* @param { 'same' | 'different' } [options.compare] 'same' | 'different'
* @param { 'value' | 'index' } [options.compresultare] 'value' | 'index'
* @returns 返回相同项或不同项的索引或值
*/
function diff(array1, array2, options = {
compare: "same",
result: "value"
}) {
const same = array1.filter((item) => array2.includes(item));
const diff$1 = array1.filter((item) => !array2.includes(item)).concat(array2.filter((item) => !array1.includes(item)));
const { compare = "same", result = "value" } = options;
if (compare === "same") return result === "value" ? same : same.map((item) => array1.indexOf(item));
return result === "value" ? splitDiff(diff$1) : diff$1.map((item) => array1.indexOf(item)).filter((i) => i >= 0);
}
function splitDiff(arr) {
if (!arr.length) return arr;
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
const result = [];
for (let i = 0; i < mid; i++) result.push([left[i], right[i]]);
return result;
}
//#endregion
//#region src/array/forEach.ts
/**
*
* @param { any[] } array 数组
* @param { Function } callback ForEachCallback
* @returns
*/
function forEach(array, callback) {
for (let i = 0; i < array.length; i++) {
const res = callback(array[i], i, array);
if (!require_parallel.isUndef(res)) return res;
}
return void 0;
}
//#endregion
//#region src/array/getAverage.ts
/**
*
* @param { number[] } array 数字数组
* @param { number } fraction 保留几位小数
* @returns 平均值
*/
function getAverage(array, fraction = 2) {
return (array.reduce((pre, cur) => pre + cur) / array.length).toFixed(fraction);
}
//#endregion
//#region src/js/executeStr.ts
function executeStr(str) {
return new Function(`return (${str})`)();
}
//#endregion
//#region src/array/quickFilter.ts
/**
*
* @param { any[] } array 数组
* @param { string | Array<string> } key 过滤条件
* @returns
*/
function quickFilter(array, key) {
const reg = /\/[\w. $]+\/[gims]*/;
return array.filter((item) => {
if (require_isBool.isArray(key)) return key.some((k) => findItem(item, k));
else return findItem(item, key);
});
function findItem(item, key$1) {
const [_key, _value] = key$1.split("=");
if (require_parallel.isUndef(item[_key])) return false;
return require_parallel.isDef(_value) ? reg.test(_value) ? new RegExp(executeStr(_value)).test(item[_key]) : _value === item[_key] : /.*/.test(item[key$1]);
}
}
//#endregion
//#region src/array/quickFind.ts
/**
*
* @param { T[] } array 数组
* @param { string | number } id 主键
* @returns
*/
function quickFind(array, id) {
const indexMap = new Map();
array.forEach((item, i) => indexMap.set(item[id], i));
return new QuickFind(array, indexMap, id);
}
var QuickFind = class {
constructor(array, indexMap, id) {
this.array = array;
this.indexMap = indexMap;
this.id = id;
this.array = array;
this.indexMap = indexMap;
this.id = id;
}
find(id) {
const index = this.indexMap.get(id);
if (require_parallel.isUndef(index)) return void 0;
return this.array[index];
}
_update(id, key, value) {
if (require_parallel.isUndef(key)) {
const index = this.indexMap.get(id);
if (require_parallel.isUndef(index)) throw new Error("当前id不存在");
if (value[this.id] !== id) throw new Error("不可修改唯一id");
this.array[index] = value;
} else {
const target = this.find(id);
if (require_parallel.isUndef(target)) return this.array;
target[key] = value;
}
return this.array;
}
delete(id) {
const index = this.indexMap.get(id);
if (require_parallel.isUndef(index)) return;
this.array.splice(index, 1);
this.indexMap.delete(id);
return this.array;
}
set(id, key, value) {
let tempValue = value;
const index = this.indexMap.get(id);
if (require_parallel.isUndef(value)) {
if (require_parallel.isUndef(key)) return this.array;
tempValue = key;
}
if (require_parallel.isDef(index)) return this._update(id, key, tempValue);
else {
if (!value) value = key;
if (require_parallel.isUndef(value[this.id])) throw new Error("新增的数据必须包含唯一id");
if (value[this.id] !== id) throw new Error("新增的数据id必须与当前id一致");
this.indexMap.set(id, this.array.length);
this.array.push(value);
return this.array;
}
}
};
//#endregion
//#region src/array/sort.ts
/**
*
* @param { any[] } array 数组
* @param { Array<string | number> | number | string } match 匹配条件
* @returns
*/
function sort(array, match) {
if (require_parallel.isType(match, "s|n")) match = [`${match}`];
return match.reduce((result, cur) => {
let flag = false;
if (cur[0] === "-") {
flag = true;
cur = cur.slice(1);
}
return result.sort((a, b) => {
if (cur !== "1" && b[cur] === a[cur]) return 0;
if (flag) {
if (cur === "1") return b > a ? 1 : -1;
return b[cur] > a[cur] ? 1 : -1;
}
if (cur === "1") return a > b ? 1 : -1;
return b[cur] > a[cur] ? -1 : 1;
});
}, array);
}
//#endregion
//#region src/array/sortByOrder.ts
/**
*
* @param { any[] } sortArr 数组
* @param { string[] } order 顺序
* @param { string } prop 按照哪个属性
* @returns
*/
function sortByOrder(sortArr, order, prop) {
if (!order) return sortArr;
const result = [];
let insertIndex;
const _prop = prop ? prop.split(".") : void 0;
order.forEach((key, idx) => {
if (key === "*") return insertIndex = idx;
const index = sortArr.findIndex((item) => getDepthVal(_prop, item) === key);
if (index !== -1) {
result.push(sortArr[index]);
sortArr.splice(index, 1);
}
});
if (require_parallel.isDef(insertIndex)) result.splice(insertIndex, 0, ...sortArr);
else result.concat(sortArr);
return result;
function getDepthVal(_prop$1, item) {
return _prop$1 ? _prop$1.reduce((result$1, cur) => {
return result$1 === null || result$1 === void 0 ? void 0 : result$1[cur];
}, item) : item;
}
}
//#endregion
//#region src/array/uniqueArray.ts
/**
*
* @param { any[] } array 数组
* @returns 去重后的数组
*/
function uniqueArray(array) {
return array.reduce((result, item) => {
if (require_parallel.isType(item, "o|a") && !isHave(result, item) || !result.includes(item)) result.push(item);
return result;
}, []);
}
function equals(a, b) {
if (Object.keys(a).length !== Object.keys(b).length) return false;
for (const key in a) if (require_parallel.isType(a[key], "o|a") && require_parallel.isType(b[key], "o|a") && !equals(a[key], b[key]) || a[key] !== b[key]) return false;
return true;
}
function isHave(result, item) {
return result.some((i) => require_parallel.isType(i, "o|a") && equals(item, i));
}
//#endregion
//#region src/array/chunk.ts
/**
*
* @param { T[] } arr 数组
* @param { number } size 以多少为基准分割
* @returns 分割后的数组
*/
function chunk(arr, size = 1) {
if (size < 1) return [];
const result = [];
for (let i = 0; i < arr.length; i += size) result.push(arr.slice(i, i + size));
return result;
}
//#endregion
//#region src/array/countBy.ts
/**
*
* @param { any[] } array 数组
* @param { Function } iterator 迭代数组函数
* @returns 结果的个数
*/
function countBy(array, iterator) {
return array.reduce((result, item) => {
const val = iterator(item);
if (!result[val]) result[val] = 1;
else result[val]++;
return result;
}, {});
}
//#endregion
//#region src/array/flatten.ts
/**
*
* @param { Record<string, any> | Record<string, any>[] } o 对象或者数组
* @param { string }flattenProps 展开的属性默认为children
* @param { boolean }onlyLastNode 只保留最后一层级的数据
* @returns 一层的数组
*/
function flatten(o, flattenProps = "children", onlyLastNode = false, result = []) {
o = require_isBool.isArray(o) ? o : [o];
o.forEach((node) => {
const children = node[flattenProps];
if (!onlyLastNode) result.push(node);
if (children) flatten(children, flattenProps, onlyLastNode, result);
else if (onlyLastNode) result.push(node);
});
return result;
}
//#endregion
//#region src/array/filterEmpty.ts
/**
*
* @param { any[] } array 数组
* @returns 过滤空值后的数组
*/
function filterEmpty(array) {
return array.filter(Boolean);
}
//#endregion
//#region src/canvas/Canvas.ts
var Canvas = class {
canvas = document.createElement("canvas");
ctx = this.canvas.getContext("2d");
constructor(width, height) {
if (width) this.canvas.width = width * devicePixelRatio;
if (height) this.canvas.height = height * devicePixelRatio;
return this;
}
};
//#endregion
//#region src/event/insertElement.ts
/**
* 插入元素
* @param { HTMLElement | string } parent 父元素
* @param { HTMLElement | DocumentFragment | string } element 将被插入的元素
* @param { HTMLElement } target 插入在这个元素之前
* @returns
*/
function insertElement(parent$1, element, target) {
return require_createElement.mount(parent$1, element, (parent$2, element$1) => parent$2.insertBefore(element$1, require_isBool.isNull(target) ? null : target || parent$2.firstChild));
}
//#endregion
//#region src/event/removeElement.ts
/**
* 删除元素
* @param { HTMLElement | ChildNode | DocumentFragment | string } el 待被删除的节点
* @returns 父节点
*/
function removeElement(el) {
if (require_isBool.isStr(el)) el = require_createElement.findElement(el) || el;
if (require_isBool.isStr(el)) throw new Error(`${el} is not a element`);
const p = el.parentElement;
if (p) p.removeChild(el);
return p;
}
//#endregion
//#region src/event/useEventListener.ts
/**
* 事件监听
* @param { Window | Document | Element | string } target 元素
* @param { T } eventName 事件名
* @param { (e: (WindowEventMap & DocumentEventMap)[T]) => void } callback 回调
* @param { boolean | AddEventListenerOptions } useCapture 捕获
* @param { boolean } autoRemove 是否自动被移除
* @returns 停止
*/
function useEventListener(target, eventName, callback, useCapture, autoRemove) {
let stopped = false;
let stop;
let animationStop;
if (eventName === "DOMContentLoaded") stopped = true;
function event(e) {
try {
var _callback$call;
callback === null || callback === void 0 || (_callback$call = callback.call) === null || _callback$call === void 0 || _callback$call.call(callback, e.target, e);
} catch (error) {
animationStop === null || animationStop === void 0 || animationStop();
throw new Error(error);
}
if (autoRemove) stop();
}
require_createElement.unmount(() => stop === null || stop === void 0 ? void 0 : stop());
require_createElement.mount(target, (target$1) => {
const originCall = target$1 === null || target$1 === void 0 ? void 0 : target$1[eventName];
const eventFunction = (e) => {
if (stopped) stop === null || stop === void 0 || stop();
try {
const isRawEvent = originCall && originCall.toString().includes("() { [native code] }");
if (!isRawEvent && originCall) originCall === null || originCall === void 0 || originCall();
} catch (error) {
console.error(error);
}
event(e);
};
target$1.addEventListener(eventName, eventFunction, useCapture);
let mutationStop;
if (target$1 instanceof Element && target$1.parentNode) mutationStop = require_createElement.useMutationObserver(target$1.parentNode, (mutations) => {
for (const mutation of mutations) for (const node of Array.from(mutation.removedNodes)) if (node === target$1) {
stop === null || stop === void 0 || stop();
mutationStop === null || mutationStop === void 0 || mutationStop();
}
});
stop = () => {
target$1.removeEventListener(eventName, eventFunction, useCapture);
mutationStop === null || mutationStop === void 0 || mutationStop();
};
});
return () => {
if (!stop) return stopped = true;
stop === null || stop === void 0 || stop();
};
}
//#endregion
//#region src/event/useKeyBoard.ts
/**
* 检测指定按键
* @param { string } c 按键字符串
* @param { Function } callback 按键与案件字符串一致时的回调
* @returns 停止
*/
function useKeyBoard(c, callback) {
return useEventListener(document, "keydown", (e) => {
let code = "";
const key = e.key;
if (e.shiftKey && key !== "Shift") code += "Shift+";
if (e.ctrlKey && key !== "Control") code += "Ctrl+";
if (e.altKey && key !== "Alt") code += "Alt+";
if (e.metaKey && key !== "Meta") code += "Meta+";
if (e.code === "Space") code += "Space";
code += key;
if (code === c) callback(code);
});
}
//#endregion
//#region src/canvas/CreateSignatureCanvas.ts
var CreateSignatureCanvas = class {
canvas = document.createElement("canvas");
ctx = this.canvas.getContext("2d");
stop = [];
active = false;
historyStack = [];
resetStack = [];
color = "#000000";
bg = "#eee";
constructor(lineWidth = 2, w = 400, h = 400, color = "#000000", bg = "#eee") {
this.color = color;
this.bg = bg;
this.createCanvas(lineWidth, w, h);
window.onunload = () => this.unmount();
}
createCanvas(lineWidth = 2, w, h) {
this.canvas.width = w;
this.canvas.height = h;
this.ctx.fillStyle = this.bg;
this.ctx.fillRect(0, 0, w, h);
this.ctx.strokeStyle = this.color;
this.ctx.lineWidth = lineWidth;
this.ctx.lineCap = "round";
let offsetY = 0;
let offsetX = 0;
this.stop.push(useEventListener(this.canvas, "touchstart", (e) => {
offsetY = this.canvas.offsetTop;
offsetX = this.canvas.offsetLeft;
this.ctx.beginPath();
this.ctx.moveTo(e.changedTouches[0].pageX + 2 - offsetX, e.changedTouches[0].pageY - offsetY);
}, false));
let down = false;
this.stop.push(useEventListener(this.canvas, "mousedown", (e) => {
offsetY = this.canvas.offsetTop;
offsetX = this.canvas.offsetLeft;
down = true;
this.ctx.beginPath();
this.ctx.moveTo(e.pageX + 2 - offsetX, e.pageY - offsetY);
}, false));
this.stop.push(useEventListener(this.canvas, "mousemove", (e) => {
if (!down) return;
this.ctx.lineTo(e.pageX + 2 - offsetX, e.pageY - offsetY);
this.ctx.stroke();
}, false));
this.stop.push(useEventListener(this.canvas, "mouseup", () => down = false));
this.stop.push(useEventListener(this.canvas, "mouseout", () => down = false));
this.stop.push(useEventListener(this.canvas, "touchmove", (e) => {
this.ctx.lineTo(e.changedTouches[0].pageX + 2 - offsetX, e.changedTouches[0].pageY - offsetY);
this.ctx.stroke();
}, false));
}
clearCanvas() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.fillStyle = this.bg;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
mount(el) {
insertElement(el, this.canvas, null);
this.listen();
return this;
}
setColor(color) {
this.color = color;
this.ctx.strokeStyle = color;
}
setBgColor(color) {
this.bg = color;
const tempCanvas = document.createElement("canvas");
tempCanvas.width = this.canvas.width;
tempCanvas.height = this.canvas.height;
const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(this.canvas, 0, 0);
this.clearCanvas();
this.ctx.fillStyle = color;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(tempCanvas, 0, 0);
}
unmount() {
removeElement(this.canvas);
this.stop.forEach((s) => s());
}
listen() {
useEventListener(this.canvas, "mousedown", () => {
this.active = true;
});
useEventListener(this.canvas, "mouseup", () => {
this.active = false;
const { width, height } = this.canvas;
const imageData = this.ctx.getImageData(0, 0, width, height);
this.historyStack.push(imageData);
});
useEventListener(this.canvas, "mouseout", () => {
this.active = false;
});
useKeyBoard("Ctrl+z", () => this.undo());
useKeyBoard("Ctrl+x", () => this.redo());
}
undo() {
if (this.historyStack.length === 0) return;
this.clearCanvas();
this.resetStack.push(this.historyStack.pop());
this.historyStack.forEach((imageData) => this.ctx.putImageData(imageData, 0, 0));
}
redo() {
if (this.resetStack.length === 0) return;
this.clearCanvas();
this.historyStack.push(this.resetStack.pop());
this.historyStack.forEach((imageData) => this.ctx.putImageData(imageData, 0, 0));
}
erase(lineWidth = 2) {
this.ctx.lineWidth = lineWidth;
this.ctx.strokeStyle = "rgba(255, 255, 255, 1)";
this.ctx.globalCompositeOperation = "destination-out";
}
unerased() {
this.ctx.strokeStyle = this.color;
this.ctx.globalCompositeOperation = "source-over";
}
save(type = "image/png", quality = 1) {
return this.canvas.toDataURL(type, quality);
}
};
//#endregion
//#region src/perf/memorizeFn.ts
/**
* 函数缓存结果
* @param { Function } fn 函数
* @param { Map<string, any> } cache 缓存对象
* @param { number } maxSize 最大缓存条目数,默认为100
* @returns { Function } 带缓存的函数
*/
function memorizeFn(fn, cache = new Map(), maxSize = 100) {
return function(...args) {
const _args = JSON.stringify(args);
if (cache.has(_args)) {
const value = cache.get(_args);
cache.delete(_args);
cache.set(_args, value);
return value;
}
if (cache.size >= maxSize) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
const result = fn.apply(fn, args);
cache.set(_args, result);
return result;
};
}
//#endregion
//#region src/perf/useRic.ts
/**
* 浏览器空闲时期被调用
* @param { Function[] } tasks 函数队列
* @param { number } timeRemaining 剩余时间大于多少才继续执行
* @param { number } timeout 超时时间
* @param { Function } callback 回调
* @returns
*/
function useRic(tasks, options) {
const { timeRemaining = 0, timeout: timeout$1 = 2e3, callback } = options || {};
let work = true;
const idleCallback = window.requestIdleCallback || function(handler) {
const startTime = Date.now();
return setTimeout(() => handler({
didTimeout: false,
timeRemaining() {
return Math.max(0, 50 - (Date.now() - startTime));
}
}), 1);
};
const taskResult = [];
const idleCancel = window.cancelIdleCallback || clearTimeout;
const animationId = idleCallback(async function animationCallback(deadline) {
if (!work) return;
if ((deadline.timeRemaining() > +timeRemaining || deadline.didTimeout) && tasks.length > 0) {
var _tasks$shift;
taskResult.push((_tasks$shift = tasks.shift()) === null || _tasks$shift === void 0 ? void 0 : _tasks$shift());
}
if (tasks.length > 0) idleCallback(animationCallback);
else {
callback === null || callback === void 0 || callback(taskResult);
stop();
}
}, { timeout: timeout$1 });
function stop() {
work = false;
idleCancel(animationId);
}
return stop;
}
//#endregion
//#region src/perf/debounce.ts
/**
* 防抖函数
* @param { Function } fn 函数
* @param { number } time 时间
* @returns
*/
function debounce(fn, time) {
let timer = null;
return function(e) {
if (!require_isBool.isNull(timer)) clearTimeout(timer);
timer = setTimeout(() => {
const result = fn.call(this, e);
timer = null;
return result;
}, time);
};
}
//#endregion
//#region src/perf/fileSplice.ts
/**
*
* @param { File } _file 文件
* @param { number } _chunkSize 切分大小 默认 1024 * 100
* @returns
*/
async function fileSplice(options) {
return new Promise((resolve) => {
const { file, chunkSize = 5 * 1024 * 1024, callback } = options || {};
const THREAD_COUNT = navigator.hardwareConcurrency || 4;
const result = [];
const chunkCount = Math.ceil(file.size / chunkSize);
const workerChunkCount = Math.ceil(chunkCount / THREAD_COUNT);
let finishCount = 0;
for (let i = 0; i < THREAD_COUNT; i++) {
const worker = new Worker("./dist/worker/fileSpliceWorker.js", { type: "module" });
const startIndex = i * workerChunkCount;
let endIndex = startIndex + workerChunkCount;
if (endIndex > chunkCount) endIndex = chunkCount;
worker.postMessage({
file,
i,
chunkSize,
startIndex,
endIndex
});
worker.onmessage = ({ data }) => {
finishCount++;
const params = {
...data,
fileName: file.name,
type: file.type,
size: file.size,
lastModified: file.lastModified,
isDone: finishCount === THREAD_COUNT,
remaining: THREAD_COUNT - finishCount
};
result.push(params);
callback && callback(params);
worker.terminate();
if (finishCount === THREAD_COUNT) resolve(result);
};
}
});
}
//#endregion
//#region src/perf/getLru.ts
/**
*
* @param max 最大存储量 默认 50
* @returns
*/
function getLru(max = 50) {
return {
set(key, value) {
if (this.cache.has(key)) this.cache.delete(key);
this.cache.set(key, value);
if (this.cache.size > this.max) this.cache.delete(this.cache.keys().next().value);
},
get(key) {
if (this.cache.has(key)) {
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
return void 0;
},
cache: new Map(),
max,
size() {
return this.cache.size;
}
};
}
//#endregion
//#region src/event/useIntersectionObserver.ts
/**
* 检测物体可见
* @param { Element | string } element 元素
* @param { (entries: IntersectionObserverEntry[]) => void } callback 元素可见回调
* @param { IntersectionObserverOptions } options {}
* @param { Element | Document | string | null } options.root 相对容器节点
* @param { string } options.rootMargin 相对容器节点位置"10px 20px 30px 40px"
* @param { number | number[] } options.threshold 相对容器节点百分比 [0, 0.25, 0.5, 0.75, 1]
* @returns
*/
function useIntersectionObserver(element, callback, options) {
let stopped = false;
let stop;
require_createElement.unmount(() => stop === null || stop === void 0 ? void 0 : stop());
require_createElement.mount(element, (element$1) => {
if ((options === null || options === void 0 ? void 0 : options.root) && require_isBool.isStr(options.root)) options.root = require_createElement.findElement(options.root);
const ob = new IntersectionObserver(callback, options);
ob.observe(element$1);
stop = () => ob.disconnect();
if (stopped) stop();
});
return () => {
if (!stop) return stopped = true;
stop === null || stop === void 0 || stop();
};
}
//#endregion
//#region src/perf/lazyLoad.ts
const loadingDefault = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHdpZHRoPSI0MHB4IiBoZWlnaHQ9IjQwcHgiIHZpZXdCb3g9IjAgMCA0MCA0MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjE7IiB4PSIwcHgiIHk9IjBweCI+CiAgICA8ZGVmcz4KICAgICAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWwogICAgICAgICAgICBALXdlYmtpdC1rZXlmcmFtZXMgc3BpbiB7CiAgICAgICAgICAgICAgZnJvbSB7CiAgICAgICAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKDBkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHRvIHsKICAgICAgICAgICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoLTM1OWRlZykKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgQGtleWZyYW1lcyBzcGluIHsKICAgICAgICAgICAgICBmcm9tIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKDBkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHRvIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKC0zNTlkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIHN2ZyB7CiAgICAgICAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybS1vcmlnaW46IDUwJSA1MCU7CiAgICAgICAgICAgICAgICAtd2Via2l0LWFuaW1hdGlvbjogc3BpbiAxLjVzIGxpbmVhciBpbmZpbml0ZTsKICAgICAgICAgICAgICAgIC13ZWJraXQtYmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICAgICAgICAgICAgYW5pbWF0aW9uOiBzcGluIDEuNXMgbGluZWFyIGluZmluaXRlOwogICAgICAgICAgICB9CiAgICAgICAgXV0+PC9zdHlsZT4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJvdXRlciI+CiAgICAgICAgPGc+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMCwwQzIyLjIwNTgsMCAyMy45OTM5LDEuNzg4MTMgMjMuOTkzOSwzLjk5MzlDMjMuOTkzOSw2LjE5OTY4IDIyLjIwNTgsNy45ODc4MSAyMCw3Ljk4NzgxQzE3Ljc5NDIsNy45ODc4MSAxNi4wMDYxLDYuMTk5NjggMTYuMDA2MSwzLjk5MzlDMTYuMDA2MSwxLjc4ODEzIDE3Ljc5NDIsMCAyMCwwWiIgc3R5bGU9ImZpbGw6YmxhY2s7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNNS44NTc4Niw1Ljg1Nzg2QzcuNDE3NTgsNC4yOTgxNSA5Ljk0NjM4LDQuMjk4MTUgMTEuNTA2MSw1Ljg1Nzg2QzEzLjA2NTgsNy40MTc1OCAxMy4wNjU4LDkuOTQ2MzggMTEuNTA2MSwxMS41MDYxQzkuOTQ2MzgsMTMuMDY1OCA3LjQxNzU4LDEzLjA2NTggNS44NTc4NiwxMS41MDYxQzQuMjk4MTUsOS45NDYzOCA0LjI5ODE1LDcuNDE3NTggNS44NTc4Niw1Ljg1Nzg2WiIgc3R5bGU9ImZpbGw6cmdiKDIxMCwyMTAsMjEwKTsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGc+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMCwzMi4wMTIyQzIyLjIwNTgsMzIuMDEyMiAyMy45OTM5LDMzLjgwMDMgMjMuOTkzOSwzNi4wMDYxQzIzLjk5MzksMzguMjExOSAyMi4yMDU4LDQwIDIwLDQwQzE3Ljc5NDIsNDAgMTYuMDA2MSwzOC4yMTE5IDE2LjAwNjEsMzYuMDA2MUMxNi4wMDYxLDMzLjgwMDMgMTcuNzk0MiwzMi4wMTIyIDIwLDMyLjAxMjJaIiBzdHlsZT0iZmlsbDpyZ2IoMTMwLDEzMCwxMzApOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTI4LjQ5MzksMjguNDkzOUMzMC4wNTM2LDI2LjkzNDIgMzIuNTgyNCwyNi45MzQyIDM0LjE0MjEsMjguNDkzOUMzNS43MDE5LDMwLjA1MzYgMzUuNzAxOSwzMi41ODI0IDM0LjE0MjEsMzQuMTQyMUMzMi41ODI0LDM1LjcwMTkgMzAuMDUzNiwzNS43MDE5IDI4LjQ5MzksMzQuMTQyMUMyNi45MzQyLDMyLjU4MjQgMjYuOTM0MiwzMC4wNTM2IDI4LjQ5MzksMjguNDkzOVoiIHN0eWxlPSJmaWxsOnJnYigxMDEsMTAxLDEwMSk7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNMy45OTM5LDE2LjAwNjFDNi4xOTk2OCwxNi4wMDYxIDcuOTg3ODEsMTcuNzk0MiA3Ljk4NzgxLDIwQzcuOTg3ODEsMjIuMjA1OCA2LjE5OTY4LDIzLjk5MzkgMy45OTM5LDIzLjk5MzlDMS43ODgxMywyMy45OTM5IDAsMjIuMjA1OCAwLDIwQzAsMTcuNzk0MiAxLjc4ODEzLDE2LjAwNjEgMy45OTM5LDE2LjAwNjFaIiBzdHlsZT0iZmlsbDpyZ2IoMTg3LDE4NywxODcpOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTUuODU3ODYsMjguNDkzOUM3LjQxNzU4LDI2LjkzNDIgOS45NDYzOCwyNi45MzQyIDExLjUwNjEsMjguNDkzOUMxMy4wNjU4LDMwLjA1MzYgMTMuMDY1OCwzMi41ODI0IDExLjUwNjEsMzQuMTQyMUM5Ljk0NjM4LDM1LjcwMTkgNy40MTc1OCwzNS43MDE5IDUuODU3ODYsMzQuMTQyMUM0LjI5ODE1LDMyLjU4MjQgNC4yOTgxNSwzMC4wNTM2IDUuODU3ODYsMjguNDkzOVoiIHN0eWxlPSJmaWxsOnJnYigxNjQsMTY0LDE2NCk7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNMzYuMDA2MSwxNi4wMDYxQzM4LjIxMTksMTYuMDA2MSA0MCwxNy43OTQyIDQwLDIwQzQwLDIyLjIwNTggMzguMjExOSwyMy45OTM5IDM2LjAwNjEsMjMuOTkzOUMzMy44MDAzLDIzLjk5MzkgMzIuMDEyMiwyMi4yMDU4IDMyLjAxMjIsMjBDMzIuMDEyMiwxNy43OTQyIDMzLjgwMDMsMTYuMDA2MSAzNi4wMDYxLDE2LjAwNjFaIiBzdHlsZT0iZmlsbDpyZ2IoNzQsNzQsNzQpOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTI4LjQ5MzksNS44NTc4NkMzMC4wNTM2LDQuMjk4MTUgMzIuNTgyNCw0LjI5ODE1IDM0LjE0MjEsNS44NTc4NkMzNS43MDE5LDcuNDE3NTggMzUuNzAxOSw5Ljk0NjM4IDM0LjE0MjEsMTEuNTA2MUMzMi41ODI0LDEzLjA2NTggMzAuMDUzNiwxMy4wNjU4IDI4LjQ5MzksMTEuNTA2MUMyNi45MzQyLDkuOTQ2MzggMjYuOTM0Miw3LjQxNzU4IDI4LjQ5MzksNS44NTc4NloiIHN0eWxlPSJmaWxsOnJnYig1MCw1MCw1MCk7Ii8+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4K";
/**
*
* @param { MaybeElement } element 元素
* @param { string } loadingUrl 懒加载的loading图
* @returns { Function } 停止懒加载的函数
*/
function lazyLoad(element, loadingUrl = loadingDefault) {
return require_createElement.mount(element, (el) => {
require_createElement.findElement(["img", "video"], true, el).forEach((child) => {
const datasrc = child.dataset.src;
const src = datasrc || child.src;
if (!datasrc) child.src = loadingUrl;
const stop = useIntersectionObserver(child, (entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
stop();
entry.target.src = src;
}
});
});
});
});
}
//#endregion
//#region src/perf/preload.ts
/**
* 图片视频预加载函数
* @param { string[] } list 图片数组
* @param { string } style 设置样式
* @returns { HTMLImageElement[] } 图片数组
*/
function preload(list, style) {
let imageNode;
let videoNode;
if (!require_isBool.isArray(list)) list = [list];
return list.map((src) => require_parallel.isVideo(src) ? createVideo(src) : createImage(src));
function createImage(src) {
if (!imageNode) imageNode = new Image();
const image = imageNode.cloneNode();
image.src = src;
if (style) image.setAttribute("style", style);
return image;
}
function createVideo(src) {
if (!videoNode) videoNode = require_createElement.createElement("video");
const video = videoNode.cloneNode();
video.src = src;
if (style) video.setAttribute("style", style);
return video;
}
}
//#endregion
//#region src/perf/prefetch.ts
/**
* 借助浏览器空闲时间去加载一些图片资源
* @param { string[] } list 图片或视频地址数组
* @param { number } timeRemaining 浏览器空闲时间大于多少去加载
* @returns { Function } 停止加载的函数
*/
function prefetch(list, timeRemaining = 0) {
const imageNode = new Image();
const videoNode = require_createElement.createElement("video");
return useRic(list.map((src) => {
if (require_parallel.isVideo(src)) return () => {
videoNode.src = src;
};
return () => {
imageNode.src = src;
};
}, timeRemaining));
}
//#endregion
//#region src/perf/throttle.ts
/**
* 截流函数
* @param { Function } fn 函数
* @param { number } stop 时间
* @returns
*/
function throttle(fn, stop) {
let start = 0;
return function(...args) {
const end = Date.now();
if (end - start >= stop) {
const result = fn.call(this, ...args);
start = end;
return result;
}
};
}
//#endregion
//#region src/perf/useRaf.ts
/**
* 使用 requestAnimationFrame 执行一个函数,并提供停止执行的功能。
*
* @param {function(number): void} fn - 在每一帧调用的函数,参数是时间戳。
* @param {object} [options] - 配置选项。
* @param {number} [options.delta] - 两次调用之间的最小时间间隔(毫秒)。
* @param {boolean} [options.autoStop] - 是否在首次调用后自动停止。
* @param {boolean} [options.immediate] - 是否在首次调用时立即执行。
* @returns {function(): void} - 停止执行的函数。
*/
function useRaf(fn, options = {}) {
let start;
let isStopped = false;
let rafId;
const { immediate, delta = 0, autoStop } = options;
const animationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || ((fn$1) => setTimeout(fn$1, 1e3 / 60));
const cancelAnimation = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame || clearTimeout;
const stop = () => {
isStopped = true;
cancelAnimation(rafId);
};
rafId = animationFrame(function myFrame(timestamp = Date.now()) {
if (require_parallel.isUndef(start)) {
start = timestamp;
if (immediate) fn === null || fn === void 0 || fn(timestamp);
} else if (isStopped) return;
else if (timestamp - start > delta) {
fn === null || fn === void 0 || fn(timestamp);
start = timestamp;
if (autoStop) {
stop();
return;
}
}
rafId = animationFrame(myFrame);
});
return stop;
}
//#endregion
//#region src/perf/once.ts
/**
* 只执行一次函数
* @param { Function } fn 函数
* @returns { Function } 函数
*/
function once(fn) {
let called = false;
return function(...args) {
if (!called) {
called = true;
return fn.apply(this, args);
}
};
}
//#endregion
//#region src/perf/createHybridMap.ts
var HybridMap = class {
objectStore;
primitiveStore;
constructor(entries) {
this.objectStore = new WeakMap();
this.primitiveStore = new Map();
if (entries) for (const [k, v] of entries) this.set(k, v);
}
isObjectKey(key) {
return typeof key === "object" && key !== null;
}
set(key, value) {
if (this.isObjectKey(key)) this.objectStore.set(key, value);
else this.primitiveStore.set(key, value);
return this;
}
get(key) {
if (this.isObjectKey(key)) return this.objectStore.get(key);
else return this.primitiveStore.get(key);
}
has(key) {
if (this.isObjectKey(key)) return this.objectStore.has(key);
else return this.primitiveStore.has(key);
}
delete(key) {
if (this.isObjectKey(key)) return this.objectStore.delete(key);
else return this.primitiveStore.delete(key);
}
clear() {
this.primitiveStore.clear();
this.objectStore = new WeakMap();
}
get size() {
throw new Error("HybridMap does not support size due to WeakMap limitations.");
}
forEach() {
throw new Error("HybridMap does not support forEach due to WeakMap limitations.");
}
keys() {
throw new Error("HybridMap does not support iteration due to WeakMap limitations.");
}
values() {
throw new Error("HybridMap does not support iteration due to WeakMap limitations.");
}
entries() {
throw new Error("HybridMap does not support iteration due to WeakMap limitations.");
}
[Symbol.iterator]() {
throw new Error("HybridMap does not support iteration due to WeakMap limitations.");
}
};
function createHybridMap(entries) {
return new HybridMap(entries);
}
//#endregion
//#region src/canvas/DotImageCanvas.ts
/**
* DotImageCanvas 将图片转换为点阵图形式展示,并提供动画绘制效果
*
* 支持多种绘制方向,颜色控制,背景设置,以及动画效果控制
* @class
*/
var DotImageCanvas = class {
/** 用于绘制的 Canvas 元素 */
canvas = document.createElement("canvas");
/** Canvas 的绘制上下文 */
ctx = this.canvas.getContext("2d");
/** 缓存已处理图像的点阵数据 */
points = new Map();
/** 原始图片源地址 */
originSrc = "";
/** 点阵图颜色,设置后将覆盖原图颜色 */
color = "";
/** 点阵粗细,影响绘制的圆点大小 */
fontWeight = 1;
/** 绘制状态:pending-绘制中,success-完成,fail-失败,reverted-已撤销 */
status = "pending";
/** 背景颜色 */
bgColor = "#fff";
/** 停止当前动画的函数 */
stop = () => {};
/** 绘制方向 */
direction = "horizontal";
/** 所有绘制任务 */
allTasks = [];
/** 已完成的任务索引 */
completedTaskIndex = 0;
/** 是否正在撤销绘制 */
isReverting = false;
/** 每个绘制任务中的点坐标记录 */
drawnPoints = [];
/** 清除任务列表 */
clearTasks = [];
/** 是否使用优先渲染模式 (RAF) */
isPreferred = false;
/** 是否已挂载到DOM */
mounted = false;
/**
* 创建 DotImageCanvas 实例
*
* @param {string | DotImageCanvasOptions} srcOrOptions - 图片URL或选项对象
* @param {string} [color] - 绘制颜色,为空时保留原图颜色
* @param {number} [fontWeight] - 点阵粗细
* @param {string} [bgColor] - 背景颜色
* @param {Direction} [direction] - 绘制方向
*/
constructor(srcOrOptions, color, fontWeight, bgColor = "#fff", direction) {
if (typeof srcOrOptions === "string") this.initOptions(srcOrOptions, color, fontWeight, bgColor, direction);
else {
const options = srcOrOptions;
this.initOptions(options.src, options.color, options.fontWeight, options.bgColor || "#fff", options.direction);
if (options.isPreferred !== void 0) this.isPreferred = options.isPreferred;
}
this.executor();
}
createDotImage(img) {
this.canvas.width = img.width * devicePixelRatio;
this.canvas.height = img.height * devicePixelRatio;
this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height);
const width = this.canvas.width;
const height = this.canvas.height;
const imageDataObj = this.ctx.getImageData(0, 0, width, height);
const rawData = imageDataObj.data;
const pixelView = new Uint32Array(rawData.buffer);
const pixelBitmap = new Uint8Array(width * height);
const colorMap = new Map();
const imagePointSet = new Array(height);
for (let i = 0; i < height; i++) imagePointSet[i] = new Array(width);
const WHITE_THRESHOLD = 15132390;
for (let i = 0; i < height; i++) for (let j = 0; j < width; j++) {
const pixelIndex = i * width + j;
const pixelValue = pixelView[pixelIndex];
const r = pixelValue >> 0 & 255;
const g = pixelValue >> 8 & 255;
const b = pixelValue >> 16 & 255;
const a = pixelValue >> 24 & 255;
if (a === 0 || r > 230 && g > 230 && b > 230) {
pixelBitmap[pixelIndex] = 0;
imagePointSet[i][j] = this.color ? 0 : this.bgColor;
} else {
pixelBitmap[pixelIndex] = 1;
const color = `rgba(${r},${g},${b},${a / 255})`;
colorMap.set(pixelIndex, color);
imagePointSet[i][j] = color;
}
}
this.points.set(this.originSrc, {
width,
height,
imagePointSet,
pixelBitmap,
colorMap
});
return imagePointSet;
}
createImage() {
if (this.hasImage()) {
const { imagePointSet, width, height } = this.points.get(this.originSrc);
const pRatio = window.devicePixelRatio || 1;
this.canvas.width = width * pRatio;
this.canvas.height = height * pRatio;
this.getCanvas(imagePointSet);
return;
}
const img = require_createElement.createElement("img", {
crossOrigin: "anonymous",
src: this.originSrc
});
return new Promise((resolve) => {
img.onload = () => {
this.getCanvas(this.createDotImage(img));
resolve(img);
};
img.onerror = () => {
this.status = "fail";
};
});
}
hasImage() {
return this.points.has(this.originSrc);
}
async executor() {
try {
await this.createImage();
} catch (error) {}
return this;
}
/**
* 创建绘制和清除任务的辅助方法 - 保持原有实现方式
*/
createDrawAndClearTasks(pointsGenerator, size) {
const batchPoints = [];
const drawTask = () => {
const points = pointsGenerator();
for (const { x, y, color } of points) {
batchPoints.push({
x,
y,
color
});
this.ctx.beginPath();
this.ctx.arc(x, y, size, 0, Math.PI * 2);
this.ctx.fillStyle = color;
this.ctx.fill();
}
};
const clearTask = () => {
for (const { x, y, color } of batchPoints) {
this.ctx.beginPath();
this.ctx.fillStyle = color;
this.ctx.clearRect(x - size, y - size, size * 2.5, size * 2.5);
}
};
return {
drawTask,
clearTask
};
}
getCanvas(imagePointSet) {
var _imagePointSet$;
this.clearCanvas();
const h = imagePointSet.length;
const w = (_imagePointSet$ = imagePointSet[0]) === null || _imagePointSet$ === void 0 ? void 0 : _imagePointSet$.length;
const oneTempLength = this.canvas.width * 1 / h;
const size = this.fontWeight * 50 / this.canvas.width;
const getPoint = memorizeFn((i) => oneTempLength * (i + .5));
const tasks = [];
const clearTasks = [];
if (this.direction === "horizontal-reverse") for (let i = h - 1; i >= 0; i--) {
const row = i;
const { drawTask, clearTask } = this.createDrawAndClearTasks(() => {
const points = new Array(w);
let pointCount = 0;
for (let j = w - 1; j >= 0; j--) {
const color = imagePointSet[row][j];
if (color) points[pointCount++] = {
x: getPoint(j),
y: getPoint(row),
color: this.color || `${color}`
};
}
return pointCount < w ? points.slice(0, pointCount) : points;
}, size);
tasks.push(drawTask);
clearTasks.push(clearTask);
}
else if (this.direction === "horizontal") for (let i = 0; i < h; i++) {
const row = i;
const { drawTask, clearTask } = this.createDrawAndClearTasks(() => {
const points = [];
for (let j = 0; j < w; j++) {
const color = imagePointSet[row][j];
if (color) points.push({
x: getPoint(j),
y: getPoint(row),
color: this.color || `${color}`
});
}
return points;
}, size);
tasks.push(drawTask);
clearTasks.push(clearTask);
}
else if (this.direction === "vertical") for (let j = 0; j < w; j++) {
const col = j;
const { drawTask, clearTask } = this.createDrawAndClearTasks(() => {
const points = [];
for (let i = 0; i < h; i++) {
const color = imagePointSet[i][col];
if (color) points.push({
x: getPoint(col),
y: getPoint(i),
color: this.color || `${color}`
});
}
return points;
}, size);
tasks.push(drawTask);
clearTasks.push(clearTask);
}
else if (this.direction === "vertical-reverse") for (let j = w - 1; j >= 0; j--) {
const col = j;
const { drawTask, clearTask } = this.createDrawAndClearTasks(() => {
const points = [];
for (let i = h - 1; i >= 0; i--) {
const color = imagePointSet[i][col];
if (color) points.push({
x: getPoint(col),
y: getPoint(i),
color: this.color || `${color}`
});
}
return points;
}, size);
tasks.push(drawTask);
clearTasks.push(clearTask);
}
else if (this.direction === "center-out" || this.direction === "out-center") {
const centerX = Math.floor(w / 2);
const centerY = Math.floor(h / 2);
const { pixelBitmap, colorMap } = this.points.get(this.originSrc) || {};
const validPixels = pixelBitmap ? Array.from(pixelBitmap).filter((p) => p === 1).length : Math.ceil(w * h * .3);
let distances = new Float32Array(validPixels);
const pointData = {
indices: new Uint32Array(validPixels),
x: new Uint16Array(validPixels),
y: new Uint16Array(validPixels)
};
let pointCount = 0;
for (let i = 0; i < h; i++) for (let j = 0; j < w; j++) {
const pixelIndex = i * w + j;
if (pixelBitmap && pixelBitmap[pixelIndex] === 0) continue;
const color = imagePointSet[i][j];
if (!color) continue;
pointData.indices[pointCount] = pixelIndex;
pointData.x[pointCount] = j;
pointData.y[pointCount] = i;
distances[pointCount] = Math.sqrt((j - centerX) ** 2 + (i - centerY) ** 2);
pointCount++;
}
if (pointCount < validPixels) {
const newIndices = new Uint32Array(pointCount);
const newX = new Uint16Array(pointCount);
const newY = new Uint16Array(pointCount);
const newDistances = new Float32Array(pointCount);
for (let i = 0; i < pointCount; i++) {
newIndices[i] = pointData.indices[i];
newX[i] = pointData.x[i];
newY[i] = pointData.y[i];
newDistances[i] = distances[i];
}
pointData.indices = newIndices;
pointData.x = newX;
pointData.y = newY;
distances = newDistances;
}
const indexOrder = new Uint32Array(pointCount);
for (let i = 0; i < pointCount; i++) indexOrder[i] = i;
if (this.direction === "center-out") indexOrder.sort((a, b) => distances[a] - distances[b]);
else indexOrder.sort((a, b) => distances[b] - distances[a]);
const batchSize = Math.max(1, Math.min(500, Math.floor(pointCount / 100)));
for (let i = 0; i < pointCount; i += batchSize) {
const end = Math.min(i + batchSize, pointCount);
const { drawTask, clearTask } = this.createDrawAndClearTasks(() => {
const result = [];
for (let j = i; j < end; j++) {
const pointIndex = indexOrder[j];
const x = getPoint(pointData.x[pointIndex]);
const y = getPoint(pointData.y[pointIndex]);
const pixelIndex = pointData.indices[pointIndex];
const color = colorMap ? colorMap.get(pixelIndex) || this.color : this.color || imagePointSet[pointData.y[pointIndex]][pointData.x[pointIndex]];
result.push({
x,
y,
color
});
}
return result;
}, size);
tasks.push(drawTask);
clearTasks.push(clearTask);
}
}
this.allTasks = tasks;
this.clearTasks = clearTasks;
this.completedTaskIndex = 0;
this.isReverting = false;
this.startAnimation();
}
startAnimation() {
this.stop();
const tasksToRun = this.isReverting ? Array.from({ length: this.completedTaskIndex }, (_, i) => {
const idx = this.completedTaskIndex - 1 - i;
return () => {
const clearTask = this.clearTasks[idx];
if (clearTask) {
clearTask();
this.completedTaskIndex = idx;
}
return true;
};
}).filter((task) => task !== null) : Array.from({ length: this.allTasks.length - this.completedTaskIndex }, (_, i) => {
const idx = this.completedTaskIndex + i;
return () => {
const drawTask = this.allTasks[idx];
if (drawTask) {
drawTask();
this.completedTaskIndex = idx + 1;
}
return true;
};
});
if (this.isPreferred) {
let currentIndex = 0;
this.stop = useRaf(() => {
const tasksPerFrame = 5;
let count = 0;
while (currentIndex < tasksToRun.length && count < tasksPerFrame) {
tasksToRun[currentIndex]();
currentIndex++;
count++;
}
if (currentIndex >= tasksToRun.length) {
this.stop();
this.status = this.isReverting ? "reverted" : "success";
if (this.isReverting) this.completedTaskIndex = 0;
else this.completedTaskIndex = this.allTasks.length;
}
});
} else this.stop = useRic(tasksToRun, { callback: () => {
this.status = this.isReverting ? "reverted" : "success";
if (this.isReverting) this.completedTaskIndex = 0;
else this.completedTaskIndex = this.allTasks.length;
this.stop();
} });
return this;
}
revert() {
if (this.completedTaskIndex <= 0) return this;
this.isReverting = true;
this.startAnimation();
return this;
}
continue() {
if (this.completedTaskIndex >= this.allTasks.length && !this.isReverting) return this;
this.isReverting = false;
this.startAnimation();
return this;
}
initOptions(src, color, fontWeight, bgColor, direction = "horizontal") {
this.originSrc = src;
this.color = color;
this.fontWeight = fontWeight;
this.bgColor = bgColor;
this.direction = direction;
}
/**
* 重新绘制图像,可以更新配置
*
* @param {string | Partial<DotImageCanvasOptions>} srcOrOptions - 图片URL或选项对象
* @param {string} [color] - 点阵颜色
* @param {number} [fontWeight] - 点阵粗细
* @param {string} [bgColor] - 背景颜色
* @param {Direction} [direction] - 绘制方向
* @returns {Promise<DotImageCanvas>} 更新后的实例
*/
async repaint(srcOrOptions, color, fontWeight, bgColor = "#fff", direction) {
this.stop();
const p = removeElement(this.canvas);
this.status = "pending";
if (typeof srcOrOptions === "string") this.initOptions(srcOrOptions, color, fontWeight, bgColor, direction);
else {
const options = srcOrOptions;
this.initOptions(options.src || this.originSrc, options.color || this.color, options.fontWeight !== void 0 ? options.fontWeight : this.fontWeight, options.bgColor || this.bgColor || "#fff", options.direction || this.direction);
if (options.isPreferred !== void 0) this.isPreferred = options.isPreferred;
}
if (!p) throw new Error("repaint error not found canvas container or has been removed");
return Object.assign(this, await this.executor()).append(p);
}
clearCanvas() {
this.stop();
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.completedTaskIndex = 0;
this.isReverting = false;
}
append(container) {
insertElement(container, this.canvas);
this.mounted = true;
return this;
}
destory() {
this.stop();
this.mounted = false;
removeElement(this.canvas);
}
};
//#endregion
//#region src/canvas/DotTextCanvas.ts
/**
* DotTextCanvas 将文字转换为点阵形式展示,并提供动画绘制效果
*
* 支持多种绘制方向、颜色控制、间距设置以及动画效果
* @class
*/
var DotTextCanvas = class DotTextCanvas {
/** 用于绘制的 Canvas 元素 */
canvas = document.createElement("canvas");
/** Canvas 的绘制上下文 */
ctx = this.canvas.getContext("2d");
/** 缓存已处理字符的点阵数据 */
points = new Map();
/** 原始文本内容 */
originText;
/** 字体大小 */
fontSize;
/** 点阵颜色 */
color;
/** 点阵粗细,影响绘制的圆点大小 */
fontWeight;
/** 绘制方向 */
direction;
/** 全局字符间距,默认为0 */
charSpacing = 0;
/** 每个字符对之间的间距数组,优先级高于全局间距 */
charSpacings = [];
/** 存储最终合成的点阵数据 */
textPointSet = [];
/** 绘制状态:pending-绘制中,success-完成 */
status = "pending";
/** 容器元素 */
container;
/** 停止当前动画的函数 */
stop = () => {};
/** 是否已挂载到DOM */
mounted = false;
/** 是否使用优先渲染模式(RAF) */
isPreferred = false;
/** 用于文本绘制的临时Canvas元素 */
_textCanvas;
/** 临时Canvas的绘制上下文 */
_textCtx;
/**
* 创建 DotTextCanvas 实例
*
* @param {string | DotTextCanvasOptions} textOrOptions - 文本内容或选项对象
* @param {number} [fontSize] - 字体大小
* @param {string} [color] - 点阵颜色
* @param {number} [fontWeight] - 点阵粗细
* @param {Direction} [direction] - 绘制方向
* @param {boolean} [isPreferred] - 是否使用优先渲染模式
* @param {number} [charSpacing] - 全局字符间距
* @param {number[]} [charSpacings] - 每个字符对之间的间距数组
*/
constructor(textOrOptions, fontSize, color, fontWeight, direction = "vertical", isPreferred = false, charSpacing = 0, charSpacings = []) {
if (typeof textOrOptions === "string") {
this.originText = textOrOptions;
this.fontSize = fontSize;
this.color = color;
this.fontWeight = fontWeight;
this.direction = direction;
this.isPreferred = isPreferred;
this.charSpacing = Math.max(0, charSpacing);
this.charSpacings = charSpacings.map((s) => Math.max(0, s));
} else {
const options = textOrOptions;
this.originText = options.text;
this.fontSize = options.fontSize;
this.color = options.color;
this.fontWeight = options.fontWeight;
this.direction = options.direction || "vertical";
this.isPreferred = options.isPreferred || false;
this.charSpacing = options.charSpacing !== void 0 ? Math.max(0, options.charSpacing) : 0;
this.charSpacings = options.charSpacings ? options.charSpacings.map((s) => Math.max(0, s)) : [];
}
this.executor();
}
/**
* 为单个字符创建点阵数据
*
* @param {string} text - 要转换为点阵的字符
* @returns {Array<number[]>} 字符的点阵数据
*/
createTextPoint(text) {
if (!this._textCanvas) {
this._textCanvas = document.createElement("canvas");
this._textCtx = this._textCanvas.getContext("2d");
}
const canvas = this._textCanvas;
const ctx = this._textCtx;
const pRatio = window.devicePixelRatio || 1;
const size = 16 * pRatio;
canvas.width = canvas.height = size;
ctx.font = `${size}px SimSun`;
ctx.fillText(text, 0, 14 * pRatio);
const { data: imageData, width, height } = ctx.getImageData(0, 0, size, size);
const textPointSet = [];
for (let i = 0; i < height; i++) {
const temp = [];
textPointSet.push(temp);
for (let j = 0; j < width; j++) {
const pxStartIndex = i * width * 4 + j * 4;
temp.push(imageData[pxStartIndex + 3] ? 1 : 0);
}
}
this.points.set(text, textPointSet);
return textPointSet;
}
/**
* 执行文字点阵转换流程
*/
executor() {
this.originText.split("").forEach((text) => this.getText(text));
this.textPointSet = this.combineText();
this.getCanvas();
}
/**
* 获取字符的点阵数据,如果不存在则创建
*
* @param {string} text - 要获取点阵数据的字符
* @returns {Array<number[]>|undefined} 字符的点阵数据
*/
getText(text) {
return this.points.has(text) ? this.points.get(text) : this.createTextPoint(text);
}
/**
* 合并所有字符的点阵数据,并应用字符间距
*
* @returns {Array<number[]>} 合并后的点阵数据
*/
combineText() {
const result = [[]];
const len = this.originText.length;
for (let i = 0; i < len; i++) {
const charPoints = this.points.get(this.originText[i]) || [];
charPoints.forEach((item, index) => {
result[index] = (result[index] || []).concat(item);
});
if (i < len - 1) {
const currentSpacing = i < this.charSpacings.length ? this.charSpacings[i] : this.charSpacing;
if (currentSpacing > 0) {
const charHeight = charPoints.length;
for (let row = 0; row < charHeight; row++) {
if (!result[row]) result[row] = [];
for (let space = 0; space < currentSpacing; space++) result[row].push(0);
}
}
}
}
return result;
}
/**
* 根据绘制方向生成点坐标数组
*
* @param {number} h - 点阵高度
* @param {number} w - 点阵宽度
* @returns {Array<[number, number]>} 按指定方向排列的点坐标数组
*/
get