one-screen-scale 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +181 -38
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +78 -2
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/react.cjs.js +1 -1
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.d.ts +30 -0
- package/dist/react.esm.js +1 -1
- package/dist/react.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/baseStyles.js +91 -0
- package/src/compensate.js +320 -0
- package/src/composables.js +69 -67
- package/src/core.js +185 -24
- package/src/directives.js +126 -16
- package/src/index.d.ts +78 -2
- package/src/index.js +21 -2
- package/src/react.d.ts +30 -0
- package/src/react.js +110 -17
package/dist/react.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.esm.js","sources":["../src/core.js","../src/react.js"],"sourcesContent":["/**\n * 一屏自适应缩放 — 核心算法(框架无关)\n *\n * 两种模式:\n * 'contain' 一屏展示,内容不溢出,用 min(scaleX, scaleY)\n * 'fixed-width' 固定视觉宽度,内容可滚动,反向补高\n */\n\n// ---------- helpers ----------\n\nfunction resolveEl(el) {\n if (!el) return null\n if (typeof el === 'string') return document.querySelector(el)\n if (el instanceof HTMLElement) return el\n // Vue ref / React ref\n if (el.value instanceof HTMLElement) return el.value\n if (el.current instanceof HTMLElement) return el.current\n return null\n}\n\n// ---------- scale calculation ----------\n\nexport function calculateScale({\n sourceWidth,\n sourceHeight,\n targetWidth = (typeof window !== 'undefined' ? window.innerWidth : 1920),\n targetHeight = (typeof window !== 'undefined' ? window.innerHeight : 1080),\n mode = 'contain',\n visualWidth = 450, // fixed-width 模式下的目标视觉宽度\n screenWidth = (typeof window !== 'undefined' ? window.screen.width : 1920), // fixed-width 模式下的设备屏幕宽度\n}) {\n if (!sourceWidth || !sourceHeight || sourceWidth <= 0 || sourceHeight <= 0) return 1\n\n let scale\n\n if (mode === 'fixed-width') {\n if(screenWidth < visualWidth){\n scale = 1\n }else{\n scale = visualWidth / Math.max(targetWidth, screenWidth)\n }\n } else {\n const sx = targetWidth / sourceWidth\n const sy = targetHeight / sourceHeight\n scale = Math.min(sx, sy)\n }\n\n return scale\n}\n\n// ---------- apply / read ----------\n\nexport function applyScale(el, scale, origin = 'top center') {\n const target = resolveEl(el)\n if (!target) return\n target.style.transform = `scale(${scale})`\n target.style.transformOrigin = origin\n}\n\nexport function getPageScale(el) {\n const target = resolveEl(el)\n if (!target) return 1\n\n const style = window.getComputedStyle(target)\n const t = style.transform\n if (!t || t === 'none') return 1\n\n // matrix(a, b, c, d, e, f) — 2d\n const m2 = t.match(/matrix\\(([^,]+),\\s*[^,]+,\\s*[^,]+,\\s*([^,]+)/)\n if (m2) return (parseFloat(m2[1]) + parseFloat(m2[2])) / 2\n\n // matrix3d — 3d\n const m3 = t.match(/matrix3d\\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\\)/)\n if (m3) return (parseFloat(m3[1]) + parseFloat(m3[2])) / 2\n\n // scale(x, y) or scale(s)\n const sm = t.match(/scale\\(([^)]+)\\)/)\n if (sm) {\n const vals = sm[1].split(',').map(v => parseFloat(v.trim()))\n return vals.length >= 2 ? (vals[0] + vals[1]) / 2 : vals[0]\n }\n\n return 1\n}\n\nexport function getInverseScale(el = '.share-page') {\n const s = getPageScale(el)\n return s > 0 ? 1 / s : 1\n}\n\n// ---------- scaler instance ----------\n\nexport function createScaler(el, options = {}) {\n const target = resolveEl(el)\n if (!target) throw new Error('[one-screen-scale] element not found')\n\n const {\n mode = 'contain',\n visualWidth = 450,\n origin = 'top center',\n autoResize = true\n } = options\n\n let currentScale = 1\n let handler = null\n\n function compute() {\n const sw = target.offsetWidth\n const sh = target.offsetHeight\n const tw = window.innerWidth\n const th = window.innerHeight\n\n return calculateScale({\n sourceWidth: sw,\n sourceHeight: sh,\n targetWidth: tw,\n targetHeight: th,\n mode,\n visualWidth,\n screenWidth: window.screen.width\n })\n }\n\n function refresh() {\n const next = compute()\n if (next !== currentScale) {\n currentScale = next\n applyScale(target, next, origin)\n\n // fixed-width 模式:反向补偿高度\n if (mode === 'fixed-width' && next > 0) {\n target.style.width = `${Math.max(window.innerWidth, window.screen.width)}px`\n target.style.height = `${Math.round(window.innerHeight / next)}px`\n }\n }\n return currentScale\n }\n\n function start() {\n refresh()\n if (autoResize) {\n handler = () => refresh()\n window.addEventListener('resize', handler)\n }\n return currentScale\n }\n\n function stop() {\n if (handler) {\n window.removeEventListener('resize', handler)\n handler = null\n }\n }\n\n return { start, stop, refresh, getScale: () => currentScale }\n}\n","/**\n * React hook\n * useFitScreen(ref, options) → { scale, inverseScale, refresh }\n */\nimport { useRef, useState, useEffect, useCallback } from 'react'\nimport { createScaler } from './core.js'\n\nexport function useFitScreen(targetRef, options = {}) {\n const [scale, setScale] = useState(1)\n const [inverseScale, setInverseScale] = useState(1)\n const scalerRef = useRef(null)\n\n useEffect(() => {\n const el = targetRef?.current ?? targetRef\n if (!el) return\n\n const scaler = createScaler(el, options)\n scalerRef.current = scaler\n const s = scaler.start()\n setScale(s)\n setInverseScale(s > 0 ? 1 / s : 1)\n\n const onResize = () => {\n const s = scaler.getScale()\n setScale(s)\n setInverseScale(s > 0 ? 1 / s : 1)\n }\n window.addEventListener('resize', onResize)\n\n return () => {\n window.removeEventListener('resize', onResize)\n scaler.stop()\n scalerRef.current = null\n }\n }, [targetRef])\n\n const refresh = useCallback(() => {\n const el = targetRef?.current ?? targetRef\n if (!el || !scalerRef.current) return\n const s = scalerRef.current.refresh()\n setScale(s)\n setInverseScale(s > 0 ? 1 / s : 1)\n }, [targetRef])\n\n return { scale, inverseScale, refresh }\n}\n"],"names":["resolveEl","el","document","querySelector","HTMLElement","value","current","createScaler","options","target","Error","mode","visualWidth","origin","autoResize","currentScale","handler","compute","sourceWidth","sourceHeight","targetWidth","window","innerWidth","targetHeight","innerHeight","screenWidth","screen","width","scale","Math","max","sx","sy","min","calculateScale","offsetWidth","offsetHeight","refresh","next","style","transform","transformOrigin","applyScale","height","round","start","addEventListener","stop","removeEventListener","getScale","useFitScreen","targetRef","setScale","useState","inverseScale","setInverseScale","scalerRef","useRef","useEffect","scaler","s","onResize","useCallback"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"react.esm.js","sources":["../src/baseStyles.js","../src/core.js","../src/compensate.js","../src/react.js"],"sourcesContent":["/**\n * 指令挂载时注入的「适配生效」基础样式。\n *\n * 前提:页面用了 postcss-px-to-viewport(或同类)把设计稿 px 转成 vw。\n *\n * - contain:宽拉满视口 + 最小一屏高,高度由内容撑开\n * - fixed-width:宽高由 refresh 行内补偿,暂不注入\n * - compensate:第三方组件反缩放(抵消页面 scale,ECharts / Element Plus 等)\n */\n\nexport const STYLE_ELEMENT_ID = 'one-screen-scale-base-styles'\n\nexport const BASE_CLASS = {\n contain: 'one-screen-scale--contain',\n 'fixed-width': 'one-screen-scale--fixed-width',\n compensate: 'one-screen-scale--compensate',\n}\n\nconst BASE_CSS = `\n.${BASE_CLASS.contain} {\n width: 100vw;\n min-height: 100vh;\n min-height: 100dvh;\n}\n/* fixed-width:占位 class,样式先不注入 */\n.${BASE_CLASS['fixed-width']} {\n}\n/*\n * 纯 CSS 反缩放(可选)。v-compensate 指令已改为 JS 行内写入同一公式,更稳。\n * ECharts/Canvas:请用 offsetWidth 量布局盒,勿用 getBoundingClientRect。\n */\n.${BASE_CLASS.compensate} {\n transform: scale(var(--one-screen-inverse-scale, 1));\n transform-origin: top left;\n width: calc(100% * var(--one-screen-scale, 1));\n height: calc(100% * var(--one-screen-scale, 1));\n}\n`.trim()\n\n/** 向 document 注入 / 同步基础样式表(已存在则更新内容,便于本地迭代) */\nexport function ensureBaseStyleSheet() {\n if (typeof document === 'undefined') return null\n let styleEl = document.getElementById(STYLE_ELEMENT_ID)\n if (styleEl) {\n styleEl.textContent = BASE_CSS\n return styleEl\n }\n\n styleEl = document.createElement('style')\n styleEl.id = STYLE_ELEMENT_ID\n styleEl.textContent = BASE_CSS\n document.head.appendChild(styleEl)\n return styleEl\n}\n\n/**\n * 给缩放根节点挂上对应模式的 class。\n * @returns {string | null} 本次加上的 class,便于 stop 时移除\n */\nexport function applyBaseStyles(el, mode = 'contain') {\n if (!el || typeof el.classList === 'undefined') return null\n ensureBaseStyleSheet()\n\n const nextClass = BASE_CLASS[mode] || BASE_CLASS.contain\n // 切换模式时清掉另一套页面级 class(不动 compensate)\n ;[BASE_CLASS.contain, BASE_CLASS['fixed-width']].forEach((cls) => {\n if (cls !== nextClass) el.classList.remove(cls)\n })\n el.classList.add(nextClass)\n return nextClass\n}\n\n/** 移除本库注入的页面级基础 class(不含 compensate) */\nexport function removeBaseStyles(el) {\n if (!el || typeof el.classList === 'undefined') return\n el.classList.remove(BASE_CLASS.contain, BASE_CLASS['fixed-width'])\n}\n\n/** 挂上反缩放补偿 class */\nexport function applyCompensateStyles(el) {\n if (!el || typeof el.classList === 'undefined') return null\n ensureBaseStyleSheet()\n el.classList.add(BASE_CLASS.compensate)\n return BASE_CLASS.compensate\n}\n\n/** 移除反缩放补偿 class */\nexport function removeCompensateStyles(el) {\n if (!el || typeof el.classList === 'undefined') return\n el.classList.remove(BASE_CLASS.compensate)\n}\n","/**\n * 一屏自适应缩放 — 核心算法(框架无关)\n *\n * 两种模式:\n * 'contain' 一屏展示,内容不溢出,用 min(scaleX, scaleY)\n * 'fixed-width' 固定视觉宽度,内容可滚动,反向补高\n *\n * 配套前提:H5 使用 postcss-px-to-viewport(或同类)做移动端适配。\n * createScaler / 指令默认会注入基础宽高样式,保证测量与缩放生效。\n *\n * 图片等异步内容会改变 offsetHeight:默认用 ResizeObserver / 图片 load\n * 在尺寸稳定后再 refresh,避免首屏偶发「量矮了 → scale 偏大」。\n */\n\nimport { applyBaseStyles, removeBaseStyles } from './baseStyles.js'\n\n// ---------- helpers ----------\n\nfunction resolveEl(el) {\n if (!el) return null\n if (typeof el === 'string') return document.querySelector(el)\n if (el instanceof HTMLElement) return el\n // Vue ref / React ref\n if (el.value instanceof HTMLElement) return el.value\n if (el.current instanceof HTMLElement) return el.current\n return null\n}\n\n// ---------- scale calculation ----------\n\nexport function calculateScale({\n sourceWidth,\n sourceHeight,\n targetWidth = (typeof window !== 'undefined' ? window.innerWidth : 1920),\n targetHeight = (typeof window !== 'undefined' ? window.innerHeight : 1080),\n mode = 'contain',\n visualWidth = 450, // fixed-width 模式下的目标视觉宽度\n}) {\n if (!sourceWidth || !sourceHeight || sourceWidth <= 0 || sourceHeight <= 0) return 1\n\n let scale\n\n if (mode === 'fixed-width') {\n // 只用视口宽:视口比目标宽才缩小,否则 1:1(保持清晰)\n scale = targetWidth > visualWidth ? visualWidth / targetWidth : 1\n } else {\n const sx = targetWidth / sourceWidth\n const sy = targetHeight / sourceHeight\n scale = Math.min(sx, sy)\n }\n\n return scale\n}\n\n// ---------- apply / read ----------\n\n/** 页面 scale 变化时抛出,供 ECharts.resize / 业务监听 */\nexport const SCALE_CHANGE_EVENT = 'one-screen-scale-change'\n\nexport function applyScale(el, scale, origin = 'top center') {\n const target = resolveEl(el)\n if (!target) return\n target.style.transform = `scale(${scale})`\n target.style.transformOrigin = origin\n // 供业务 / v-compensate 做反缩放(继承到子树)\n const inv = scale > 0 ? 1 / scale : 1\n target.style.setProperty('--one-screen-scale', String(scale))\n target.style.setProperty('--one-screen-inverse-scale', String(inv))\n if (typeof window !== 'undefined') {\n window.dispatchEvent(\n new CustomEvent(SCALE_CHANGE_EVENT, { detail: { scale, inverseScale: inv, el: target } })\n )\n }\n}\n\nexport function getPageScale(el) {\n const target = resolveEl(el)\n if (!target) return 1\n\n const style = window.getComputedStyle(target)\n const t = style.transform\n if (!t || t === 'none') return 1\n\n // matrix(a, b, c, d, e, f) — 2d\n const m2 = t.match(/matrix\\(([^,]+),\\s*[^,]+,\\s*[^,]+,\\s*([^,]+)/)\n if (m2) return (parseFloat(m2[1]) + parseFloat(m2[2])) / 2\n\n // matrix3d — 3d\n const m3 = t.match(/matrix3d\\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\\)/)\n if (m3) return (parseFloat(m3[1]) + parseFloat(m3[2])) / 2\n\n // scale(x, y) or scale(s)\n const sm = t.match(/scale\\(([^)]+)\\)/)\n if (sm) {\n const vals = sm[1].split(',').map(v => parseFloat(v.trim()))\n return vals.length >= 2 ? (vals[0] + vals[1]) / 2 : vals[0]\n }\n\n return 1\n}\n\nexport function getInverseScale(el = '.share-page') {\n const s = getPageScale(el)\n return s > 0 ? 1 / s : 1\n}\n\n// ---------- scaler instance ----------\n\nexport function createScaler(el, options = {}) {\n const target = resolveEl(el)\n if (!target) throw new Error('[one-screen-scale] element not found')\n\n const {\n mode = 'contain',\n visualWidth = 450,\n origin = 'top center',\n autoResize = true,\n /** 是否自动注入模式所需的宽高基础样式,默认 true */\n injectBaseStyles = true,\n /**\n * 内容尺寸变化时自动重算(图片 load、字体、DOM 撑高)。\n * 解决刷新偶发「图未加载完就测量 → scale 偏大」。\n */\n observeContent = true,\n } = options\n\n let currentScale = 1\n let resizeHandler = null\n let loadHandler = null\n let windowLoadHandler = null\n let rafId = 0\n let unlockRaf = 0\n let ro = null\n let refreshing = false\n let lastMeasured = { w: 0, h: 0 }\n // start / stop 各加一,用来丢掉停掉之后才回来的字体、帧回调\n let session = 0\n\n // fixed-width 会改 inline width/height,stop 时还原\n const savedInline = {\n width: target.style.width,\n height: target.style.height,\n transform: target.style.transform,\n transformOrigin: target.style.transformOrigin,\n }\n\n function compute() {\n const sw = target.offsetWidth\n const sh = target.offsetHeight\n const tw = document.documentElement.clientWidth || window.innerWidth\n const th = window.innerHeight\n\n lastMeasured = { w: sw, h: sh }\n\n return calculateScale({\n sourceWidth: sw,\n sourceHeight: sh,\n targetWidth: tw,\n targetHeight: th,\n mode,\n visualWidth,\n })\n }\n\n function refresh() {\n refreshing = true\n const next = compute()\n currentScale = next\n // 始终写入,保证首次 scale=1 时也带上 transform-origin\n applyScale(target, next, origin)\n\n // fixed-width 模式:反向补偿高度(视觉宽固定,布局宽仍用视口宽再缩小)\n if (mode === 'fixed-width' && next > 0) {\n const viewportWidth = document.documentElement.clientWidth || window.innerWidth\n const nextW = `${viewportWidth}px`\n const nextH = `${Math.round(window.innerHeight / next)}px`\n // 值不变不写,避免 ResizeObserver 死循环\n if (target.style.width !== nextW) target.style.width = nextW\n if (target.style.height !== nextH) target.style.height = nextH\n }\n // 等布局提交后再放开,避免 RO 回调里立刻重入\n const token = session\n cancelAnimationFrame(unlockRaf)\n unlockRaf = requestAnimationFrame(() => {\n if (token !== session) return\n refreshing = false\n })\n return currentScale\n }\n\n function scheduleRefresh() {\n if (refreshing) return\n cancelAnimationFrame(rafId)\n const token = session\n rafId = requestAnimationFrame(() => {\n if (token !== session) return\n // 尺寸没变就跳过(fixed-width 改 height 触发的 RO)\n const w = target.offsetWidth\n const h = target.offsetHeight\n if (w === lastMeasured.w && h === lastMeasured.h && target.style.transform) {\n // 视口变化仍需算:由 window.resize 路径直接 refresh\n return\n }\n refresh()\n })\n }\n\n function onWindowResize() {\n refresh()\n }\n\n function start() {\n session += 1\n const token = session\n // 复用同一实例 start 时,上一次 stop 之后留下的锁要清掉\n refreshing = false\n if (injectBaseStyles) {\n applyBaseStyles(target, mode)\n }\n // 注入样式后再量尺寸\n refresh()\n\n if (autoResize) {\n resizeHandler = onWindowResize\n window.addEventListener('resize', resizeHandler)\n }\n\n if (observeContent && typeof window !== 'undefined') {\n // 1) 子树布局尺寸变化(图片解码后撑高最常见)\n if (typeof ResizeObserver !== 'undefined') {\n ro = new ResizeObserver(() => scheduleRefresh())\n ro.observe(target)\n }\n\n // 2) 捕获阶段听内部 img/video 的 load(含后插入的节点)\n loadHandler = (e) => {\n const t = e.target\n if (t && (t.tagName === 'IMG' || t.tagName === 'VIDEO' || t.tagName === 'IFRAME')) {\n scheduleRefresh()\n }\n }\n target.addEventListener('load', loadHandler, true)\n target.addEventListener('error', loadHandler, true)\n\n // 3) 字体就绪。stop 之后 session 变了,这次回调直接丢掉\n if (document.fonts && document.fonts.ready) {\n document.fonts.ready\n .then(() => {\n if (token !== session) return\n scheduleRefresh()\n })\n .catch(() => {})\n }\n\n // 4) 整页资源 load(缓存命中时可能已 complete)\n if (document.readyState === 'complete') {\n scheduleRefresh()\n } else {\n windowLoadHandler = () => scheduleRefresh()\n window.addEventListener('load', windowLoadHandler, { once: true })\n }\n\n // 5) 再等两帧,覆盖首屏同步布局抖动\n requestAnimationFrame(() => {\n requestAnimationFrame(() => scheduleRefresh())\n })\n }\n\n return currentScale\n }\n\n function stop() {\n session += 1\n if (resizeHandler) {\n window.removeEventListener('resize', resizeHandler)\n resizeHandler = null\n }\n if (loadHandler) {\n target.removeEventListener('load', loadHandler, true)\n target.removeEventListener('error', loadHandler, true)\n loadHandler = null\n }\n if (windowLoadHandler) {\n window.removeEventListener('load', windowLoadHandler)\n windowLoadHandler = null\n }\n if (ro) {\n ro.disconnect()\n ro = null\n }\n cancelAnimationFrame(rafId)\n cancelAnimationFrame(unlockRaf)\n rafId = 0\n unlockRaf = 0\n\n if (injectBaseStyles) {\n removeBaseStyles(target)\n }\n target.style.width = savedInline.width\n target.style.height = savedInline.height\n target.style.transform = savedInline.transform\n target.style.transformOrigin = savedInline.transformOrigin\n target.style.removeProperty('--one-screen-scale')\n target.style.removeProperty('--one-screen-inverse-scale')\n }\n\n return { start, stop, refresh, getScale: () => currentScale }\n}\n\nexport {\n applyBaseStyles,\n removeBaseStyles,\n applyCompensateStyles,\n removeCompensateStyles,\n ensureBaseStyleSheet,\n BASE_CLASS,\n} from './baseStyles.js'\n","/**\n * 组件级反缩放补偿(与业务 WinRateChart 手写公式一致):\n * transform: scale(inverse); width/height: 100% * pageScale; origin: top left\n *\n * 只抵消页面 v-fit-screen 的 scale;不处理浏览器 Ctrl+/-。\n */\n\nimport { SCALE_CHANGE_EVENT } from './core.js'\n\n/** 子树 canvas 需要重绘时抛出(挂在每个 <canvas> 上) */\nexport const CANVAS_RESIZE_EVENT = 'one-screen-scale-canvas-resize'\n\n/** @type {null | { getInstanceByDom?: (el: Element) => { resize: Function } | undefined }} */\nlet registeredEcharts = null\n\n/** 业务可注入 echarts 模块(避免插件硬依赖);供 resizeCanvas 探测 */\nexport function setCompensateEcharts(echarts) {\n registeredEcharts = echarts || null\n}\n\nfunction readCssVarScale(el) {\n if (!el || typeof getComputedStyle === 'undefined') return NaN\n const raw = getComputedStyle(el).getPropertyValue('--one-screen-scale').trim()\n if (!raw) return NaN\n const v = parseFloat(raw)\n return v > 0 ? v : NaN\n}\n\nfunction readTransformScale(el) {\n if (!el || typeof getComputedStyle === 'undefined') return NaN\n const t = getComputedStyle(el).transform\n if (!t || t === 'none') return NaN\n\n const m2 = t.match(/matrix\\(([^,]+),\\s*[^,]+,\\s*[^,]+,\\s*([^,]+)/)\n if (m2) return (parseFloat(m2[1]) + parseFloat(m2[2])) / 2\n\n const m3 = t.match(\n /matrix3d\\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\\)/\n )\n if (m3) return (parseFloat(m3[1]) + parseFloat(m3[2])) / 2\n\n const sm = t.match(/scale\\(([^)]+)\\)/)\n if (sm) {\n const vals = sm[1].split(',').map((v) => parseFloat(v.trim()))\n return vals.length >= 2 ? (vals[0] + vals[1]) / 2 : vals[0]\n }\n return NaN\n}\n\n/** 从 fromEl 向上解析页面 scale(跳过已挂补偿的节点自身 transform) */\nexport function resolvePageScale(fromEl) {\n let node = fromEl\n while (node && node.nodeType === 1) {\n const fromVar = readCssVarScale(node)\n if (fromVar > 0) return fromVar\n node = node.parentElement\n }\n\n node = fromEl\n while (node && node.nodeType === 1) {\n if (!node.__oneScreenCompensate) {\n const fromTf = readTransformScale(node)\n if (fromTf > 0 && fromTf !== 1) return fromTf\n }\n node = node.parentElement\n }\n return 1\n}\n\n/**\n * 给槽位内层写入反缩放行内样式。父槽位需有明确宽高。\n * @returns {{ scale: number, inverseScale: number }}\n */\nexport function applyCompensateTransform(el) {\n if (!el) return { scale: 1, inverseScale: 1 }\n const scale = resolvePageScale(el)\n const inverseScale = scale > 0 ? 1 / scale : 1\n el.style.transform = `scale(${inverseScale})`\n el.style.transformOrigin = 'top left'\n el.style.width = `${100 * scale}%`\n el.style.height = `${100 * scale}%`\n el.style.overflow = 'visible'\n el.__oneScreenCompensate = true\n return { scale, inverseScale }\n}\n\nexport function clearCompensateTransform(el) {\n if (!el) return\n el.style.transform = ''\n el.style.transformOrigin = ''\n el.style.width = ''\n el.style.height = ''\n el.style.overflow = ''\n delete el.__oneScreenCompensate\n}\n\nfunction resolveEchartsLib(bindingEcharts) {\n return (\n bindingEcharts ||\n registeredEcharts ||\n (typeof globalThis !== 'undefined' ? globalThis.echarts : null) ||\n null\n )\n}\n\nfunction boxSize(node) {\n const w = Math.max(1, Math.round(node?.offsetWidth || 0))\n const h = Math.max(1, Math.round(node?.offsetHeight || 0))\n return { width: w, height: h }\n}\n\nfunction tryEchartsResize(node, getInst) {\n if (!node || typeof getInst !== 'function') return false\n let inst\n try {\n inst = getInst(node)\n } catch {\n return false\n }\n if (!inst || typeof inst.resize !== 'function') return false\n const { width, height } = boxSize(node)\n try {\n inst.resize({ width, height })\n return true\n } catch {\n return false\n }\n}\n\nfunction tryChartJsResize(canvas) {\n const Chart = typeof globalThis !== 'undefined' ? globalThis.Chart : null\n const getChart = Chart?.getChart\n if (typeof getChart !== 'function') return false\n let chart\n try {\n chart = getChart(canvas)\n } catch {\n return false\n }\n if (!chart || typeof chart.resize !== 'function') return false\n try {\n chart.resize()\n return true\n } catch {\n return false\n }\n}\n\n/**\n * 补偿后通知子树 canvas 重绘:\n * - 探测 ECharts / Chart.js(若已加载)\n * - 每个 <canvas> 派发 CANVAS_RESIZE_EVENT,业务可自行监听重绘\n * 量尺寸用 offsetWidth(勿用 getBoundingClientRect)\n *\n * @returns {number} 成功调用已知图表库 resize 的次数\n */\nexport function resizeCanvasUnder(el, echartsLib) {\n if (!el) return 0\n\n const echarts = resolveEchartsLib(echartsLib)\n const getInst = echarts?.getInstanceByDom\n let count = 0\n\n const canvases = el.querySelectorAll ? el.querySelectorAll('canvas') : []\n for (const canvas of canvases) {\n const host = canvas.parentElement || canvas\n const { width, height } = boxSize(host)\n\n if (tryEchartsResize(canvas, getInst)) count += 1\n else if (tryEchartsResize(host, getInst)) count += 1\n\n if (tryChartJsResize(canvas)) count += 1\n\n try {\n canvas.dispatchEvent(\n new CustomEvent(CANVAS_RESIZE_EVENT, {\n bubbles: true,\n detail: { width, height, canvas, el },\n })\n )\n } catch {\n /* ignore */\n }\n }\n\n // ECharts 常挂在 div 上(不一定已有 canvas)\n if (typeof getInst === 'function') {\n const nodes = [el, ...el.querySelectorAll('div')]\n for (const node of nodes) {\n if (tryEchartsResize(node, getInst)) count += 1\n }\n }\n\n return count\n}\n\n/** @deprecated 请用 resizeCanvasUnder */\nexport function resizeEchartsUnder(el, echartsLib) {\n return resizeCanvasUnder(el, echartsLib)\n}\n\nfunction wantsResizeCanvas(options = {}) {\n return Boolean(\n options.resizeCanvas ||\n options.echarts || // 兼容旧名\n options.echartsLib\n )\n}\n\nfunction resolveBoundEcharts(options = {}) {\n if (typeof options.echarts === 'object') return options.echarts\n if (options.echartsLib) return options.echartsLib\n return undefined\n}\n\n/** options 可以是对象,也可以是返回最新对象的函数(指令 / hook 传 getter 用) */\nfunction readLiveOptions(options) {\n const value = typeof options === 'function' ? options() : options\n return value && typeof value === 'object' ? value : {}\n}\n\n/**\n * @param {HTMLElement} el\n * @param {{\n * resizeCanvas?: boolean,\n * echarts?: boolean | object,\n * echartsLib?: object,\n * onRefresh?: Function\n * } | (() => object)} [options]\n */\nexport function createCompensate(el, options = {}) {\n if (!el) throw new Error('[one-screen-scale] compensate element required')\n\n const initial = readLiveOptions(options)\n const wantResize = wantsResizeCanvas(initial)\n let raf = 0\n let ro = null\n let mo = null\n let retryTimers = []\n let stopped = false\n\n const clearRetries = () => {\n retryTimers.forEach((id) => clearTimeout(id))\n retryTimers = []\n }\n\n const refresh = () => {\n if (stopped) return\n const live = readLiveOptions(options)\n const { scale, inverseScale } = applyCompensateTransform(el)\n if (typeof live.onRefresh === 'function') {\n try {\n live.onRefresh({ scale, inverseScale, el })\n } catch {\n /* user callback */\n }\n }\n if (wantsResizeCanvas(live)) {\n const lib = resolveBoundEcharts(live)\n const n = resizeCanvasUnder(el, lib)\n if (n === 0) {\n clearRetries()\n ;[50, 200].forEach((delay) => {\n const id = setTimeout(() => {\n if (stopped) return\n const next = readLiveOptions(options)\n resizeCanvasUnder(el, resolveBoundEcharts(next))\n }, delay)\n retryTimers.push(id)\n })\n }\n }\n }\n\n const schedule = () => {\n if (stopped) return\n cancelAnimationFrame(raf)\n raf = requestAnimationFrame(() => {\n raf = 0\n if (stopped) return\n refresh()\n })\n }\n\n refresh()\n\n if (typeof window !== 'undefined') {\n window.addEventListener(SCALE_CHANGE_EVENT, schedule)\n window.addEventListener('resize', schedule)\n }\n\n if (typeof ResizeObserver !== 'undefined') {\n ro = new ResizeObserver(schedule)\n if (el.parentElement) ro.observe(el.parentElement)\n ro.observe(el)\n }\n\n if (wantResize && typeof MutationObserver !== 'undefined') {\n mo = new MutationObserver(schedule)\n mo.observe(el, { childList: true, subtree: true })\n }\n\n const stop = () => {\n stopped = true\n cancelAnimationFrame(raf)\n clearRetries()\n raf = 0\n if (typeof window !== 'undefined') {\n window.removeEventListener(SCALE_CHANGE_EVENT, schedule)\n window.removeEventListener('resize', schedule)\n }\n ro?.disconnect()\n ro = null\n mo?.disconnect()\n mo = null\n clearCompensateTransform(el)\n }\n\n return { refresh, stop }\n}\n","/**\n * React hooks\n * useFitScreen(ref, options) → { scale, inverseScale, refresh }\n * useCompensate(ref, options) → { refresh } // 对应 Vue v-compensate\n */\nimport { useRef, useState, useEffect, useCallback } from 'react'\nimport { createScaler, SCALE_CHANGE_EVENT } from './core.js'\nimport { createCompensate } from './compensate.js'\n\nfunction resolveElement(targetRef) {\n if (!targetRef) return null\n if (typeof HTMLElement !== 'undefined' && targetRef instanceof HTMLElement) return targetRef\n if (targetRef.current instanceof HTMLElement) return targetRef.current\n if (targetRef.value instanceof HTMLElement) return targetRef.value\n return null\n}\n\n/** ref 第一次 effect 时可能还是空的,等到节点出现再挂。卸载时停掉等待 */\nfunction waitForElement(targetRef, onReady) {\n let raf = 0\n let timer = 0\n let frames = 0\n let settled = false\n\n const finish = () => {\n settled = true\n cancelAnimationFrame(raf)\n clearInterval(timer)\n }\n\n const tick = () => {\n if (settled) return\n const el = resolveElement(targetRef)\n if (el) {\n finish()\n onReady(el)\n return\n }\n frames += 1\n if (frames < 60) raf = requestAnimationFrame(tick)\n else if (!timer) timer = setInterval(tick, 200)\n }\n\n tick()\n return finish\n}\n\nexport function useFitScreen(targetRef, options = {}) {\n const [scale, setScale] = useState(1)\n const [inverseScale, setInverseScale] = useState(1)\n const scalerRef = useRef(null)\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n useEffect(() => {\n let detach = () => {}\n let disposed = false\n\n const cancelWait = waitForElement(targetRef, (el) => {\n if (disposed) return\n const scaler = createScaler(el, optionsRef.current)\n scalerRef.current = scaler\n const s = scaler.start()\n setScale(s)\n setInverseScale(s > 0 ? 1 / s : 1)\n\n const onScale = (event) => {\n if (event.detail?.el !== el) return\n setScale(event.detail.scale)\n setInverseScale(event.detail.inverseScale)\n }\n window.addEventListener(SCALE_CHANGE_EVENT, onScale)\n detach = () => {\n window.removeEventListener(SCALE_CHANGE_EVENT, onScale)\n scaler.stop()\n scalerRef.current = null\n }\n })\n\n return () => {\n disposed = true\n cancelWait()\n detach()\n }\n }, [targetRef])\n\n const refresh = useCallback(() => {\n const el = targetRef?.current ?? targetRef\n if (!el || !scalerRef.current) return\n const s = scalerRef.current.refresh()\n setScale(s)\n setInverseScale(s > 0 ? 1 / s : 1)\n }, [targetRef])\n\n return { scale, inverseScale, refresh }\n}\n\n/**\n * 第三方组件反缩放补偿(对应 Vue `v-compensate`)。\n * 挂在槽位内层 ref 上;父槽位需有明确宽高。\n *\n * @example\n * const innerRef = useRef(null)\n * useCompensate(innerRef, { resizeCanvas: true })\n */\nexport function useCompensate(targetRef, options = {}) {\n const apiRef = useRef(null)\n const optionsRef = useRef(options)\n optionsRef.current = options\n const resizeFlag = Boolean(options?.resizeCanvas || options?.echarts || options?.echartsLib)\n const lib =\n (options && typeof options.echarts === 'object' && options.echarts) ||\n options?.echartsLib ||\n null\n\n useEffect(() => {\n let api = null\n let disposed = false\n\n const cancelWait = waitForElement(targetRef, (el) => {\n if (disposed) return\n api = createCompensate(el, () => optionsRef.current || {})\n apiRef.current = api\n })\n\n return () => {\n disposed = true\n cancelWait()\n api?.stop()\n apiRef.current = null\n }\n }, [targetRef, resizeFlag, lib])\n\n const refresh = useCallback(() => {\n apiRef.current?.refresh()\n }, [])\n\n return { refresh }\n}\n"],"names":["STYLE_ELEMENT_ID","BASE_CLASS","contain","compensate","BASE_CSS","trim","applyBaseStyles","el","mode","classList","document","styleEl","getElementById","textContent","createElement","id","head","appendChild","ensureBaseStyleSheet","nextClass","forEach","cls","remove","add","resolveEl","querySelector","HTMLElement","value","current","SCALE_CHANGE_EVENT","createScaler","options","target","Error","visualWidth","origin","autoResize","injectBaseStyles","observeContent","currentScale","resizeHandler","loadHandler","windowLoadHandler","rafId","unlockRaf","ro","refreshing","lastMeasured","w","h","session","savedInline","width","style","height","transform","transformOrigin","compute","sw","offsetWidth","sh","offsetHeight","tw","documentElement","clientWidth","window","innerWidth","th","innerHeight","sourceWidth","sourceHeight","targetWidth","targetHeight","scale","sx","sy","Math","min","calculateScale","refresh","next","inv","setProperty","String","dispatchEvent","CustomEvent","detail","inverseScale","applyScale","nextW","nextH","round","token","cancelAnimationFrame","requestAnimationFrame","scheduleRefresh","onWindowResize","start","addEventListener","ResizeObserver","observe","e","t","tagName","fonts","ready","then","catch","readyState","once","stop","removeEventListener","disconnect","removeBaseStyles","removeProperty","getScale","readCssVarScale","getComputedStyle","NaN","raw","getPropertyValue","v","parseFloat","readTransformScale","m2","match","m3","sm","vals","split","map","length","applyCompensateTransform","fromEl","node","nodeType","fromVar","parentElement","__oneScreenCompensate","fromTf","resolvePageScale","overflow","boxSize","max","tryEchartsResize","getInst","inst","resize","tryChartJsResize","canvas","Chart","globalThis","getChart","chart","resizeCanvasUnder","echartsLib","echarts","getInstanceByDom","count","canvases","querySelectorAll","host","bubbles","nodes","wantsResizeCanvas","Boolean","resizeCanvas","resolveBoundEcharts","readLiveOptions","waitForElement","targetRef","onReady","raf","timer","frames","settled","finish","clearInterval","tick","resolveElement","setInterval","useFitScreen","setScale","useState","setInverseScale","scalerRef","useRef","optionsRef","useEffect","detach","disposed","cancelWait","scaler","s","onScale","event","useCallback","useCompensate","apiRef","resizeFlag","lib","api","wantResize","mo","retryTimers","stopped","clearRetries","clearTimeout","live","onRefresh","delay","setTimeout","push","schedule","MutationObserver","childList","subtree","clearCompensateTransform","createCompensate"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "one-screen-scale",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Transform:scale 实现的一屏自适应方案。框架无关核心 + Vue/React
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Transform:scale 实现的一屏自适应方案。框架无关核心 + Vue/React 开箱即用;指令自动注入模式基础宽高样式",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/request-stack/one-screen-scale.git"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 指令挂载时注入的「适配生效」基础样式。
|
|
3
|
+
*
|
|
4
|
+
* 前提:页面用了 postcss-px-to-viewport(或同类)把设计稿 px 转成 vw。
|
|
5
|
+
*
|
|
6
|
+
* - contain:宽拉满视口 + 最小一屏高,高度由内容撑开
|
|
7
|
+
* - fixed-width:宽高由 refresh 行内补偿,暂不注入
|
|
8
|
+
* - compensate:第三方组件反缩放(抵消页面 scale,ECharts / Element Plus 等)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const STYLE_ELEMENT_ID = 'one-screen-scale-base-styles'
|
|
12
|
+
|
|
13
|
+
export const BASE_CLASS = {
|
|
14
|
+
contain: 'one-screen-scale--contain',
|
|
15
|
+
'fixed-width': 'one-screen-scale--fixed-width',
|
|
16
|
+
compensate: 'one-screen-scale--compensate',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const BASE_CSS = `
|
|
20
|
+
.${BASE_CLASS.contain} {
|
|
21
|
+
width: 100vw;
|
|
22
|
+
min-height: 100vh;
|
|
23
|
+
min-height: 100dvh;
|
|
24
|
+
}
|
|
25
|
+
/* fixed-width:占位 class,样式先不注入 */
|
|
26
|
+
.${BASE_CLASS['fixed-width']} {
|
|
27
|
+
}
|
|
28
|
+
/*
|
|
29
|
+
* 纯 CSS 反缩放(可选)。v-compensate 指令已改为 JS 行内写入同一公式,更稳。
|
|
30
|
+
* ECharts/Canvas:请用 offsetWidth 量布局盒,勿用 getBoundingClientRect。
|
|
31
|
+
*/
|
|
32
|
+
.${BASE_CLASS.compensate} {
|
|
33
|
+
transform: scale(var(--one-screen-inverse-scale, 1));
|
|
34
|
+
transform-origin: top left;
|
|
35
|
+
width: calc(100% * var(--one-screen-scale, 1));
|
|
36
|
+
height: calc(100% * var(--one-screen-scale, 1));
|
|
37
|
+
}
|
|
38
|
+
`.trim()
|
|
39
|
+
|
|
40
|
+
/** 向 document 注入 / 同步基础样式表(已存在则更新内容,便于本地迭代) */
|
|
41
|
+
export function ensureBaseStyleSheet() {
|
|
42
|
+
if (typeof document === 'undefined') return null
|
|
43
|
+
let styleEl = document.getElementById(STYLE_ELEMENT_ID)
|
|
44
|
+
if (styleEl) {
|
|
45
|
+
styleEl.textContent = BASE_CSS
|
|
46
|
+
return styleEl
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
styleEl = document.createElement('style')
|
|
50
|
+
styleEl.id = STYLE_ELEMENT_ID
|
|
51
|
+
styleEl.textContent = BASE_CSS
|
|
52
|
+
document.head.appendChild(styleEl)
|
|
53
|
+
return styleEl
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 给缩放根节点挂上对应模式的 class。
|
|
58
|
+
* @returns {string | null} 本次加上的 class,便于 stop 时移除
|
|
59
|
+
*/
|
|
60
|
+
export function applyBaseStyles(el, mode = 'contain') {
|
|
61
|
+
if (!el || typeof el.classList === 'undefined') return null
|
|
62
|
+
ensureBaseStyleSheet()
|
|
63
|
+
|
|
64
|
+
const nextClass = BASE_CLASS[mode] || BASE_CLASS.contain
|
|
65
|
+
// 切换模式时清掉另一套页面级 class(不动 compensate)
|
|
66
|
+
;[BASE_CLASS.contain, BASE_CLASS['fixed-width']].forEach((cls) => {
|
|
67
|
+
if (cls !== nextClass) el.classList.remove(cls)
|
|
68
|
+
})
|
|
69
|
+
el.classList.add(nextClass)
|
|
70
|
+
return nextClass
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 移除本库注入的页面级基础 class(不含 compensate) */
|
|
74
|
+
export function removeBaseStyles(el) {
|
|
75
|
+
if (!el || typeof el.classList === 'undefined') return
|
|
76
|
+
el.classList.remove(BASE_CLASS.contain, BASE_CLASS['fixed-width'])
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 挂上反缩放补偿 class */
|
|
80
|
+
export function applyCompensateStyles(el) {
|
|
81
|
+
if (!el || typeof el.classList === 'undefined') return null
|
|
82
|
+
ensureBaseStyleSheet()
|
|
83
|
+
el.classList.add(BASE_CLASS.compensate)
|
|
84
|
+
return BASE_CLASS.compensate
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 移除反缩放补偿 class */
|
|
88
|
+
export function removeCompensateStyles(el) {
|
|
89
|
+
if (!el || typeof el.classList === 'undefined') return
|
|
90
|
+
el.classList.remove(BASE_CLASS.compensate)
|
|
91
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组件级反缩放补偿(与业务 WinRateChart 手写公式一致):
|
|
3
|
+
* transform: scale(inverse); width/height: 100% * pageScale; origin: top left
|
|
4
|
+
*
|
|
5
|
+
* 只抵消页面 v-fit-screen 的 scale;不处理浏览器 Ctrl+/-。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SCALE_CHANGE_EVENT } from './core.js'
|
|
9
|
+
|
|
10
|
+
/** 子树 canvas 需要重绘时抛出(挂在每个 <canvas> 上) */
|
|
11
|
+
export const CANVAS_RESIZE_EVENT = 'one-screen-scale-canvas-resize'
|
|
12
|
+
|
|
13
|
+
/** @type {null | { getInstanceByDom?: (el: Element) => { resize: Function } | undefined }} */
|
|
14
|
+
let registeredEcharts = null
|
|
15
|
+
|
|
16
|
+
/** 业务可注入 echarts 模块(避免插件硬依赖);供 resizeCanvas 探测 */
|
|
17
|
+
export function setCompensateEcharts(echarts) {
|
|
18
|
+
registeredEcharts = echarts || null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function readCssVarScale(el) {
|
|
22
|
+
if (!el || typeof getComputedStyle === 'undefined') return NaN
|
|
23
|
+
const raw = getComputedStyle(el).getPropertyValue('--one-screen-scale').trim()
|
|
24
|
+
if (!raw) return NaN
|
|
25
|
+
const v = parseFloat(raw)
|
|
26
|
+
return v > 0 ? v : NaN
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readTransformScale(el) {
|
|
30
|
+
if (!el || typeof getComputedStyle === 'undefined') return NaN
|
|
31
|
+
const t = getComputedStyle(el).transform
|
|
32
|
+
if (!t || t === 'none') return NaN
|
|
33
|
+
|
|
34
|
+
const m2 = t.match(/matrix\(([^,]+),\s*[^,]+,\s*[^,]+,\s*([^,]+)/)
|
|
35
|
+
if (m2) return (parseFloat(m2[1]) + parseFloat(m2[2])) / 2
|
|
36
|
+
|
|
37
|
+
const m3 = t.match(
|
|
38
|
+
/matrix3d\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\)/
|
|
39
|
+
)
|
|
40
|
+
if (m3) return (parseFloat(m3[1]) + parseFloat(m3[2])) / 2
|
|
41
|
+
|
|
42
|
+
const sm = t.match(/scale\(([^)]+)\)/)
|
|
43
|
+
if (sm) {
|
|
44
|
+
const vals = sm[1].split(',').map((v) => parseFloat(v.trim()))
|
|
45
|
+
return vals.length >= 2 ? (vals[0] + vals[1]) / 2 : vals[0]
|
|
46
|
+
}
|
|
47
|
+
return NaN
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 从 fromEl 向上解析页面 scale(跳过已挂补偿的节点自身 transform) */
|
|
51
|
+
export function resolvePageScale(fromEl) {
|
|
52
|
+
let node = fromEl
|
|
53
|
+
while (node && node.nodeType === 1) {
|
|
54
|
+
const fromVar = readCssVarScale(node)
|
|
55
|
+
if (fromVar > 0) return fromVar
|
|
56
|
+
node = node.parentElement
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
node = fromEl
|
|
60
|
+
while (node && node.nodeType === 1) {
|
|
61
|
+
if (!node.__oneScreenCompensate) {
|
|
62
|
+
const fromTf = readTransformScale(node)
|
|
63
|
+
if (fromTf > 0 && fromTf !== 1) return fromTf
|
|
64
|
+
}
|
|
65
|
+
node = node.parentElement
|
|
66
|
+
}
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 给槽位内层写入反缩放行内样式。父槽位需有明确宽高。
|
|
72
|
+
* @returns {{ scale: number, inverseScale: number }}
|
|
73
|
+
*/
|
|
74
|
+
export function applyCompensateTransform(el) {
|
|
75
|
+
if (!el) return { scale: 1, inverseScale: 1 }
|
|
76
|
+
const scale = resolvePageScale(el)
|
|
77
|
+
const inverseScale = scale > 0 ? 1 / scale : 1
|
|
78
|
+
el.style.transform = `scale(${inverseScale})`
|
|
79
|
+
el.style.transformOrigin = 'top left'
|
|
80
|
+
el.style.width = `${100 * scale}%`
|
|
81
|
+
el.style.height = `${100 * scale}%`
|
|
82
|
+
el.style.overflow = 'visible'
|
|
83
|
+
el.__oneScreenCompensate = true
|
|
84
|
+
return { scale, inverseScale }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function clearCompensateTransform(el) {
|
|
88
|
+
if (!el) return
|
|
89
|
+
el.style.transform = ''
|
|
90
|
+
el.style.transformOrigin = ''
|
|
91
|
+
el.style.width = ''
|
|
92
|
+
el.style.height = ''
|
|
93
|
+
el.style.overflow = ''
|
|
94
|
+
delete el.__oneScreenCompensate
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveEchartsLib(bindingEcharts) {
|
|
98
|
+
return (
|
|
99
|
+
bindingEcharts ||
|
|
100
|
+
registeredEcharts ||
|
|
101
|
+
(typeof globalThis !== 'undefined' ? globalThis.echarts : null) ||
|
|
102
|
+
null
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function boxSize(node) {
|
|
107
|
+
const w = Math.max(1, Math.round(node?.offsetWidth || 0))
|
|
108
|
+
const h = Math.max(1, Math.round(node?.offsetHeight || 0))
|
|
109
|
+
return { width: w, height: h }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function tryEchartsResize(node, getInst) {
|
|
113
|
+
if (!node || typeof getInst !== 'function') return false
|
|
114
|
+
let inst
|
|
115
|
+
try {
|
|
116
|
+
inst = getInst(node)
|
|
117
|
+
} catch {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
if (!inst || typeof inst.resize !== 'function') return false
|
|
121
|
+
const { width, height } = boxSize(node)
|
|
122
|
+
try {
|
|
123
|
+
inst.resize({ width, height })
|
|
124
|
+
return true
|
|
125
|
+
} catch {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function tryChartJsResize(canvas) {
|
|
131
|
+
const Chart = typeof globalThis !== 'undefined' ? globalThis.Chart : null
|
|
132
|
+
const getChart = Chart?.getChart
|
|
133
|
+
if (typeof getChart !== 'function') return false
|
|
134
|
+
let chart
|
|
135
|
+
try {
|
|
136
|
+
chart = getChart(canvas)
|
|
137
|
+
} catch {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
if (!chart || typeof chart.resize !== 'function') return false
|
|
141
|
+
try {
|
|
142
|
+
chart.resize()
|
|
143
|
+
return true
|
|
144
|
+
} catch {
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 补偿后通知子树 canvas 重绘:
|
|
151
|
+
* - 探测 ECharts / Chart.js(若已加载)
|
|
152
|
+
* - 每个 <canvas> 派发 CANVAS_RESIZE_EVENT,业务可自行监听重绘
|
|
153
|
+
* 量尺寸用 offsetWidth(勿用 getBoundingClientRect)
|
|
154
|
+
*
|
|
155
|
+
* @returns {number} 成功调用已知图表库 resize 的次数
|
|
156
|
+
*/
|
|
157
|
+
export function resizeCanvasUnder(el, echartsLib) {
|
|
158
|
+
if (!el) return 0
|
|
159
|
+
|
|
160
|
+
const echarts = resolveEchartsLib(echartsLib)
|
|
161
|
+
const getInst = echarts?.getInstanceByDom
|
|
162
|
+
let count = 0
|
|
163
|
+
|
|
164
|
+
const canvases = el.querySelectorAll ? el.querySelectorAll('canvas') : []
|
|
165
|
+
for (const canvas of canvases) {
|
|
166
|
+
const host = canvas.parentElement || canvas
|
|
167
|
+
const { width, height } = boxSize(host)
|
|
168
|
+
|
|
169
|
+
if (tryEchartsResize(canvas, getInst)) count += 1
|
|
170
|
+
else if (tryEchartsResize(host, getInst)) count += 1
|
|
171
|
+
|
|
172
|
+
if (tryChartJsResize(canvas)) count += 1
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
canvas.dispatchEvent(
|
|
176
|
+
new CustomEvent(CANVAS_RESIZE_EVENT, {
|
|
177
|
+
bubbles: true,
|
|
178
|
+
detail: { width, height, canvas, el },
|
|
179
|
+
})
|
|
180
|
+
)
|
|
181
|
+
} catch {
|
|
182
|
+
/* ignore */
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ECharts 常挂在 div 上(不一定已有 canvas)
|
|
187
|
+
if (typeof getInst === 'function') {
|
|
188
|
+
const nodes = [el, ...el.querySelectorAll('div')]
|
|
189
|
+
for (const node of nodes) {
|
|
190
|
+
if (tryEchartsResize(node, getInst)) count += 1
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return count
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** @deprecated 请用 resizeCanvasUnder */
|
|
198
|
+
export function resizeEchartsUnder(el, echartsLib) {
|
|
199
|
+
return resizeCanvasUnder(el, echartsLib)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function wantsResizeCanvas(options = {}) {
|
|
203
|
+
return Boolean(
|
|
204
|
+
options.resizeCanvas ||
|
|
205
|
+
options.echarts || // 兼容旧名
|
|
206
|
+
options.echartsLib
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function resolveBoundEcharts(options = {}) {
|
|
211
|
+
if (typeof options.echarts === 'object') return options.echarts
|
|
212
|
+
if (options.echartsLib) return options.echartsLib
|
|
213
|
+
return undefined
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** options 可以是对象,也可以是返回最新对象的函数(指令 / hook 传 getter 用) */
|
|
217
|
+
function readLiveOptions(options) {
|
|
218
|
+
const value = typeof options === 'function' ? options() : options
|
|
219
|
+
return value && typeof value === 'object' ? value : {}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @param {HTMLElement} el
|
|
224
|
+
* @param {{
|
|
225
|
+
* resizeCanvas?: boolean,
|
|
226
|
+
* echarts?: boolean | object,
|
|
227
|
+
* echartsLib?: object,
|
|
228
|
+
* onRefresh?: Function
|
|
229
|
+
* } | (() => object)} [options]
|
|
230
|
+
*/
|
|
231
|
+
export function createCompensate(el, options = {}) {
|
|
232
|
+
if (!el) throw new Error('[one-screen-scale] compensate element required')
|
|
233
|
+
|
|
234
|
+
const initial = readLiveOptions(options)
|
|
235
|
+
const wantResize = wantsResizeCanvas(initial)
|
|
236
|
+
let raf = 0
|
|
237
|
+
let ro = null
|
|
238
|
+
let mo = null
|
|
239
|
+
let retryTimers = []
|
|
240
|
+
let stopped = false
|
|
241
|
+
|
|
242
|
+
const clearRetries = () => {
|
|
243
|
+
retryTimers.forEach((id) => clearTimeout(id))
|
|
244
|
+
retryTimers = []
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const refresh = () => {
|
|
248
|
+
if (stopped) return
|
|
249
|
+
const live = readLiveOptions(options)
|
|
250
|
+
const { scale, inverseScale } = applyCompensateTransform(el)
|
|
251
|
+
if (typeof live.onRefresh === 'function') {
|
|
252
|
+
try {
|
|
253
|
+
live.onRefresh({ scale, inverseScale, el })
|
|
254
|
+
} catch {
|
|
255
|
+
/* user callback */
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (wantsResizeCanvas(live)) {
|
|
259
|
+
const lib = resolveBoundEcharts(live)
|
|
260
|
+
const n = resizeCanvasUnder(el, lib)
|
|
261
|
+
if (n === 0) {
|
|
262
|
+
clearRetries()
|
|
263
|
+
;[50, 200].forEach((delay) => {
|
|
264
|
+
const id = setTimeout(() => {
|
|
265
|
+
if (stopped) return
|
|
266
|
+
const next = readLiveOptions(options)
|
|
267
|
+
resizeCanvasUnder(el, resolveBoundEcharts(next))
|
|
268
|
+
}, delay)
|
|
269
|
+
retryTimers.push(id)
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const schedule = () => {
|
|
276
|
+
if (stopped) return
|
|
277
|
+
cancelAnimationFrame(raf)
|
|
278
|
+
raf = requestAnimationFrame(() => {
|
|
279
|
+
raf = 0
|
|
280
|
+
if (stopped) return
|
|
281
|
+
refresh()
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
refresh()
|
|
286
|
+
|
|
287
|
+
if (typeof window !== 'undefined') {
|
|
288
|
+
window.addEventListener(SCALE_CHANGE_EVENT, schedule)
|
|
289
|
+
window.addEventListener('resize', schedule)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
293
|
+
ro = new ResizeObserver(schedule)
|
|
294
|
+
if (el.parentElement) ro.observe(el.parentElement)
|
|
295
|
+
ro.observe(el)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (wantResize && typeof MutationObserver !== 'undefined') {
|
|
299
|
+
mo = new MutationObserver(schedule)
|
|
300
|
+
mo.observe(el, { childList: true, subtree: true })
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const stop = () => {
|
|
304
|
+
stopped = true
|
|
305
|
+
cancelAnimationFrame(raf)
|
|
306
|
+
clearRetries()
|
|
307
|
+
raf = 0
|
|
308
|
+
if (typeof window !== 'undefined') {
|
|
309
|
+
window.removeEventListener(SCALE_CHANGE_EVENT, schedule)
|
|
310
|
+
window.removeEventListener('resize', schedule)
|
|
311
|
+
}
|
|
312
|
+
ro?.disconnect()
|
|
313
|
+
ro = null
|
|
314
|
+
mo?.disconnect()
|
|
315
|
+
mo = null
|
|
316
|
+
clearCompensateTransform(el)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return { refresh, stop }
|
|
320
|
+
}
|