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 CHANGED
@@ -1,186 +1,323 @@
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
+ 暂不自动注入基础 CSS;`width` / `height` 由 `refresh()` 行内补偿写入。业务自己管内部滚动等。
63
+ 滚动条被 scale 变细时,可用根节点上的 `--one-screen-inverse-scale` 做反缩放,例如:
64
+
65
+ ```css
66
+ /* 若项目用了 px-to-viewport,这里要用大写 Px,避免被转成 vw 后再 ×反缩放过粗 */
67
+ .scroll::-webkit-scrollbar {
68
+ width: calc(6Px * var(--one-screen-inverse-scale, 1));
69
+ }
70
+ ```
71
+
72
+ ## 安装
73
+
74
+ ```bash
75
+ npm install one-screen-scale
76
+ ```
77
+
78
+ ## 快速开始
79
+
80
+ ### Vue 3 — 指令(最简)
81
+
82
+ ```vue
83
+ <template>
84
+ <!-- 一屏:自动注入 min-height:100vh + width:100vw -->
85
+ <div v-fit-screen class="page">
86
+ <h1>活动标题</h1>
87
+ <p>页面内容...</p>
88
+ </div>
89
+ </template>
90
+
91
+ <script setup>
92
+ import { vFitScreen } from 'one-screen-scale'
93
+ </script>
94
+ ```
95
+
96
+ ```vue
97
+ <template>
98
+ <!-- 长滚动 -->
99
+ <div v-fit-screen="{ mode: 'fixed-width' }" class="rules-page">
100
+ <header>...</header>
101
+ <main class="rules-body"><!-- overflow-y: auto --></main>
102
+ </div>
103
+ </template>
104
+ ```
105
+
106
+ ### Vue 3 — `v-compensate`(第三方组件反缩放)
107
+
108
+ 页面 `transform: scale` 后,ECharts 等常会尺寸偏小或量错。在槽位内再包一层即可(**JS 行内**写入):
109
+
110
+ `width/height: 100% * pageScale` + `transform: scale(inverse)`,scale 读祖先 `--one-screen-scale`。
111
+
112
+ ```vue
113
+ <template>
114
+ <div v-fit-screen class="page">
115
+ <div class="chart-slot">
116
+ <div v-compensate.resize-canvas class="chart-inner">
117
+ <div ref="chartEl" class="chart"></div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </template>
122
+
123
+ <script setup>
124
+ import * as echarts from 'echarts'
125
+ import { vFitScreen, vCompensate, setCompensateEcharts } from 'one-screen-scale'
126
+
127
+ // 可选:全局注入 echarts,供 resizeCanvas 自动探测
128
+ setCompensateEcharts(echarts)
129
+ </script>
130
+ ```
131
+
132
+ ```css
133
+ .chart-slot { width: 100%; height: 300px; overflow: hidden; } /* 设计稿槽位,需明确宽高 */
134
+ .chart-inner, .chart { width: 100%; height: 100%; }
135
+ ```
136
+
137
+ > **适用**:任意被页面 scale 搞小的区域,尤其 **canvas**——先反缩放,再用 `offsetWidth` 量盒(勿用 `getBoundingClientRect`)。
138
+ > **`{ resizeCanvas: true }` / `.resize-canvas`**:补偿后通知子树 canvas 重绘(探测 ECharts / Chart.js,并在每个 `<canvas>` 上派发 `one-screen-scale-canvas-resize`)。
139
+ > 只抵消页面 `v-fit-screen` 的 scale;**不处理**浏览器 Ctrl+/-。
140
+ > **不适用**:`el-slider` 等依赖指针坐标 / 易被 overflow 裁切的控件。
141
+
142
+ ### Vue 3 — Composable(需要拿到 scale 数值时)
143
+
144
+ ```vue
145
+ <script setup>
146
+ import { ref } from 'vue'
147
+ import { useFitScreen } from 'one-screen-scale'
148
+
149
+ const pageRef = ref(null)
150
+ const { scale, inverseScale } = useFitScreen(pageRef)
151
+ </script>
152
+ ```
153
+
154
+ ### React
155
+
156
+ ```jsx
157
+ import { useRef } from 'react'
158
+ import { useFitScreen, useCompensate } from 'one-screen-scale/react'
159
+
160
+ function App() {
161
+ const pageRef = useRef(null)
162
+ const chartInnerRef = useRef(null)
163
+ const { scale, inverseScale } = useFitScreen(pageRef)
164
+ // 需要通知子树 canvas 重绘时加 { resizeCanvas: true }
165
+ useCompensate(chartInnerRef, { resizeCanvas: true })
166
+
167
+ return (
168
+ <div ref={pageRef} className="page">
169
+ <div className="chart-slot" style={{ width: 350, height: 300 }}>
170
+ <div ref={chartInnerRef} style={{ width: '100%', height: '100%' }}>
171
+ <div id="chart" style={{ width: '100%', height: '100%' }} />
172
+ </div>
173
+ </div>
174
+ </div>
175
+ )
176
+ }
177
+ ```
178
+
179
+ ### 纯 JS / HTML
180
+
181
+ ```js
182
+ import { createScaler, createCompensate } from 'one-screen-scale'
183
+
184
+ const page = document.querySelector('.page')
185
+ const scaler = createScaler(page, { mode: 'contain' })
186
+ scaler.start()
187
+
188
+ // 第三方组件反缩放(对应 Vue v-compensate / React useCompensate)
189
+ const inner = document.querySelector('.chart-inner')
190
+ const compensate = createCompensate(inner, { resizeCanvas: true })
191
+
192
+ // 销毁
193
+ compensate.stop()
194
+ scaler.stop() // 移除注入的 class,并还原本库写过的 inline 样式
195
+ ```
196
+
197
+ ## 两种模式
198
+
199
+ | 模式 | 算法 | 效果 | 场景 |
200
+ |------|------|------|------|
201
+ | `contain`(默认) | `min(scaleX, scaleY)` | 内容完整可见,不溢出 | 活动页、落地页 |
202
+ | `fixed-width` | `visualWidth / innerWidth`(视口 ≤ visualWidth 时为 1) | 固定手机观感宽度,可滚动 | 规则页、长表单 |
203
+
204
+ ```js
205
+ // contain(默认)
206
+ createScaler(el)
207
+
208
+ // fixed-width
209
+ createScaler(el, { mode: 'fixed-width', visualWidth: 450 })
210
+
211
+ // 自己写根节点宽高时关闭注入
212
+ createScaler(el, { injectBaseStyles: false })
213
+ ```
214
+
215
+ ## API
216
+
217
+ ### 核心函数(框架无关)
218
+
219
+ ```ts
220
+ calculateScale(options: ScaleOptions): number
221
+
222
+ applyScale(el: Element | string, scale: number, origin?: string): void
223
+
224
+ getPageScale(el?: Element | string): number
225
+ getInverseScale(el?: Element | string): number
226
+
227
+ createScaler(el: Element | string, options?: ScalerOptions): ScalerInstance
228
+
229
+ ensureBaseStyleSheet(): HTMLStyleElement | null
230
+ applyBaseStyles(el: HTMLElement, mode?: 'contain' | 'fixed-width'): string | null
231
+ removeBaseStyles(el: HTMLElement): void
232
+ applyCompensateStyles(el: HTMLElement): string | null
233
+ removeCompensateStyles(el: HTMLElement): void
234
+
235
+ SCALE_CHANGE_EVENT // 'one-screen-scale-change',页面 scale 变化时 window 上触发
236
+
237
+ // 组件反缩放(仅抵消页面 scale)
238
+ resolvePageScale(fromEl?): number
239
+ applyCompensateTransform(el): { scale, inverseScale }
240
+ clearCompensateTransform(el): void
241
+ createCompensate(el, options?: CompensateOptions): { refresh, stop }
242
+ resizeCanvasUnder(el, echartsLib?): number
243
+ setCompensateEcharts(echarts): void // 可选,供 resizeCanvas 探测 ECharts
244
+ CANVAS_RESIZE_EVENT // 'one-screen-scale-canvas-resize'
245
+ ```
246
+
247
+ ### ScalerInstance
248
+
249
+ ```ts
250
+ {
251
+ start(): number // 注入基础样式 + 启动缩放 + 监听 resize
252
+ stop(): void // 停止监听,移除注入 class,还原 inline
253
+ refresh(): number // 手动重算
254
+ getScale(): number
255
+ }
256
+ ```
257
+
258
+ ### ScalerOptions
259
+
260
+ | 参数 | 类型 | 默认值 | 说明 |
261
+ |------|------|--------|------|
262
+ | `mode` | `'contain' \| 'fixed-width'` | `'contain'` | 缩放模式 |
263
+ | `visualWidth` | `number` | `450` | fixed-width 模式目标视觉宽度 |
264
+ | `origin` | `string` | `'top center'` | transform-origin |
265
+ | `autoResize` | `boolean` | `true` | 自动监听窗口 resize |
266
+ | `injectBaseStyles` | `boolean` | `true` | 是否自动注入模式所需宽高样式 |
267
+ | `observeContent` | `boolean` | `true` | 内容尺寸变化(如图片加载撑高)时自动重算,避免偶发 scale 偏大 |
268
+
269
+ ### Vue
270
+
271
+ ```ts
272
+ vFitScreen: Directive<HTMLElement, ScalerOptions>
273
+ vCompensate: Directive<HTMLElement> // 第三方反缩放
274
+ vFitCompensate: Directive<HTMLElement> // 同上别名
275
+
276
+ useFitScreen(ref: Ref, options?: ScalerOptions): {
277
+ scale: Ref<number>
278
+ inverseScale: Ref<number>
279
+ refresh: () => void
280
+ }
281
+ ```
282
+
283
+ ### React(`one-screen-scale/react`)
284
+
285
+ ```ts
286
+ useFitScreen(ref, options?: ScalerOptions): { scale, inverseScale, refresh }
287
+ useCompensate(ref, options?: CompensateOptions): { refresh } // 对应 v-compensate
288
+ ```
289
+
290
+ ### CompensateOptions
291
+
292
+ | 参数 | 类型 | 默认值 | 说明 |
293
+ |------|------|--------|------|
294
+ | `resizeCanvas` | `boolean` | — | **可选**。补偿后通知子树 canvas 重绘 |
295
+ | `onRefresh` | `(detail) => void` | — | 每次写入反缩放后回调 |
296
+ | `echarts` / `echartsLib` | — | — | 旧名,等价于开启 `resizeCanvas`;对象形式可传 echarts 模块 |
297
+
298
+ 也可 `setCompensateEcharts(echarts)` 全局注入,方便自动探测。
299
+
300
+ ## Canvas 模糊 / 偏小
301
+
302
+ CSS `transform: scale()` 会把 **所有 canvas** 一起缩小。先反缩放,再重绘:
303
+
304
+ | 环境 | 反缩放 | 通知 canvas 重绘 |
305
+ |------|--------|------------------|
306
+ | Vue | `v-compensate` | `v-compensate.resize-canvas` |
307
+ | React | `useCompensate(ref)` | `useCompensate(ref, { resizeCanvas: true })` |
308
+ | 纯 JS | `createCompensate(el)` | `createCompensate(el, { resizeCanvas: true })` |
309
+
310
+ `resizeCanvas` 会:探测已加载的 ECharts / Chart.js;并在每个 `<canvas>` 上派发事件,自研库可监听:
311
+
312
+ ```js
313
+ canvas.addEventListener('one-screen-scale-canvas-resize', (e) => {
314
+ const { width, height } = e.detail
315
+ myChart.resize(width, height)
316
+ })
317
+ ```
318
+
319
+ 手写等价:`width/height: 100% * pageScale` + `transform: scale(1/pageScale)`,量尺寸用 `offsetWidth`。
320
+
321
+ ## License
322
+
323
+ 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/* fixed-width:占位 class,样式先不注入 */\n.${n["fixed-width"]} {\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 f(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 d(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:f=!0,injectBaseStyles:d=!0,observeContent:m=!0}=t;let h=1,p=null,v=null,y=null,w=0,S=0,g=null,C=!1,_={w:0,h:0},E=0;const x={width:n.style.width,height:n.style.height,transform:n.style.transform,transformOrigin:n.style.transformOrigin};function b(){C=!0;const e=function(){const e=n.offsetWidth,t=n.offsetHeight,s=document.documentElement.clientWidth||window.innerWidth,i=window.innerHeight;return _={w:e,h:t},c({sourceWidth:e,sourceHeight:t,targetWidth:s,targetHeight:i,mode:r,visualWidth:o})}();if(h=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=E;return cancelAnimationFrame(S),S=requestAnimationFrame(()=>{t===E&&(C=!1)}),h}function L(){if(C)return;cancelAnimationFrame(w);const e=E;w=requestAnimationFrame(()=>{if(e!==E)return;const t=n.offsetWidth,r=n.offsetHeight;t===_.w&&r===_.h&&n.style.transform||b()})}function z(){b()}return{start:function(){E+=1;const e=E;return C=!1,d&&s(n,r),b(),f&&(p=z,window.addEventListener("resize",p)),m&&"undefined"!=typeof window&&("undefined"!=typeof ResizeObserver&&(g=new ResizeObserver(()=>L()),g.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===E&&L()}).catch(()=>{}),"complete"===document.readyState?L():(y=()=>L(),window.addEventListener("load",y,{once:!0})),requestAnimationFrame(()=>{requestAnimationFrame(()=>L())})),h},stop:function(){E+=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),g&&(g.disconnect(),g=null),cancelAnimationFrame(w),cancelAnimationFrame(S),w=0,S=0,d&&i(n),n.style.width=x.width,n.style.height=x.height,n.style.transform=x.transform,n.style.transformOrigin=x.transformOrigin,n.style.removeProperty("--one-screen-scale"),n.style.removeProperty("--one-screen-inverse-scale")},refresh:b,getScale:()=>h}}const m="one-screen-scale-canvas-resize";let h=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 S(e){e&&(e.style.transform="",e.style.transformOrigin="",e.style.width="",e.style.height="",e.style.overflow="",delete e.__oneScreenCompensate)}function g(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}=g(e);try{return n.resize({width:r,height:o}),!0}catch{return!1}}function _(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 E(e,t){if(!e)return 0;const n=t||h||("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}=g(n);(C(t,r)||C(n,r))&&(o+=1),_(t)&&(o+=1);try{t.dispatchEvent(new CustomEvent(m,{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 x(e={}){return Boolean(e.resizeCanvas||e.echarts||e.echartsLib)}function b(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=x(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(x(n)){const r=b(n);0===E(e,r)&&(c(),[50,200].forEach(n=>{const r=setTimeout(()=>{if(a)return;const n=L(t);E(e,b(n))},n);i.push(r)}))}},f=()=>{a||(cancelAnimationFrame(r),r=requestAnimationFrame(()=>{r=0,a||u()}))};u(),"undefined"!=typeof window&&(window.addEventListener(l,f),window.addEventListener("resize",f)),"undefined"!=typeof ResizeObserver&&(o=new ResizeObserver(f),e.parentElement&&o.observe(e.parentElement),o.observe(e)),n&&"undefined"!=typeof MutationObserver&&(s=new MutationObserver(f),s.observe(e,{childList:!0,subtree:!0}));return{refresh:u,stop:()=>{a=!0,cancelAnimationFrame(r),c(),r=0,"undefined"!=typeof window&&(window.removeEventListener(l,f),window.removeEventListener("resize",f)),o?.disconnect(),o=null,s?.disconnect(),s=null,S(e)}}}function A(e){return e.value&&"object"==typeof e.value?e.value:{}}function F(e,t){const n=A(t),r=d(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=m,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=S,exports.createCompensate=z,exports.createScaler=d,exports.ensureBaseStyleSheet=o,exports.getInverseScale=function(e=".share-page"){const t=f(e);return t>0?1/t:1},exports.getPageScale=f,exports.removeBaseStyles=i,exports.removeCompensateStyles=function(e){e&&void 0!==e.classList&&e.classList.remove(n.compensate)},exports.resizeCanvasUnder=E,exports.resizeEchartsUnder=function(e,t){return E(e,t)},exports.resolvePageScale=y,exports.setCompensateEcharts=function(e){h=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=d(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