/** * 常用 JavaScript 函数片段 * author chen tao * date 2020-10-6 */ /* 数组相关函数 */ // 数组去重 export function noRepeat(arr: T[]): T[] { return [...new Set(arr)] } // 查找数组最大值 export function arrayMax(arr: number[]): number { if (!arr.length) throw new Error('Array is empty') return Math.max(...arr) } // 查找数组最小值 export function arrayMin(arr: number[]): number { if (!arr.length) throw new Error('Array is empty') return Math.min(...arr) } // 数组分割 export function chunk(arr: T[], size: number = 1): T[][] { if (size <= 0) return [arr.slice()] return Array.from({ length: Math.ceil(arr.length / size) }, (_, i) => arr.slice(i * size, i * size + size) ) } // 检查元素出现次数 export function countOccurrences(arr: T[], value: T): number { return arr.reduce((count, current) => (current === value ? count + 1 : count), 0) } // 扁平化数组 export function flatten(arr: any[], depth: number = Infinity): T[] { return arr.flat(depth) } // 返回两个数组的差集 export function difference(arrA: T[], arrB: T[]): T[] { const setB = new Set(arrB) return arrA.filter((item) => !setB.has(item)) } // 返回两个数组的交集 export function intersection(arr1: T[], arr2: T[]): T[] { const set2 = new Set(arr2) return arr1.filter((item) => set2.has(item)) } // 从右删除 n 个元素 export function dropRight(arr: T[], n: number = 0): T[] { return arr.slice(0, Math.max(0, arr.length - n)) } // 返回间隔 nth 的元素 export function everyNth(arr: T[], nth: number): T[] { if (nth <= 0) return [] return arr.filter((_, i) => i % nth === nth - 1) } // 返回第 n 个元素 export function nthElement(arr: T[], n: number = 0): T | undefined { const index = n >= 0 ? n : arr.length + n return arr[index] } // 数组乱序 export function shuffle(arr: T[]): T[] { const array = [...arr] // 创建新数组避免修改原数组 for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) ;[array[i], array[j]] = [array[j], array[i]] } return array } /* 浏览器对象 BOM */ // 当前网页地址 export function currentURL(): string { return window.location.href } // 获取滚动条位置 export function getScrollPosition(el: HTMLElement | Window = window): { x: number; y: number } { return el === window ? { x: window.scrollX || document.documentElement.scrollLeft, y: window.scrollY || document.documentElement.scrollTop } : { x: (el as HTMLElement).scrollLeft, y: (el as HTMLElement).scrollTop } } // 获取 URL 参数 export function getURLParameters(url: string): Record { return Object.fromEntries(new URLSearchParams(url.split('?')[1]).entries()) } // 兼容非 HTTPS 等环境:使用 execCommand 复制 function fallbackCopy(str: string): boolean { try { const textarea = document.createElement('textarea') textarea.value = str textarea.style.position = 'fixed' textarea.style.left = '-9999px' textarea.setAttribute('readonly', '') document.body.appendChild(textarea) textarea.select() const ok = document.execCommand('copy') document.body.removeChild(textarea) return ok } catch { return false } } // 复制文本(同步,优先 clipboard API,不可用时回退 execCommand) export function copy(str: string): boolean { try { if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { navigator.clipboard.writeText(str) return true } return fallbackCopy(str) } catch (err) { console.error('Copy failed:', err) return fallbackCopy(str) } } /** * 异步复制到剪贴板,生产环境安全:优先 clipboard API,不可用时回退 execCommand。 */ export async function copyToClipboard(str: string): Promise { try { if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { await navigator.clipboard.writeText(str) return true } return fallbackCopy(str) } catch { return fallbackCopy(str) } } /** * 从图片 URL 拉取图片并复制到剪贴板(需支持 ClipboardItem,通常为 HTTPS 环境)。 */ export async function copyImageFromUrl(imageUrl: string): Promise { try { if (!navigator.clipboard || typeof navigator.clipboard.write !== 'function') return false const res = await fetch(imageUrl, { mode: 'cors' }) if (!res.ok) return false const blob = await res.blob() const type = blob.type || 'image/png' await navigator.clipboard.write([new ClipboardItem({ [type]: blob })]) return true } catch { return false } } /** * 将 Blob 图片写入剪贴板(用于 html2canvas 等生成的图片)。 */ export async function copyImageBlobToClipboard(blob: Blob): Promise { try { if (!navigator.clipboard || typeof navigator.clipboard.write !== 'function') return false const type = blob.type || 'image/png' await navigator.clipboard.write([new ClipboardItem({ [type]: blob })]) return true } catch { return false } } // 检测设备类型 export function detectDeviceType(): 'Mobile' | 'Desktop' { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ? 'Mobile' : 'Desktop' } /* Cookie 操作 */ // 设置 Cookie export function setCookie(key: string, value: string, expireDays: number): void { const date = new Date() date.setDate(date.getDate() + expireDays) document.cookie = `${key}=${encodeURIComponent(value)}${ expireDays ? `;expires=${date.toUTCString()}` : '' }` } // 删除 Cookie export function delCookie(name: string): void { document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT` } // 获取 Cookie export function getCookie(name: string): string | null { const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`)) return match ? decodeURIComponent(match[1]) : null } /* 日期相关 */ // 时间戳转时间 export function timestampToTime(timestamp: number = Date.now(), isMs: boolean = true): string { const date = new Date(isMs ? timestamp : timestamp * 1000) return date.toISOString().replace('T', ' ').slice(0, 19) } /* DOM 操作 */ let scrollTop = 0 export function preventScroll(): void { scrollTop = window.scrollY Object.assign(document.body.style, { position: 'fixed', width: '100%', top: `-${scrollTop}px`, overflowY: 'hidden' }) } export function recoverScroll(): void { Object.assign(document.body.style, { position: '', width: '', top: '', overflowY: '' }) window.scrollTo(0, scrollTop) } // 判断是否到达页面底部 export function bottomVisible(): boolean { return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight } // 判断元素是否在可视区域 export function elementIsVisibleInViewport( el: HTMLElement, partiallyVisible: boolean = false ): boolean { const { top, left, bottom, right } = el.getBoundingClientRect() const { innerHeight, innerWidth } = window return partiallyVisible ? top + el.offsetHeight > 0 && top < innerHeight && left + el.offsetWidth > 0 && left < innerWidth : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth } // 获取元素样式 export function getStyle(el: HTMLElement, ruleName: string): string { return window.getComputedStyle(el)[ruleName as any] } /* 数字操作 */ export function commafy(num: number): string { return num.toLocaleString('en-US') } export function randomNum(min: number, max?: number): number { if (max === undefined) return Math.floor(Math.random() * min) return Math.floor(Math.random() * (max - min + 1)) + min } /* 字符串操作 */ export function removeHtmlTags(str: string = ''): string { return str.replace(/<[^>]*>/g, '') } // 判断是不是iframe export function isIframe(url: string) { return url.includes('/outside/iframe') }