one-screen-scale 1.1.0 → 1.3.1
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/README.md +356 -186
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +122 -46
- 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 +49 -19
- package/dist/react.esm.js +1 -1
- package/dist/react.esm.js.map +1 -1
- package/package.json +71 -71
- package/src/baseStyles.js +107 -0
- package/src/compensate.js +320 -0
- package/src/composables.js +13 -11
- package/src/core.js +317 -160
- package/src/directives.js +144 -34
- package/src/index.d.ts +122 -46
- package/src/index.js +33 -14
- package/src/react.d.ts +49 -19
- package/src/react.js +139 -46
|
@@ -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
|
+
}
|
package/src/composables.js
CHANGED
|
@@ -3,33 +3,35 @@
|
|
|
3
3
|
* useFitScreen(ref, options) → { scale, inverseScale, refresh }
|
|
4
4
|
*/
|
|
5
5
|
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
|
6
|
-
import { createScaler,
|
|
6
|
+
import { createScaler, SCALE_CHANGE_EVENT } from './core.js'
|
|
7
7
|
|
|
8
8
|
export function useFitScreen(targetRef, options = {}) {
|
|
9
9
|
const scale = ref(1)
|
|
10
10
|
const inverseScale = ref(1)
|
|
11
11
|
|
|
12
12
|
let scaler = null
|
|
13
|
-
let
|
|
13
|
+
let onScale = null
|
|
14
14
|
|
|
15
15
|
function setup(el) {
|
|
16
16
|
if (!el) return
|
|
17
17
|
scaler = createScaler(el, options)
|
|
18
18
|
const s = scaler.start()
|
|
19
19
|
scale.value = s
|
|
20
|
-
inverseScale.value =
|
|
20
|
+
inverseScale.value = s > 0 ? 1 / s : 1
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// 图片加载等导致的重算不经过 window.resize,跟页面缩放事件走
|
|
23
|
+
onScale = (event) => {
|
|
24
|
+
if (event.detail?.el !== el) return
|
|
25
|
+
scale.value = event.detail.scale
|
|
26
|
+
inverseScale.value = event.detail.inverseScale
|
|
25
27
|
}
|
|
26
|
-
window.addEventListener(
|
|
28
|
+
window.addEventListener(SCALE_CHANGE_EVENT, onScale)
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function teardown() {
|
|
30
|
-
if (
|
|
31
|
-
window.removeEventListener(
|
|
32
|
-
|
|
32
|
+
if (onScale) {
|
|
33
|
+
window.removeEventListener(SCALE_CHANGE_EVENT, onScale)
|
|
34
|
+
onScale = null
|
|
33
35
|
}
|
|
34
36
|
scaler?.stop()
|
|
35
37
|
scaler = null
|
|
@@ -60,7 +62,7 @@ export function useFitScreen(targetRef, options = {}) {
|
|
|
60
62
|
if (!el || !scaler) return
|
|
61
63
|
const s = scaler.refresh()
|
|
62
64
|
scale.value = s
|
|
63
|
-
inverseScale.value =
|
|
65
|
+
inverseScale.value = s > 0 ? 1 / s : 1
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
return { scale, inverseScale, refresh }
|