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 CHANGED
@@ -1,186 +1,356 @@
1
- # one-screen-scale
2
-
3
- 基于 CSS `transform: scale()` 的一屏自适应方案。框架无关核心 + Vue 3 / React 开箱即用。
4
-
5
- ## 为什么用这个
6
-
7
- - 🎯 **真正的"一屏"** — 内容不溢出、不滚动(contain 模式)
8
- - 🧩 **零依赖核心** — 纯 JS,不绑定任何框架
9
- - ⚡ **指令 / Hook 开箱即用** — Vue 3 和 React 都支持
10
- - 🖥️ **Canvas 友好** — 通过反缩放补偿,ECharts 等 canvas 内容保持清晰
11
-
12
- ## 主要面向场景
13
-
14
- - 使用了pxToViewport autoprefixer 或者类似的移动端自适应插件的h5网页
15
- - 可以使用该插件在任何设备实现一屏自适应,如果是固定高度(一屏)则使用contain模式,如果是滚动高度则使用fixed-width模式
16
- - 同时在pc端可以正常自适应显示同一个页面,从而无需再开发pc端对应页面
17
-
18
- ## 安装
19
-
20
- ```bash
21
- npm install one-screen-scale
22
- ```
23
-
24
- ## 快速开始
25
-
26
- ### Vue 3 — 指令(最简)
27
-
28
- ```vue
29
- <template>
30
- <div v-fit-screen class="page">
31
- <h1>活动标题</h1>
32
- <p>页面内容...</p>
33
- </div>
34
- </template>
35
-
36
- <script setup>
37
- import { vFitScreen } from 'one-screen-scale'
38
- </script>
39
- ```
40
-
41
- ### Vue 3 — Composable(需要 scale 值,如 ECharts 补偿)
42
-
43
- ```vue
44
- <template>
45
- <div ref="pageRef" class="page">
46
- <div :style="{ transform: `scale(${inverseScale})` }">
47
- <ECharts />
48
- </div>
49
- </div>
50
- </template>
51
-
52
- <script setup>
53
- import { ref } from 'vue'
54
- import { useFitScreen } from 'one-screen-scale'
55
-
56
- const pageRef = ref(null)
57
- const { scale, inverseScale } = useFitScreen(pageRef)
58
- // inverseScale 可用于子元素反缩放补偿
59
- </script>
60
- ```
61
-
62
- ### React
63
-
64
- ```jsx
65
- import { useRef } from 'react'
66
- import { useFitScreen } from 'one-screen-scale/react'
67
-
68
- function App() {
69
- const pageRef = useRef(null)
70
- const { scale, inverseScale } = useFitScreen(pageRef)
71
-
72
- return (
73
- <div ref={pageRef} className="page">
74
- <h1>活动标题</h1>
75
- </div>
76
- )
77
- }
78
- ```
79
-
80
- ### 纯 JS / jQuery
81
-
82
- ```js
83
- import { createScaler } from 'one-screen-scale'
84
-
85
- const scaler = createScaler(document.querySelector('.page'), {
86
- mode: 'contain'
87
- })
88
- scaler.start()
89
-
90
- // 销毁
91
- scaler.stop()
92
- ```
93
-
94
- ## 两种模式
95
-
96
- | 模式 | 算法 | 效果 | 场景 |
97
- |------|------|------|------|
98
- | `contain`(默认) | `min(scaleX, scaleY)` | 内容完整可见,不溢出 | 活动页、落地页 |
99
- | `fixed-width` | `visualWidth / innerWidth`(视口 ≤ visualWidth 时为 1) | 固定手机观感宽度,可滚动 | 规则页、长表单 |
100
-
101
- ```js
102
- // contain(默认)
103
- createScaler(el)
104
-
105
- // fixed-width
106
- createScaler(el, { mode: 'fixed-width', visualWidth: 450 })
107
- ```
108
-
109
- ## API
110
-
111
- ### 核心函数(框架无关)
112
-
113
- ```ts
114
- calculateScale(options: ScaleOptions): number
115
-
116
- applyScale(el: Element | string, scale: number, origin?: string): void
117
-
118
- getPageScale(el?: Element | string): number // 默认查 .share-page
119
- getInverseScale(el?: Element | string): number // 1 / getPageScale()
120
-
121
- createScaler(el: Element | string, options?: ScalerOptions): ScalerInstance
122
- ```
123
-
124
- ### ScalerInstance
125
-
126
- ```ts
127
- {
128
- start(): number // 启动缩放 + 监听 resize,返回当前 scale
129
- stop(): void // 停止监听
130
- refresh(): number // 手动重算
131
- getScale(): number // 获取当前 scale
132
- }
133
- ```
134
-
135
- ### ScalerOptions
136
-
137
- | 参数 | 类型 | 默认值 | 说明 |
138
- |------|------|--------|------|
139
- | `mode` | `'contain' \| 'fixed-width'` | `'contain'` | 缩放模式 |
140
- | `visualWidth` | `number` | `450` | fixed-width 模式目标视觉宽度 |
141
- | `origin` | `string` | `'top center'` | transform-origin |
142
- | `autoResize` | `boolean` | `true` | 自动监听窗口 resize |
143
-
144
- ### Vue
145
-
146
- ```ts
147
- // 指令
148
- vFitScreen: Directive<HTMLElement, ScalerOptions>
149
-
150
- // Composable
151
- useFitScreen(ref: Ref, options?: ScalerOptions): {
152
- scale: Ref<number>
153
- inverseScale: Ref<number>
154
- refresh: () => void
155
- }
156
- ```
157
-
158
- ### React
159
-
160
- ```ts
161
- import { useFitScreen } from 'one-screen-scale/react'
162
-
163
- useFitScreen(ref: RefObject, options?: ScalerOptions): {
164
- scale: number
165
- inverseScale: number
166
- refresh: () => void
167
- }
168
- ```
169
-
170
- ## Canvas / ECharts 模糊问题
171
-
172
- CSS `transform: scale()` 缩放的是位图,canvas 内容会变糊。解决办法:对 canvas 容器做反缩放补偿。
173
-
174
- ```js
175
- // Vue composable 示例
176
- const { inverseScale } = useFitScreen(pageRef)
177
-
178
- // 在包裹层上应用反缩放
179
- // <div :style="{ transform: `scale(${inverseScale})`, transformOrigin: 'top left' }">
180
- ```
181
-
182
- 反缩放后 canvas 以原生分辨率渲染,保持清晰。详情见 [Canvas 缩放补偿指南](https://github.com/nicepkg/one-screen-scale)。
183
-
184
- ## License
185
-
186
- MIT
1
+ # one-screen-scale
2
+
3
+ 基于 CSS `transform: scale()` 的一屏自适应方案。框架无关核心 + Vue 3 / React 开箱即用。
4
+
5
+ ## 为什么用这个
6
+
7
+ - 🎯 **真正的"一屏"** — 内容不溢出、不滚动(contain 模式)
8
+ - 🧩 **零依赖核心** — 纯 JS,不绑定任何框架
9
+ - ⚡ **指令 / Hook 开箱即用** — Vue 3 和 React 都支持;挂载时自动注入基础宽高样式
10
+ - 🖥️ **Canvas 友好** — 通过反缩放补偿,ECharts 等 canvas 内容保持清晰
11
+
12
+ ## 主要面向场景
13
+
14
+ 面向已经用 **postcss-px-to-viewport**(或同类方案)做移动端适配的 H5 页面:
15
+
16
+ | 模式 | 用途 | 效果 |
17
+ |------|------|------|
18
+ | `contain`(默认) | 活动页 / 一屏落地页 | 任意设备完整展示一屏;高屏左右留白;极小视口仍能看到完整页;**PC 与手机看到的是同一张 H5** |
19
+ | `fixed-width` | 规则页 / 长表单 | 固定视觉宽度,内容可纵向滚动 |
20
+
21
+ 不必再单独开发一套 PC 页。
22
+
23
+ ## 页面怎么配合
24
+
25
+ 指令会**自动注入**下列基础样式(可用 `injectBaseStyles: false` 关闭):
26
+
27
+ **contain — 一屏**
28
+
29
+ ```css
30
+ width: 100vw;
31
+ min-height: 100vh;
32
+ min-height: 100dvh;
33
+ ```
34
+
35
+ 算法用元素的 `offsetWidth / offsetHeight` 和视口比:`scale = min(视口宽/内容宽, 视口高/内容高)`。
36
+ 所以 **内容高度必须能被正确量出来**,PC 上(px→vw 后)通常会高于视口,scale 才会小于 1。
37
+
38
+ ### ⚠️ contain 使用禁忌(否则 PC 看起来像「没缩放」)
39
+
40
+ 1. **禁止用 `height: 100vh` / `100dvh` 锁死根节点高度**
41
+ 只能用插件注入的 **`min-height`**。写成 `height: 100vh` 后,`offsetHeight ≈ 视口高` → `scaleY ≈ 1` → 整页几乎不缩。
42
+
43
+ 2. **禁止把撑高页面的大图 / 主视觉设为 `position: absolute`(或脱离文档流)**
44
+ 绝对定位不参与父级高度计算。常见错误:棋盘/海报 `position: absolute` + 外层 `flex: 1; min-height: 0` 去「填满一屏」→ 根节点高度仍被锁在视口上。
45
+ **正确做法:大图留在文档流里**(`position: relative` / 默认 static,`width: 100%; height: auto`),用图片自身比例撑开整页高度。
46
+
47
+ 3. **避免「整页 flex 填视口、子项 `flex:1; min-height:0`」当唯一高度来源**
48
+ 那会把布局收成刚好一屏,同样量不出「内容高于视口」。一屏裁切用 `overflow: hidden` 即可,高度仍交给内容。
49
+
50
+ **对照**
51
+
52
+ | 写法 | 结果 |
53
+ |------|------|
54
+ | `min-height: 100vh` + 大图在文档流 | ✅ PC/手机构图一致,contain 正常缩小 |
55
+ | `height: 100vh` 锁死 | ❌ PC scale≈1 |
56
+ | 大图 `absolute` + flex 填满视口 | ❌ 量不到真实内容高,PC 不缩 |
57
+
58
+ 图片异步加载导致偶发量矮:插件默认 `observeContent: true`,会在尺寸变化后自动 `refresh`。
59
+
60
+ **fixed-width — 长滚动**
61
+
62
+ #### 什么时候用
63
+
64
+ 规则页、协议、长表单这种内容比一屏高、需要上下滚动的页面。手机上看是正常宽度;PC 上视口更宽时,只把页面缩到一条手机宽(默认视觉宽 450),两边留白,不要把字拉得很大。
65
+
66
+ `contain` 不适合这种页:它要整页塞进一屏,长文会被缩得很小。
67
+
68
+ #### 插件做了什么,页面还要做什么
69
+
70
+ 指令做两件事:
71
+
72
+ 1. 按视口把根节点缩小,并把根节点的宽高写进行内样式。
73
+ 2. 滚动条会跟着缩小。Chrome 里只把滚动条改窄、不写滑块颜色,滑块会直接消失。所以指令会给根节点和里面的滚动区域一起补上宽度和滑块颜色,看起来大约 6px。**滚动条样式不用再写。**
74
+
75
+ 指令不知道标题要不要钉在上面、正文从哪里开始滚。这两件事是页面布局,必须自己写。只加 `v-fit-screen="{ mode: 'fixed-width' }"`、不写下边的结构,页面不会滚。
76
+
77
+ #### 怎么写
78
+
79
+ 根节点做成纵向弹性盒,自己不滚动。标题高度固定。剩下的高度全部交给滚动区,由滚动区 `overflow-y: auto` 滚动。
80
+
81
+ ```vue
82
+ <template>
83
+ <div v-fit-screen="{ mode: 'fixed-width' }" class="rules-page">
84
+ <header class="rules-header">标题</header>
85
+ <div class="rules-scroll">
86
+ <main>很长的正文...</main>
87
+ </div>
88
+ </div>
89
+ </template>
90
+
91
+ <style scoped>
92
+ .rules-page {
93
+ display: flex; /* 纵向排列标题和正文 */
94
+ flex-direction: column;
95
+ overflow: hidden; /* 整页不滚,避免和内部滚动条抢 */
96
+ }
97
+
98
+ .rules-header {
99
+ flex-shrink: 0; /* 标题保持自己的高度,不被正文挤扁 */
100
+ }
101
+
102
+ .rules-scroll {
103
+ flex: 1; /* 拿走标题下面的全部剩余高度 */
104
+ min-height: 0; /* 允许它比内容矮,否则 flex 子项不会出现滚动 */
105
+ overflow-y: auto; /* 正文超出这块区域时,在这里面滚 */
106
+ overflow-x: hidden;
107
+ }
108
+ </style>
109
+ ```
110
+
111
+ 背景色、圆角按设计另写。三行滚动布局(`flex: 1`、`min-height: 0`、`overflow-y: auto`)不能省。
112
+
113
+ ## 安装
114
+
115
+ ```bash
116
+ npm install one-screen-scale
117
+ ```
118
+
119
+ ## 快速开始
120
+
121
+ ### Vue 3 — 指令(最简)
122
+
123
+ ```vue
124
+ <template>
125
+ <!-- 一屏:自动注入 min-height:100vh + width:100vw -->
126
+ <div v-fit-screen class="page">
127
+ <h1>活动标题</h1>
128
+ <p>页面内容...</p>
129
+ </div>
130
+ </template>
131
+
132
+ <script setup>
133
+ import { vFitScreen } from 'one-screen-scale'
134
+ </script>
135
+ ```
136
+
137
+ 长滚动的完整结构见上面「fixed-width — 长滚动」:根节点 `display: flex; flex-direction: column; overflow: hidden`,滚动区 `flex: 1; min-height: 0; overflow-y: auto`。只写指令、不写这几行,页面不会滚。
138
+
139
+ ### Vue 3 — `v-compensate`(第三方组件反缩放)
140
+
141
+ 页面 `transform: scale` 后,ECharts 等常会尺寸偏小或量错。在槽位内再包一层即可(**JS 行内**写入):
142
+
143
+ `width/height: 100% * pageScale` + `transform: scale(inverse)`,scale 读祖先 `--one-screen-scale`。
144
+
145
+ ```vue
146
+ <template>
147
+ <div v-fit-screen class="page">
148
+ <div class="chart-slot">
149
+ <div v-compensate.resize-canvas class="chart-inner">
150
+ <div ref="chartEl" class="chart"></div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </template>
155
+
156
+ <script setup>
157
+ import * as echarts from 'echarts'
158
+ import { vFitScreen, vCompensate, setCompensateEcharts } from 'one-screen-scale'
159
+
160
+ // 可选:全局注入 echarts,供 resizeCanvas 自动探测
161
+ setCompensateEcharts(echarts)
162
+ </script>
163
+ ```
164
+
165
+ ```css
166
+ .chart-slot { width: 100%; height: 300px; overflow: hidden; } /* 设计稿槽位,需明确宽高 */
167
+ .chart-inner, .chart { width: 100%; height: 100%; }
168
+ ```
169
+
170
+ > **适用**:任意被页面 scale 搞小的区域,尤其 **canvas**——先反缩放,再用 `offsetWidth` 量盒(勿用 `getBoundingClientRect`)。
171
+ > **`{ resizeCanvas: true }` / `.resize-canvas`**:补偿后通知子树 canvas 重绘(探测 ECharts / Chart.js,并在每个 `<canvas>` 上派发 `one-screen-scale-canvas-resize`)。
172
+ > 只抵消页面 `v-fit-screen` 的 scale;**不处理**浏览器 Ctrl+/-。
173
+ > **不适用**:`el-slider` 等依赖指针坐标 / 易被 overflow 裁切的控件。
174
+
175
+ ### Vue 3 — Composable(需要拿到 scale 数值时)
176
+
177
+ ```vue
178
+ <script setup>
179
+ import { ref } from 'vue'
180
+ import { useFitScreen } from 'one-screen-scale'
181
+
182
+ const pageRef = ref(null)
183
+ const { scale, inverseScale } = useFitScreen(pageRef)
184
+ </script>
185
+ ```
186
+
187
+ ### React
188
+
189
+ ```jsx
190
+ import { useRef } from 'react'
191
+ import { useFitScreen, useCompensate } from 'one-screen-scale/react'
192
+
193
+ function App() {
194
+ const pageRef = useRef(null)
195
+ const chartInnerRef = useRef(null)
196
+ const { scale, inverseScale } = useFitScreen(pageRef)
197
+ // 需要通知子树 canvas 重绘时加 { resizeCanvas: true }
198
+ useCompensate(chartInnerRef, { resizeCanvas: true })
199
+
200
+ return (
201
+ <div ref={pageRef} className="page">
202
+ <div className="chart-slot" style={{ width: 350, height: 300 }}>
203
+ <div ref={chartInnerRef} style={{ width: '100%', height: '100%' }}>
204
+ <div id="chart" style={{ width: '100%', height: '100%' }} />
205
+ </div>
206
+ </div>
207
+ </div>
208
+ )
209
+ }
210
+ ```
211
+
212
+ ### 纯 JS / HTML
213
+
214
+ ```js
215
+ import { createScaler, createCompensate } from 'one-screen-scale'
216
+
217
+ const page = document.querySelector('.page')
218
+ const scaler = createScaler(page, { mode: 'contain' })
219
+ scaler.start()
220
+
221
+ // 第三方组件反缩放(对应 Vue v-compensate / React useCompensate)
222
+ const inner = document.querySelector('.chart-inner')
223
+ const compensate = createCompensate(inner, { resizeCanvas: true })
224
+
225
+ // 销毁
226
+ compensate.stop()
227
+ scaler.stop() // 移除注入的 class,并还原本库写过的 inline 样式
228
+ ```
229
+
230
+ ## 两种模式
231
+
232
+ | 模式 | 算法 | 效果 | 场景 |
233
+ |------|------|------|------|
234
+ | `contain`(默认) | `min(scaleX, scaleY)` | 内容完整可见,不溢出 | 活动页、落地页 |
235
+ | `fixed-width` | `visualWidth / innerWidth`(视口 ≤ visualWidth 时为 1) | 固定手机观感宽度,可滚动 | 规则页、长表单 |
236
+
237
+ ```js
238
+ // contain(默认)
239
+ createScaler(el)
240
+
241
+ // fixed-width
242
+ createScaler(el, { mode: 'fixed-width', visualWidth: 450 })
243
+
244
+ // 自己写根节点宽高时关闭注入
245
+ createScaler(el, { injectBaseStyles: false })
246
+ ```
247
+
248
+ ## API
249
+
250
+ ### 核心函数(框架无关)
251
+
252
+ ```ts
253
+ calculateScale(options: ScaleOptions): number
254
+
255
+ applyScale(el: Element | string, scale: number, origin?: string): void
256
+
257
+ getPageScale(el?: Element | string): number
258
+ getInverseScale(el?: Element | string): number
259
+
260
+ createScaler(el: Element | string, options?: ScalerOptions): ScalerInstance
261
+
262
+ ensureBaseStyleSheet(): HTMLStyleElement | null
263
+ applyBaseStyles(el: HTMLElement, mode?: 'contain' | 'fixed-width'): string | null
264
+ removeBaseStyles(el: HTMLElement): void
265
+ applyCompensateStyles(el: HTMLElement): string | null
266
+ removeCompensateStyles(el: HTMLElement): void
267
+
268
+ SCALE_CHANGE_EVENT // 'one-screen-scale-change',页面 scale 变化时 window 上触发
269
+
270
+ // 组件反缩放(仅抵消页面 scale)
271
+ resolvePageScale(fromEl?): number
272
+ applyCompensateTransform(el): { scale, inverseScale }
273
+ clearCompensateTransform(el): void
274
+ createCompensate(el, options?: CompensateOptions): { refresh, stop }
275
+ resizeCanvasUnder(el, echartsLib?): number
276
+ setCompensateEcharts(echarts): void // 可选,供 resizeCanvas 探测 ECharts
277
+ CANVAS_RESIZE_EVENT // 'one-screen-scale-canvas-resize'
278
+ ```
279
+
280
+ ### ScalerInstance
281
+
282
+ ```ts
283
+ {
284
+ start(): number // 注入基础样式 + 启动缩放 + 监听 resize
285
+ stop(): void // 停止监听,移除注入 class,还原 inline
286
+ refresh(): number // 手动重算
287
+ getScale(): number
288
+ }
289
+ ```
290
+
291
+ ### ScalerOptions
292
+
293
+ | 参数 | 类型 | 默认值 | 说明 |
294
+ |------|------|--------|------|
295
+ | `mode` | `'contain' \| 'fixed-width'` | `'contain'` | 缩放模式 |
296
+ | `visualWidth` | `number` | `450` | fixed-width 模式目标视觉宽度 |
297
+ | `origin` | `string` | `'top center'` | transform-origin |
298
+ | `autoResize` | `boolean` | `true` | 自动监听窗口 resize |
299
+ | `injectBaseStyles` | `boolean` | `true` | 是否自动注入模式所需宽高样式 |
300
+ | `observeContent` | `boolean` | `true` | 内容尺寸变化(如图片加载撑高)时自动重算,避免偶发 scale 偏大 |
301
+
302
+ ### Vue
303
+
304
+ ```ts
305
+ vFitScreen: Directive<HTMLElement, ScalerOptions>
306
+ vCompensate: Directive<HTMLElement> // 第三方反缩放
307
+ vFitCompensate: Directive<HTMLElement> // 同上别名
308
+
309
+ useFitScreen(ref: Ref, options?: ScalerOptions): {
310
+ scale: Ref<number>
311
+ inverseScale: Ref<number>
312
+ refresh: () => void
313
+ }
314
+ ```
315
+
316
+ ### React(`one-screen-scale/react`)
317
+
318
+ ```ts
319
+ useFitScreen(ref, options?: ScalerOptions): { scale, inverseScale, refresh }
320
+ useCompensate(ref, options?: CompensateOptions): { refresh } // 对应 v-compensate
321
+ ```
322
+
323
+ ### CompensateOptions
324
+
325
+ | 参数 | 类型 | 默认值 | 说明 |
326
+ |------|------|--------|------|
327
+ | `resizeCanvas` | `boolean` | — | **可选**。补偿后通知子树 canvas 重绘 |
328
+ | `onRefresh` | `(detail) => void` | — | 每次写入反缩放后回调 |
329
+ | `echarts` / `echartsLib` | — | — | 旧名,等价于开启 `resizeCanvas`;对象形式可传 echarts 模块 |
330
+
331
+ 也可 `setCompensateEcharts(echarts)` 全局注入,方便自动探测。
332
+
333
+ ## Canvas 模糊 / 偏小
334
+
335
+ CSS `transform: scale()` 会把 **所有 canvas** 一起缩小。先反缩放,再重绘:
336
+
337
+ | 环境 | 反缩放 | 通知 canvas 重绘 |
338
+ |------|--------|------------------|
339
+ | Vue | `v-compensate` | `v-compensate.resize-canvas` |
340
+ | React | `useCompensate(ref)` | `useCompensate(ref, { resizeCanvas: true })` |
341
+ | 纯 JS | `createCompensate(el)` | `createCompensate(el, { resizeCanvas: true })` |
342
+
343
+ `resizeCanvas` 会:探测已加载的 ECharts / Chart.js;并在每个 `<canvas>` 上派发事件,自研库可监听:
344
+
345
+ ```js
346
+ canvas.addEventListener('one-screen-scale-canvas-resize', (e) => {
347
+ const { width, height } = e.detail
348
+ myChart.resize(width, height)
349
+ })
350
+ ```
351
+
352
+ 手写等价:`width/height: 100% * pageScale` + `transform: scale(1/pageScale)`,量尺寸用 `offsetWidth`。
353
+
354
+ ## License
355
+
356
+ MIT
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var e=require("vue");function t(e){return e?"string"==typeof e?document.querySelector(e):e instanceof HTMLElement?e:e.value instanceof HTMLElement?e.value:e.current instanceof HTMLElement?e.current:null:null}function n({sourceWidth:e,sourceHeight:t,targetWidth:n=("undefined"!=typeof window?window.innerWidth:1920),targetHeight:r=("undefined"!=typeof window?window.innerHeight:1080),mode:i="contain",visualWidth:o=450}){if(!e||!t||e<=0||t<=0)return 1;let s;if("fixed-width"===i)s=n>o?o/n:1;else{const i=n/e,o=r/t;s=Math.min(i,o)}return s}function r(e,n,r="top center"){const i=t(e);i&&(i.style.transform=`scale(${n})`,i.style.transformOrigin=r)}function i(e){const n=t(e);if(!n)return 1;const r=window.getComputedStyle(n).transform;if(!r||"none"===r)return 1;const i=r.match(/matrix\(([^,]+),\s*[^,]+,\s*[^,]+,\s*([^,]+)/);if(i)return(parseFloat(i[1])+parseFloat(i[2]))/2;const o=r.match(/matrix3d\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\)/);if(o)return(parseFloat(o[1])+parseFloat(o[2]))/2;const s=r.match(/scale\(([^)]+)\)/);if(s){const e=s[1].split(",").map(e=>parseFloat(e.trim()));return e.length>=2?(e[0]+e[1])/2:e[0]}return 1}function o(e=".share-page"){const t=i(e);return t>0?1/t:1}function s(e,i={}){const o=t(e);if(!o)throw new Error("[one-screen-scale] element not found");const{mode:s="contain",visualWidth:c=450,origin:u="top center",autoResize:a=!0}=i;let l=1,d=null;function f(){const e=n({sourceWidth:o.offsetWidth,sourceHeight:o.offsetHeight,targetWidth:document.documentElement.clientWidth||window.innerWidth,targetHeight:window.innerHeight,mode:s,visualWidth:c});if(e!==l&&(l=e,r(o,e,u)),"fixed-width"===s&&e>0){const t=document.documentElement.clientWidth||window.innerWidth;o.style.width=`${t}px`,o.style.height=`${Math.round(window.innerHeight/e)}px`}return l}return{start:function(){return f(),a&&(d=()=>f(),window.addEventListener("resize",d)),l},stop:function(){d&&(window.removeEventListener("resize",d),d=null)},refresh:f,getScale:()=>l}}const c={mounted(e,t){const n=s(e,t.value||{});n.start(),e.__fitScreenScaler=n},updated(e,t){const n=e.__fitScreenScaler;n&&(t.value,n.refresh())},unmounted(e){const t=e.__fitScreenScaler;t&&(t.stop(),delete e.__fitScreenScaler)}};exports.applyScale=r,exports.calculateScale=n,exports.createScaler=s,exports.getInverseScale=o,exports.getPageScale=i,exports.useFitScreen=function(t,n={}){const r=e.ref(1),i=e.ref(1);let c=null,u=null;function a(e){if(!e)return;c=s(e,n);const t=c.start();r.value=t,i.value=o(e),u=()=>{r.value=c?.getScale?.()??1,i.value=o(e)},window.addEventListener("resize",u)}return e.onMounted(()=>{a(t?.value??t)}),t&&"object"==typeof t&&"value"in t&&e.watch(()=>t.value,e=>{e&&!c&&a(e)}),e.onUnmounted(()=>{u&&(window.removeEventListener("resize",u),u=null),c?.stop(),c=null}),{scale:r,inverseScale:i,refresh:()=>{const e=t?.value??t;if(!e||!c)return;const n=c.refresh();r.value=n,i.value=o(e)}}},exports.vFitScreen=c;
1
+ "use strict";var e=require("vue");const t="one-screen-scale-base-styles",n={contain:"one-screen-scale--contain","fixed-width":"one-screen-scale--fixed-width",compensate:"one-screen-scale--compensate"},r=`\n.${n.contain} {\n width: 100vw;\n min-height: 100vh;\n min-height: 100dvh;\n}\n/*\n * fixed-width 整页被 scale 后,滚动条会一起变细。\n * WebKit 只要改了宽度却不写滑块,滑块就会消失,所以宽度和颜色一起注入。\n * 这段不经过业务的 px-to-viewport。根节点和内部滚动区域都覆盖。\n */\n.${n["fixed-width"]}::-webkit-scrollbar,\n.${n["fixed-width"]} *::-webkit-scrollbar {\n width: calc(6px * var(--one-screen-inverse-scale, 1));\n height: calc(6px * var(--one-screen-inverse-scale, 1));\n}\n.${n["fixed-width"]}::-webkit-scrollbar-thumb,\n.${n["fixed-width"]} *::-webkit-scrollbar-thumb {\n background: rgba(18, 51, 105, 0.28);\n border-radius: 999px;\n}\n.${n["fixed-width"]}::-webkit-scrollbar-track,\n.${n["fixed-width"]} *::-webkit-scrollbar-track {\n background: transparent;\n}\n/*\n * 纯 CSS 反缩放(可选)。v-compensate 指令已改为 JS 行内写入同一公式,更稳。\n * ECharts/Canvas:请用 offsetWidth 量布局盒,勿用 getBoundingClientRect。\n */\n.${n.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();function o(){if("undefined"==typeof document)return null;let e=document.getElementById(t);return e?(e.textContent=r,e):(e=document.createElement("style"),e.id=t,e.textContent=r,document.head.appendChild(e),e)}function s(e,t="contain"){if(!e||void 0===e.classList)return null;o();const r=n[t]||n.contain;return[n.contain,n["fixed-width"]].forEach(t=>{t!==r&&e.classList.remove(t)}),e.classList.add(r),r}function i(e){e&&void 0!==e.classList&&e.classList.remove(n.contain,n["fixed-width"])}function a(e){return e?"string"==typeof e?document.querySelector(e):e instanceof HTMLElement?e:e.value instanceof HTMLElement?e.value:e.current instanceof HTMLElement?e.current:null:null}function c({sourceWidth:e,sourceHeight:t,targetWidth:n=("undefined"!=typeof window?window.innerWidth:1920),targetHeight:r=("undefined"!=typeof window?window.innerHeight:1080),mode:o="contain",visualWidth:s=450}){if(!e||!t||e<=0||t<=0)return 1;let i;if("fixed-width"===o)i=n>s?s/n:1;else{const o=n/e,s=r/t;i=Math.min(o,s)}return i}const l="one-screen-scale-change";function u(e,t,n="top center"){const r=a(e);if(!r)return;r.style.transform=`scale(${t})`,r.style.transformOrigin=n;const o=t>0?1/t:1;r.style.setProperty("--one-screen-scale",String(t)),r.style.setProperty("--one-screen-inverse-scale",String(o)),"undefined"!=typeof window&&window.dispatchEvent(new CustomEvent(l,{detail:{scale:t,inverseScale:o,el:r}}))}function d(e){const t=a(e);if(!t)return 1;const n=window.getComputedStyle(t).transform;if(!n||"none"===n)return 1;const r=n.match(/matrix\(([^,]+),\s*[^,]+,\s*[^,]+,\s*([^,]+)/);if(r)return(parseFloat(r[1])+parseFloat(r[2]))/2;const o=n.match(/matrix3d\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\)/);if(o)return(parseFloat(o[1])+parseFloat(o[2]))/2;const s=n.match(/scale\(([^)]+)\)/);if(s){const e=s[1].split(",").map(e=>parseFloat(e.trim()));return e.length>=2?(e[0]+e[1])/2:e[0]}return 1}function f(e,t={}){const n=a(e);if(!n)throw new Error("[one-screen-scale] element not found");const{mode:r="contain",visualWidth:o=450,origin:l="top center",autoResize:d=!0,injectBaseStyles:f=!0,observeContent:h=!0}=t;let m=1,p=null,v=null,y=null,w=0,g=0,S=null,C=!1,b={w:0,h:0},x=0;const _={width:n.style.width,height:n.style.height,transform:n.style.transform,transformOrigin:n.style.transformOrigin};function E(){C=!0;const e=function(){const e=n.offsetWidth,t=n.offsetHeight,s=document.documentElement.clientWidth||window.innerWidth,i=window.innerHeight;return b={w:e,h:t},c({sourceWidth:e,sourceHeight:t,targetWidth:s,targetHeight:i,mode:r,visualWidth:o})}();if(m=e,u(n,e,l),"fixed-width"===r&&e>0){const t=`${document.documentElement.clientWidth||window.innerWidth}px`,r=`${Math.round(window.innerHeight/e)}px`;n.style.width!==t&&(n.style.width=t),n.style.height!==r&&(n.style.height=r)}const t=x;return cancelAnimationFrame(g),g=requestAnimationFrame(()=>{t===x&&(C=!1)}),m}function L(){if(C)return;cancelAnimationFrame(w);const e=x;w=requestAnimationFrame(()=>{if(e!==x)return;const t=n.offsetWidth,r=n.offsetHeight;t===b.w&&r===b.h&&n.style.transform||E()})}function z(){E()}return{start:function(){x+=1;const e=x;return C=!1,f&&s(n,r),E(),d&&(p=z,window.addEventListener("resize",p)),h&&"undefined"!=typeof window&&("undefined"!=typeof ResizeObserver&&(S=new ResizeObserver(()=>L()),S.observe(n)),v=e=>{const t=e.target;!t||"IMG"!==t.tagName&&"VIDEO"!==t.tagName&&"IFRAME"!==t.tagName||L()},n.addEventListener("load",v,!0),n.addEventListener("error",v,!0),document.fonts&&document.fonts.ready&&document.fonts.ready.then(()=>{e===x&&L()}).catch(()=>{}),"complete"===document.readyState?L():(y=()=>L(),window.addEventListener("load",y,{once:!0})),requestAnimationFrame(()=>{requestAnimationFrame(()=>L())})),m},stop:function(){x+=1,p&&(window.removeEventListener("resize",p),p=null),v&&(n.removeEventListener("load",v,!0),n.removeEventListener("error",v,!0),v=null),y&&(window.removeEventListener("load",y),y=null),S&&(S.disconnect(),S=null),cancelAnimationFrame(w),cancelAnimationFrame(g),w=0,g=0,f&&i(n),n.style.width=_.width,n.style.height=_.height,n.style.transform=_.transform,n.style.transformOrigin=_.transformOrigin,n.style.removeProperty("--one-screen-scale"),n.style.removeProperty("--one-screen-inverse-scale")},refresh:E,getScale:()=>m}}const h="one-screen-scale-canvas-resize";let m=null;function p(e){if(!e||"undefined"==typeof getComputedStyle)return NaN;const t=getComputedStyle(e).getPropertyValue("--one-screen-scale").trim();if(!t)return NaN;const n=parseFloat(t);return n>0?n:NaN}function v(e){if(!e||"undefined"==typeof getComputedStyle)return NaN;const t=getComputedStyle(e).transform;if(!t||"none"===t)return NaN;const n=t.match(/matrix\(([^,]+),\s*[^,]+,\s*[^,]+,\s*([^,]+)/);if(n)return(parseFloat(n[1])+parseFloat(n[2]))/2;const r=t.match(/matrix3d\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\)/);if(r)return(parseFloat(r[1])+parseFloat(r[2]))/2;const o=t.match(/scale\(([^)]+)\)/);if(o){const e=o[1].split(",").map(e=>parseFloat(e.trim()));return e.length>=2?(e[0]+e[1])/2:e[0]}return NaN}function y(e){let t=e;for(;t&&1===t.nodeType;){const e=p(t);if(e>0)return e;t=t.parentElement}for(t=e;t&&1===t.nodeType;){if(!t.__oneScreenCompensate){const e=v(t);if(e>0&&1!==e)return e}t=t.parentElement}return 1}function w(e){if(!e)return{scale:1,inverseScale:1};const t=y(e),n=t>0?1/t:1;return e.style.transform=`scale(${n})`,e.style.transformOrigin="top left",e.style.width=100*t+"%",e.style.height=100*t+"%",e.style.overflow="visible",e.__oneScreenCompensate=!0,{scale:t,inverseScale:n}}function g(e){e&&(e.style.transform="",e.style.transformOrigin="",e.style.width="",e.style.height="",e.style.overflow="",delete e.__oneScreenCompensate)}function S(e){return{width:Math.max(1,Math.round(e?.offsetWidth||0)),height:Math.max(1,Math.round(e?.offsetHeight||0))}}function C(e,t){if(!e||"function"!=typeof t)return!1;let n;try{n=t(e)}catch{return!1}if(!n||"function"!=typeof n.resize)return!1;const{width:r,height:o}=S(e);try{return n.resize({width:r,height:o}),!0}catch{return!1}}function b(e){const t="undefined"!=typeof globalThis?globalThis.Chart:null,n=t?.getChart;if("function"!=typeof n)return!1;let r;try{r=n(e)}catch{return!1}if(!r||"function"!=typeof r.resize)return!1;try{return r.resize(),!0}catch{return!1}}function x(e,t){if(!e)return 0;const n=t||m||("undefined"!=typeof globalThis?globalThis.echarts:null)||null;const r=n?.getInstanceByDom;let o=0;const s=e.querySelectorAll?e.querySelectorAll("canvas"):[];for(const t of s){const n=t.parentElement||t,{width:s,height:i}=S(n);(C(t,r)||C(n,r))&&(o+=1),b(t)&&(o+=1);try{t.dispatchEvent(new CustomEvent(h,{bubbles:!0,detail:{width:s,height:i,canvas:t,el:e}}))}catch{}}if("function"==typeof r){const t=[e,...e.querySelectorAll("div")];for(const e of t)C(e,r)&&(o+=1)}return o}function _(e={}){return Boolean(e.resizeCanvas||e.echarts||e.echartsLib)}function E(e={}){return"object"==typeof e.echarts?e.echarts:e.echartsLib?e.echartsLib:void 0}function L(e){const t="function"==typeof e?e():e;return t&&"object"==typeof t?t:{}}function z(e,t={}){if(!e)throw new Error("[one-screen-scale] compensate element required");const n=_(L(t));let r=0,o=null,s=null,i=[],a=!1;const c=()=>{i.forEach(e=>clearTimeout(e)),i=[]},u=()=>{if(a)return;const n=L(t),{scale:r,inverseScale:o}=w(e);if("function"==typeof n.onRefresh)try{n.onRefresh({scale:r,inverseScale:o,el:e})}catch{}if(_(n)){const r=E(n);0===x(e,r)&&(c(),[50,200].forEach(n=>{const r=setTimeout(()=>{if(a)return;const n=L(t);x(e,E(n))},n);i.push(r)}))}},d=()=>{a||(cancelAnimationFrame(r),r=requestAnimationFrame(()=>{r=0,a||u()}))};u(),"undefined"!=typeof window&&(window.addEventListener(l,d),window.addEventListener("resize",d)),"undefined"!=typeof ResizeObserver&&(o=new ResizeObserver(d),e.parentElement&&o.observe(e.parentElement),o.observe(e)),n&&"undefined"!=typeof MutationObserver&&(s=new MutationObserver(d),s.observe(e,{childList:!0,subtree:!0}));return{refresh:u,stop:()=>{a=!0,cancelAnimationFrame(r),c(),r=0,"undefined"!=typeof window&&(window.removeEventListener(l,d),window.removeEventListener("resize",d)),o?.disconnect(),o=null,s?.disconnect(),s=null,g(e)}}}function A(e){return e.value&&"object"==typeof e.value?e.value:{}}function F(e,t){const n=A(t),r=f(e,n);r.start(),e.__fitScreenScaler=r,e.__fitScreenOptionsKey=JSON.stringify({mode:n.mode||"contain",visualWidth:n.visualWidth,origin:n.origin,autoResize:n.autoResize,injectBaseStyles:n.injectBaseStyles,observeContent:n.observeContent})}function O(e){const t=e.__fitScreenScaler;t&&(t.stop(),delete e.__fitScreenScaler,delete e.__fitScreenOptionsKey)}const N={mounted(e,t){F(e,t)},updated(e,t){const n=A(t),r=JSON.stringify({mode:n.mode||"contain",visualWidth:n.visualWidth,origin:n.origin,autoResize:n.autoResize,injectBaseStyles:n.injectBaseStyles,observeContent:n.observeContent});if(e.__fitScreenOptionsKey!==r)return O(e),void F(e,t);e.__fitScreenScaler?.refresh()},unmounted(e){O(e)}};function W(e){const t=e?.value,n=e?.modifiers||{},r=t&&"object"==typeof t?{...t}:{};return(n.resizeCanvas||n["resize-canvas"]||n.echarts||"resize-canvas"===t||"resizeCanvas"===t||"echarts"===t)&&(r.resizeCanvas=r.resizeCanvas||!0),r}function R(e){return JSON.stringify({resizeCanvas:Boolean(e.resizeCanvas||e.echarts||e.echartsLib),hasLib:Boolean(e.echarts&&"object"==typeof e.echarts||e.echartsLib),hasOnRefresh:"function"==typeof e.onRefresh})}function B(e){const t=z(e,()=>e.__oneScreenCompensateOptions||{});e.__oneScreenCompensateApi=t,e.__oneScreenCompensateKey=R(e.__oneScreenCompensateOptions||{})}const T={mounted(e,t){e.__oneScreenCompensateOptions=W(t),B(e)},updated(e,t){const n=W(t),r=R(n);if(e.__oneScreenCompensateOptions=n,e.__oneScreenCompensateKey!==r)return e.__oneScreenCompensateApi?.stop(),void B(e);e.__oneScreenCompensateApi?.refresh()},unmounted(e){e.__oneScreenCompensateApi?.stop(),delete e.__oneScreenCompensateApi,delete e.__oneScreenCompensateKey,delete e.__oneScreenCompensateOptions}},H=T;exports.BASE_CLASS=n,exports.CANVAS_RESIZE_EVENT=h,exports.SCALE_CHANGE_EVENT=l,exports.applyBaseStyles=s,exports.applyCompensateStyles=function(e){return e&&void 0!==e.classList?(o(),e.classList.add(n.compensate),n.compensate):null},exports.applyCompensateTransform=w,exports.applyScale=u,exports.calculateScale=c,exports.clearCompensateTransform=g,exports.createCompensate=z,exports.createScaler=f,exports.ensureBaseStyleSheet=o,exports.getInverseScale=function(e=".share-page"){const t=d(e);return t>0?1/t:1},exports.getPageScale=d,exports.removeBaseStyles=i,exports.removeCompensateStyles=function(e){e&&void 0!==e.classList&&e.classList.remove(n.compensate)},exports.resizeCanvasUnder=x,exports.resizeEchartsUnder=function(e,t){return x(e,t)},exports.resolvePageScale=y,exports.setCompensateEcharts=function(e){m=e||null},exports.useFitScreen=function(t,n={}){const r=e.ref(1),o=e.ref(1);let s=null,i=null;function a(e){if(!e)return;s=f(e,n);const t=s.start();r.value=t,o.value=t>0?1/t:1,i=t=>{t.detail?.el===e&&(r.value=t.detail.scale,o.value=t.detail.inverseScale)},window.addEventListener(l,i)}return e.onMounted(()=>{a(t?.value??t)}),t&&"object"==typeof t&&"value"in t&&e.watch(()=>t.value,e=>{e&&!s&&a(e)}),e.onUnmounted(()=>{i&&(window.removeEventListener(l,i),i=null),s?.stop(),s=null}),{scale:r,inverseScale:o,refresh:()=>{if(!(t?.value??t)||!s)return;const e=s.refresh();r.value=e,o.value=e>0?1/e:1}}},exports.vCompensate=T,exports.vFitCompensate=H,exports.vFitScreen=N;
2
2
  //# sourceMappingURL=index.cjs.js.map