one-screen-scale 1.1.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/README.md +323 -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 +91 -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
package/src/core.js
CHANGED
|
@@ -1,160 +1,317 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 一屏自适应缩放 — 核心算法(框架无关)
|
|
3
|
-
*
|
|
4
|
-
* 两种模式:
|
|
5
|
-
* 'contain' 一屏展示,内容不溢出,用 min(scaleX, scaleY)
|
|
6
|
-
* 'fixed-width' 固定视觉宽度,内容可滚动,反向补高
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 一屏自适应缩放 — 核心算法(框架无关)
|
|
3
|
+
*
|
|
4
|
+
* 两种模式:
|
|
5
|
+
* 'contain' 一屏展示,内容不溢出,用 min(scaleX, scaleY)
|
|
6
|
+
* 'fixed-width' 固定视觉宽度,内容可滚动,反向补高
|
|
7
|
+
*
|
|
8
|
+
* 配套前提:H5 使用 postcss-px-to-viewport(或同类)做移动端适配。
|
|
9
|
+
* createScaler / 指令默认会注入基础宽高样式,保证测量与缩放生效。
|
|
10
|
+
*
|
|
11
|
+
* 图片等异步内容会改变 offsetHeight:默认用 ResizeObserver / 图片 load
|
|
12
|
+
* 在尺寸稳定后再 refresh,避免首屏偶发「量矮了 → scale 偏大」。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { applyBaseStyles, removeBaseStyles } from './baseStyles.js'
|
|
16
|
+
|
|
17
|
+
// ---------- helpers ----------
|
|
18
|
+
|
|
19
|
+
function resolveEl(el) {
|
|
20
|
+
if (!el) return null
|
|
21
|
+
if (typeof el === 'string') return document.querySelector(el)
|
|
22
|
+
if (el instanceof HTMLElement) return el
|
|
23
|
+
// Vue ref / React ref
|
|
24
|
+
if (el.value instanceof HTMLElement) return el.value
|
|
25
|
+
if (el.current instanceof HTMLElement) return el.current
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------- scale calculation ----------
|
|
30
|
+
|
|
31
|
+
export function calculateScale({
|
|
32
|
+
sourceWidth,
|
|
33
|
+
sourceHeight,
|
|
34
|
+
targetWidth = (typeof window !== 'undefined' ? window.innerWidth : 1920),
|
|
35
|
+
targetHeight = (typeof window !== 'undefined' ? window.innerHeight : 1080),
|
|
36
|
+
mode = 'contain',
|
|
37
|
+
visualWidth = 450, // fixed-width 模式下的目标视觉宽度
|
|
38
|
+
}) {
|
|
39
|
+
if (!sourceWidth || !sourceHeight || sourceWidth <= 0 || sourceHeight <= 0) return 1
|
|
40
|
+
|
|
41
|
+
let scale
|
|
42
|
+
|
|
43
|
+
if (mode === 'fixed-width') {
|
|
44
|
+
// 只用视口宽:视口比目标宽才缩小,否则 1:1(保持清晰)
|
|
45
|
+
scale = targetWidth > visualWidth ? visualWidth / targetWidth : 1
|
|
46
|
+
} else {
|
|
47
|
+
const sx = targetWidth / sourceWidth
|
|
48
|
+
const sy = targetHeight / sourceHeight
|
|
49
|
+
scale = Math.min(sx, sy)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return scale
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------- apply / read ----------
|
|
56
|
+
|
|
57
|
+
/** 页面 scale 变化时抛出,供 ECharts.resize / 业务监听 */
|
|
58
|
+
export const SCALE_CHANGE_EVENT = 'one-screen-scale-change'
|
|
59
|
+
|
|
60
|
+
export function applyScale(el, scale, origin = 'top center') {
|
|
61
|
+
const target = resolveEl(el)
|
|
62
|
+
if (!target) return
|
|
63
|
+
target.style.transform = `scale(${scale})`
|
|
64
|
+
target.style.transformOrigin = origin
|
|
65
|
+
// 供业务 / v-compensate 做反缩放(继承到子树)
|
|
66
|
+
const inv = scale > 0 ? 1 / scale : 1
|
|
67
|
+
target.style.setProperty('--one-screen-scale', String(scale))
|
|
68
|
+
target.style.setProperty('--one-screen-inverse-scale', String(inv))
|
|
69
|
+
if (typeof window !== 'undefined') {
|
|
70
|
+
window.dispatchEvent(
|
|
71
|
+
new CustomEvent(SCALE_CHANGE_EVENT, { detail: { scale, inverseScale: inv, el: target } })
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getPageScale(el) {
|
|
77
|
+
const target = resolveEl(el)
|
|
78
|
+
if (!target) return 1
|
|
79
|
+
|
|
80
|
+
const style = window.getComputedStyle(target)
|
|
81
|
+
const t = style.transform
|
|
82
|
+
if (!t || t === 'none') return 1
|
|
83
|
+
|
|
84
|
+
// matrix(a, b, c, d, e, f) — 2d
|
|
85
|
+
const m2 = t.match(/matrix\(([^,]+),\s*[^,]+,\s*[^,]+,\s*([^,]+)/)
|
|
86
|
+
if (m2) return (parseFloat(m2[1]) + parseFloat(m2[2])) / 2
|
|
87
|
+
|
|
88
|
+
// matrix3d — 3d
|
|
89
|
+
const m3 = t.match(/matrix3d\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\)/)
|
|
90
|
+
if (m3) return (parseFloat(m3[1]) + parseFloat(m3[2])) / 2
|
|
91
|
+
|
|
92
|
+
// scale(x, y) or scale(s)
|
|
93
|
+
const sm = t.match(/scale\(([^)]+)\)/)
|
|
94
|
+
if (sm) {
|
|
95
|
+
const vals = sm[1].split(',').map(v => parseFloat(v.trim()))
|
|
96
|
+
return vals.length >= 2 ? (vals[0] + vals[1]) / 2 : vals[0]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return 1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getInverseScale(el = '.share-page') {
|
|
103
|
+
const s = getPageScale(el)
|
|
104
|
+
return s > 0 ? 1 / s : 1
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------- scaler instance ----------
|
|
108
|
+
|
|
109
|
+
export function createScaler(el, options = {}) {
|
|
110
|
+
const target = resolveEl(el)
|
|
111
|
+
if (!target) throw new Error('[one-screen-scale] element not found')
|
|
112
|
+
|
|
113
|
+
const {
|
|
114
|
+
mode = 'contain',
|
|
115
|
+
visualWidth = 450,
|
|
116
|
+
origin = 'top center',
|
|
117
|
+
autoResize = true,
|
|
118
|
+
/** 是否自动注入模式所需的宽高基础样式,默认 true */
|
|
119
|
+
injectBaseStyles = true,
|
|
120
|
+
/**
|
|
121
|
+
* 内容尺寸变化时自动重算(图片 load、字体、DOM 撑高)。
|
|
122
|
+
* 解决刷新偶发「图未加载完就测量 → scale 偏大」。
|
|
123
|
+
*/
|
|
124
|
+
observeContent = true,
|
|
125
|
+
} = options
|
|
126
|
+
|
|
127
|
+
let currentScale = 1
|
|
128
|
+
let resizeHandler = null
|
|
129
|
+
let loadHandler = null
|
|
130
|
+
let windowLoadHandler = null
|
|
131
|
+
let rafId = 0
|
|
132
|
+
let unlockRaf = 0
|
|
133
|
+
let ro = null
|
|
134
|
+
let refreshing = false
|
|
135
|
+
let lastMeasured = { w: 0, h: 0 }
|
|
136
|
+
// start / stop 各加一,用来丢掉停掉之后才回来的字体、帧回调
|
|
137
|
+
let session = 0
|
|
138
|
+
|
|
139
|
+
// fixed-width 会改 inline width/height,stop 时还原
|
|
140
|
+
const savedInline = {
|
|
141
|
+
width: target.style.width,
|
|
142
|
+
height: target.style.height,
|
|
143
|
+
transform: target.style.transform,
|
|
144
|
+
transformOrigin: target.style.transformOrigin,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function compute() {
|
|
148
|
+
const sw = target.offsetWidth
|
|
149
|
+
const sh = target.offsetHeight
|
|
150
|
+
const tw = document.documentElement.clientWidth || window.innerWidth
|
|
151
|
+
const th = window.innerHeight
|
|
152
|
+
|
|
153
|
+
lastMeasured = { w: sw, h: sh }
|
|
154
|
+
|
|
155
|
+
return calculateScale({
|
|
156
|
+
sourceWidth: sw,
|
|
157
|
+
sourceHeight: sh,
|
|
158
|
+
targetWidth: tw,
|
|
159
|
+
targetHeight: th,
|
|
160
|
+
mode,
|
|
161
|
+
visualWidth,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function refresh() {
|
|
166
|
+
refreshing = true
|
|
167
|
+
const next = compute()
|
|
168
|
+
currentScale = next
|
|
169
|
+
// 始终写入,保证首次 scale=1 时也带上 transform-origin
|
|
170
|
+
applyScale(target, next, origin)
|
|
171
|
+
|
|
172
|
+
// fixed-width 模式:反向补偿高度(视觉宽固定,布局宽仍用视口宽再缩小)
|
|
173
|
+
if (mode === 'fixed-width' && next > 0) {
|
|
174
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth
|
|
175
|
+
const nextW = `${viewportWidth}px`
|
|
176
|
+
const nextH = `${Math.round(window.innerHeight / next)}px`
|
|
177
|
+
// 值不变不写,避免 ResizeObserver 死循环
|
|
178
|
+
if (target.style.width !== nextW) target.style.width = nextW
|
|
179
|
+
if (target.style.height !== nextH) target.style.height = nextH
|
|
180
|
+
}
|
|
181
|
+
// 等布局提交后再放开,避免 RO 回调里立刻重入
|
|
182
|
+
const token = session
|
|
183
|
+
cancelAnimationFrame(unlockRaf)
|
|
184
|
+
unlockRaf = requestAnimationFrame(() => {
|
|
185
|
+
if (token !== session) return
|
|
186
|
+
refreshing = false
|
|
187
|
+
})
|
|
188
|
+
return currentScale
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function scheduleRefresh() {
|
|
192
|
+
if (refreshing) return
|
|
193
|
+
cancelAnimationFrame(rafId)
|
|
194
|
+
const token = session
|
|
195
|
+
rafId = requestAnimationFrame(() => {
|
|
196
|
+
if (token !== session) return
|
|
197
|
+
// 尺寸没变就跳过(fixed-width 改 height 触发的 RO)
|
|
198
|
+
const w = target.offsetWidth
|
|
199
|
+
const h = target.offsetHeight
|
|
200
|
+
if (w === lastMeasured.w && h === lastMeasured.h && target.style.transform) {
|
|
201
|
+
// 视口变化仍需算:由 window.resize 路径直接 refresh
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
refresh()
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function onWindowResize() {
|
|
209
|
+
refresh()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function start() {
|
|
213
|
+
session += 1
|
|
214
|
+
const token = session
|
|
215
|
+
// 复用同一实例 start 时,上一次 stop 之后留下的锁要清掉
|
|
216
|
+
refreshing = false
|
|
217
|
+
if (injectBaseStyles) {
|
|
218
|
+
applyBaseStyles(target, mode)
|
|
219
|
+
}
|
|
220
|
+
// 注入样式后再量尺寸
|
|
221
|
+
refresh()
|
|
222
|
+
|
|
223
|
+
if (autoResize) {
|
|
224
|
+
resizeHandler = onWindowResize
|
|
225
|
+
window.addEventListener('resize', resizeHandler)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (observeContent && typeof window !== 'undefined') {
|
|
229
|
+
// 1) 子树布局尺寸变化(图片解码后撑高最常见)
|
|
230
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
231
|
+
ro = new ResizeObserver(() => scheduleRefresh())
|
|
232
|
+
ro.observe(target)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 2) 捕获阶段听内部 img/video 的 load(含后插入的节点)
|
|
236
|
+
loadHandler = (e) => {
|
|
237
|
+
const t = e.target
|
|
238
|
+
if (t && (t.tagName === 'IMG' || t.tagName === 'VIDEO' || t.tagName === 'IFRAME')) {
|
|
239
|
+
scheduleRefresh()
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
target.addEventListener('load', loadHandler, true)
|
|
243
|
+
target.addEventListener('error', loadHandler, true)
|
|
244
|
+
|
|
245
|
+
// 3) 字体就绪。stop 之后 session 变了,这次回调直接丢掉
|
|
246
|
+
if (document.fonts && document.fonts.ready) {
|
|
247
|
+
document.fonts.ready
|
|
248
|
+
.then(() => {
|
|
249
|
+
if (token !== session) return
|
|
250
|
+
scheduleRefresh()
|
|
251
|
+
})
|
|
252
|
+
.catch(() => {})
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 4) 整页资源 load(缓存命中时可能已 complete)
|
|
256
|
+
if (document.readyState === 'complete') {
|
|
257
|
+
scheduleRefresh()
|
|
258
|
+
} else {
|
|
259
|
+
windowLoadHandler = () => scheduleRefresh()
|
|
260
|
+
window.addEventListener('load', windowLoadHandler, { once: true })
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 5) 再等两帧,覆盖首屏同步布局抖动
|
|
264
|
+
requestAnimationFrame(() => {
|
|
265
|
+
requestAnimationFrame(() => scheduleRefresh())
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return currentScale
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function stop() {
|
|
273
|
+
session += 1
|
|
274
|
+
if (resizeHandler) {
|
|
275
|
+
window.removeEventListener('resize', resizeHandler)
|
|
276
|
+
resizeHandler = null
|
|
277
|
+
}
|
|
278
|
+
if (loadHandler) {
|
|
279
|
+
target.removeEventListener('load', loadHandler, true)
|
|
280
|
+
target.removeEventListener('error', loadHandler, true)
|
|
281
|
+
loadHandler = null
|
|
282
|
+
}
|
|
283
|
+
if (windowLoadHandler) {
|
|
284
|
+
window.removeEventListener('load', windowLoadHandler)
|
|
285
|
+
windowLoadHandler = null
|
|
286
|
+
}
|
|
287
|
+
if (ro) {
|
|
288
|
+
ro.disconnect()
|
|
289
|
+
ro = null
|
|
290
|
+
}
|
|
291
|
+
cancelAnimationFrame(rafId)
|
|
292
|
+
cancelAnimationFrame(unlockRaf)
|
|
293
|
+
rafId = 0
|
|
294
|
+
unlockRaf = 0
|
|
295
|
+
|
|
296
|
+
if (injectBaseStyles) {
|
|
297
|
+
removeBaseStyles(target)
|
|
298
|
+
}
|
|
299
|
+
target.style.width = savedInline.width
|
|
300
|
+
target.style.height = savedInline.height
|
|
301
|
+
target.style.transform = savedInline.transform
|
|
302
|
+
target.style.transformOrigin = savedInline.transformOrigin
|
|
303
|
+
target.style.removeProperty('--one-screen-scale')
|
|
304
|
+
target.style.removeProperty('--one-screen-inverse-scale')
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return { start, stop, refresh, getScale: () => currentScale }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export {
|
|
311
|
+
applyBaseStyles,
|
|
312
|
+
removeBaseStyles,
|
|
313
|
+
applyCompensateStyles,
|
|
314
|
+
removeCompensateStyles,
|
|
315
|
+
ensureBaseStyleSheet,
|
|
316
|
+
BASE_CLASS,
|
|
317
|
+
} from './baseStyles.js'
|
package/src/directives.js
CHANGED
|
@@ -1,34 +1,144 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vue 3 指令
|
|
3
|
-
* v-fit-screen
|
|
4
|
-
* v-compensate
|
|
5
|
-
*/
|
|
6
|
-
import { createScaler } from './core.js'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Vue 3 指令
|
|
3
|
+
* v-fit-screen 页面级缩放
|
|
4
|
+
* v-compensate 组件级反缩放补偿(ECharts / Element Plus 等)
|
|
5
|
+
*/
|
|
6
|
+
import { createScaler } from './core.js'
|
|
7
|
+
import { createCompensate } from './compensate.js'
|
|
8
|
+
|
|
9
|
+
function readOptions(binding) {
|
|
10
|
+
return binding.value && typeof binding.value === 'object' ? binding.value : {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function mountScaler(el, binding) {
|
|
14
|
+
const options = readOptions(binding)
|
|
15
|
+
const scaler = createScaler(el, options)
|
|
16
|
+
scaler.start()
|
|
17
|
+
el.__fitScreenScaler = scaler
|
|
18
|
+
el.__fitScreenOptionsKey = JSON.stringify({
|
|
19
|
+
mode: options.mode || 'contain',
|
|
20
|
+
visualWidth: options.visualWidth,
|
|
21
|
+
origin: options.origin,
|
|
22
|
+
autoResize: options.autoResize,
|
|
23
|
+
injectBaseStyles: options.injectBaseStyles,
|
|
24
|
+
observeContent: options.observeContent,
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function unmountScaler(el) {
|
|
29
|
+
const scaler = el.__fitScreenScaler
|
|
30
|
+
if (scaler) {
|
|
31
|
+
scaler.stop()
|
|
32
|
+
delete el.__fitScreenScaler
|
|
33
|
+
delete el.__fitScreenOptionsKey
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---- v-fit-screen ----
|
|
38
|
+
|
|
39
|
+
export const vFitScreen = {
|
|
40
|
+
mounted(el, binding) {
|
|
41
|
+
mountScaler(el, binding)
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
updated(el, binding) {
|
|
45
|
+
const options = readOptions(binding)
|
|
46
|
+
const key = JSON.stringify({
|
|
47
|
+
mode: options.mode || 'contain',
|
|
48
|
+
visualWidth: options.visualWidth,
|
|
49
|
+
origin: options.origin,
|
|
50
|
+
autoResize: options.autoResize,
|
|
51
|
+
injectBaseStyles: options.injectBaseStyles,
|
|
52
|
+
observeContent: options.observeContent,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (el.__fitScreenOptionsKey !== key) {
|
|
56
|
+
unmountScaler(el)
|
|
57
|
+
mountScaler(el, binding)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
el.__fitScreenScaler?.refresh()
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
unmounted(el) {
|
|
65
|
+
unmountScaler(el)
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readCompensateOptions(binding) {
|
|
70
|
+
const value = binding?.value
|
|
71
|
+
const modifiers = binding?.modifiers || {}
|
|
72
|
+
const options = value && typeof value === 'object' ? { ...value } : {}
|
|
73
|
+
// v-compensate.resize-canvas / .resizeCanvas / 旧名 .echarts
|
|
74
|
+
if (
|
|
75
|
+
modifiers.resizeCanvas ||
|
|
76
|
+
modifiers['resize-canvas'] ||
|
|
77
|
+
modifiers.echarts ||
|
|
78
|
+
value === 'resize-canvas' ||
|
|
79
|
+
value === 'resizeCanvas' ||
|
|
80
|
+
value === 'echarts'
|
|
81
|
+
) {
|
|
82
|
+
options.resizeCanvas = options.resizeCanvas || true
|
|
83
|
+
}
|
|
84
|
+
return options
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function compensateOptionsKey(options) {
|
|
88
|
+
return JSON.stringify({
|
|
89
|
+
resizeCanvas: Boolean(options.resizeCanvas || options.echarts || options.echartsLib),
|
|
90
|
+
hasLib: Boolean(
|
|
91
|
+
(options.echarts && typeof options.echarts === 'object') || options.echartsLib
|
|
92
|
+
),
|
|
93
|
+
hasOnRefresh: typeof options.onRefresh === 'function',
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* v-compensate — 挂在「被页面 scale 搞坏」的第三方组件外包一层。
|
|
99
|
+
*
|
|
100
|
+
* 原理与业务 WinRateChart 手写一致(JS 行内,非纯 CSS):
|
|
101
|
+
* transform: scale(inverse); width/height: 100% * pageScale; origin: top left
|
|
102
|
+
*
|
|
103
|
+
* 用法:
|
|
104
|
+
* <div class="slot"><div v-compensate><Whatever /></div></div>
|
|
105
|
+
* <div class="slot"><div v-compensate.resize-canvas><Chart /></div></div>
|
|
106
|
+
*
|
|
107
|
+
* `.resize-canvas` / `{ resizeCanvas: true }`:补偿后通知子树 canvas 重绘
|
|
108
|
+
* (探测 ECharts / Chart.js,并派发 one-screen-scale-canvas-resize)。
|
|
109
|
+
*/
|
|
110
|
+
function mountCompensate(el) {
|
|
111
|
+
const api = createCompensate(el, () => el.__oneScreenCompensateOptions || {})
|
|
112
|
+
el.__oneScreenCompensateApi = api
|
|
113
|
+
el.__oneScreenCompensateKey = compensateOptionsKey(el.__oneScreenCompensateOptions || {})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const vCompensate = {
|
|
117
|
+
mounted(el, binding) {
|
|
118
|
+
el.__oneScreenCompensateOptions = readCompensateOptions(binding)
|
|
119
|
+
mountCompensate(el)
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
updated(el, binding) {
|
|
123
|
+
const options = readCompensateOptions(binding)
|
|
124
|
+
const key = compensateOptionsKey(options)
|
|
125
|
+
el.__oneScreenCompensateOptions = options
|
|
126
|
+
// resizeCanvas / 图表库变了才重建监听;onRefresh 每次 refresh 现读
|
|
127
|
+
if (el.__oneScreenCompensateKey !== key) {
|
|
128
|
+
el.__oneScreenCompensateApi?.stop()
|
|
129
|
+
mountCompensate(el)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
el.__oneScreenCompensateApi?.refresh()
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
unmounted(el) {
|
|
136
|
+
el.__oneScreenCompensateApi?.stop()
|
|
137
|
+
delete el.__oneScreenCompensateApi
|
|
138
|
+
delete el.__oneScreenCompensateKey
|
|
139
|
+
delete el.__oneScreenCompensateOptions
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 别名,与早期注释里的命名一致 */
|
|
144
|
+
export const vFitCompensate = vCompensate
|