one-screen-scale 1.0.0 → 1.1.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/src/core.js CHANGED
@@ -1,156 +1,160 @@
1
- /**
2
- * 一屏自适应缩放 — 核心算法(框架无关)
3
- *
4
- * 两种模式:
5
- * 'contain' 一屏展示,内容不溢出,用 min(scaleX, scaleY)
6
- * 'fixed-width' 固定视觉宽度,内容可滚动,反向补高
7
- */
8
-
9
- // ---------- helpers ----------
10
-
11
- function resolveEl(el) {
12
- if (!el) return null
13
- if (typeof el === 'string') return document.querySelector(el)
14
- if (el instanceof HTMLElement) return el
15
- // Vue ref / React ref
16
- if (el.value instanceof HTMLElement) return el.value
17
- if (el.current instanceof HTMLElement) return el.current
18
- return null
19
- }
20
-
21
- // ---------- scale calculation ----------
22
-
23
- export function calculateScale({
24
- sourceWidth,
25
- sourceHeight,
26
- targetWidth = (typeof window !== 'undefined' ? window.innerWidth : 1920),
27
- targetHeight = (typeof window !== 'undefined' ? window.innerHeight : 1080),
28
- mode = 'contain',
29
- visualWidth = 450, // fixed-width 模式下的目标视觉宽度
30
- screenWidth = (typeof window !== 'undefined' ? window.screen.width : 1920), // fixed-width 模式下的设备屏幕宽度
31
- }) {
32
- if (!sourceWidth || !sourceHeight || sourceWidth <= 0 || sourceHeight <= 0) return 1
33
-
34
- let scale
35
-
36
- if (mode === 'fixed-width') {
37
- if(screenWidth < visualWidth){
38
- scale = 1
39
- }else{
40
- scale = visualWidth / Math.max(targetWidth, screenWidth)
41
- }
42
- } else {
43
- const sx = targetWidth / sourceWidth
44
- const sy = targetHeight / sourceHeight
45
- scale = Math.min(sx, sy)
46
- }
47
-
48
- return scale
49
- }
50
-
51
- // ---------- apply / read ----------
52
-
53
- export function applyScale(el, scale, origin = 'top center') {
54
- const target = resolveEl(el)
55
- if (!target) return
56
- target.style.transform = `scale(${scale})`
57
- target.style.transformOrigin = origin
58
- }
59
-
60
- export function getPageScale(el) {
61
- const target = resolveEl(el)
62
- if (!target) return 1
63
-
64
- const style = window.getComputedStyle(target)
65
- const t = style.transform
66
- if (!t || t === 'none') return 1
67
-
68
- // matrix(a, b, c, d, e, f) — 2d
69
- const m2 = t.match(/matrix\(([^,]+),\s*[^,]+,\s*[^,]+,\s*([^,]+)/)
70
- if (m2) return (parseFloat(m2[1]) + parseFloat(m2[2])) / 2
71
-
72
- // matrix3d — 3d
73
- const m3 = t.match(/matrix3d\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\)/)
74
- if (m3) return (parseFloat(m3[1]) + parseFloat(m3[2])) / 2
75
-
76
- // scale(x, y) or scale(s)
77
- const sm = t.match(/scale\(([^)]+)\)/)
78
- if (sm) {
79
- const vals = sm[1].split(',').map(v => parseFloat(v.trim()))
80
- return vals.length >= 2 ? (vals[0] + vals[1]) / 2 : vals[0]
81
- }
82
-
83
- return 1
84
- }
85
-
86
- export function getInverseScale(el = '.share-page') {
87
- const s = getPageScale(el)
88
- return s > 0 ? 1 / s : 1
89
- }
90
-
91
- // ---------- scaler instance ----------
92
-
93
- export function createScaler(el, options = {}) {
94
- const target = resolveEl(el)
95
- if (!target) throw new Error('[one-screen-scale] element not found')
96
-
97
- const {
98
- mode = 'contain',
99
- visualWidth = 450,
100
- origin = 'top center',
101
- autoResize = true
102
- } = options
103
-
104
- let currentScale = 1
105
- let handler = null
106
-
107
- function compute() {
108
- const sw = target.offsetWidth
109
- const sh = target.offsetHeight
110
- const tw = window.innerWidth
111
- const th = window.innerHeight
112
-
113
- return calculateScale({
114
- sourceWidth: sw,
115
- sourceHeight: sh,
116
- targetWidth: tw,
117
- targetHeight: th,
118
- mode,
119
- visualWidth,
120
- screenWidth: window.screen.width
121
- })
122
- }
123
-
124
- function refresh() {
125
- const next = compute()
126
- if (next !== currentScale) {
127
- currentScale = next
128
- applyScale(target, next, origin)
129
-
130
- // fixed-width 模式:反向补偿高度
131
- if (mode === 'fixed-width' && next > 0) {
132
- target.style.width = `${Math.max(window.innerWidth, window.screen.width)}px`
133
- target.style.height = `${Math.round(window.innerHeight / next)}px`
134
- }
135
- }
136
- return currentScale
137
- }
138
-
139
- function start() {
140
- refresh()
141
- if (autoResize) {
142
- handler = () => refresh()
143
- window.addEventListener('resize', handler)
144
- }
145
- return currentScale
146
- }
147
-
148
- function stop() {
149
- if (handler) {
150
- window.removeEventListener('resize', handler)
151
- handler = null
152
- }
153
- }
154
-
155
- return { start, stop, refresh, getScale: () => currentScale }
156
- }
1
+ /**
2
+ * 一屏自适应缩放 — 核心算法(框架无关)
3
+ *
4
+ * 两种模式:
5
+ * 'contain' 一屏展示,内容不溢出,用 min(scaleX, scaleY)
6
+ * 'fixed-width' 固定视觉宽度,内容可滚动,反向补高
7
+ */
8
+
9
+ // ---------- helpers ----------
10
+
11
+ function resolveEl(el) {
12
+ if (!el) return null
13
+ if (typeof el === 'string') return document.querySelector(el)
14
+ if (el instanceof HTMLElement) return el
15
+ // Vue ref / React ref
16
+ if (el.value instanceof HTMLElement) return el.value
17
+ if (el.current instanceof HTMLElement) return el.current
18
+ return null
19
+ }
20
+
21
+ // ---------- scale calculation ----------
22
+
23
+ export function calculateScale({
24
+ sourceWidth,
25
+ sourceHeight,
26
+ targetWidth = (typeof window !== 'undefined' ? window.innerWidth : 1920),
27
+ targetHeight = (typeof window !== 'undefined' ? window.innerHeight : 1080),
28
+ mode = 'contain',
29
+ visualWidth = 450, // fixed-width 模式下的目标视觉宽度
30
+ // screenWidth = (typeof window !== 'undefined' ? window.screen.width : 1920), // fixed-width 模式下的设备屏幕宽度
31
+ }) {
32
+ if (!sourceWidth || !sourceHeight || sourceWidth <= 0 || sourceHeight <= 0) return 1
33
+
34
+ let scale
35
+
36
+ if (mode === 'fixed-width') {
37
+ // if(screenWidth < visualWidth){
38
+ // scale = 1
39
+ // }else{
40
+ // scale = visualWidth / Math.max(targetWidth, screenWidth)
41
+ // }
42
+ // 只用视口宽:视口比目标宽才缩小,否则 1:1(保持清晰)
43
+ scale = targetWidth > visualWidth ? visualWidth / targetWidth : 1
44
+ } else {
45
+ const sx = targetWidth / sourceWidth
46
+ const sy = targetHeight / sourceHeight
47
+ scale = Math.min(sx, sy)
48
+ }
49
+
50
+ return scale
51
+ }
52
+
53
+ // ---------- apply / read ----------
54
+
55
+ export function applyScale(el, scale, origin = 'top center') {
56
+ const target = resolveEl(el)
57
+ if (!target) return
58
+ target.style.transform = `scale(${scale})`
59
+ target.style.transformOrigin = origin
60
+ }
61
+
62
+ export function getPageScale(el) {
63
+ const target = resolveEl(el)
64
+ if (!target) return 1
65
+
66
+ const style = window.getComputedStyle(target)
67
+ const t = style.transform
68
+ if (!t || t === 'none') return 1
69
+
70
+ // matrix(a, b, c, d, e, f) — 2d
71
+ const m2 = t.match(/matrix\(([^,]+),\s*[^,]+,\s*[^,]+,\s*([^,]+)/)
72
+ if (m2) return (parseFloat(m2[1]) + parseFloat(m2[2])) / 2
73
+
74
+ // matrix3d — 3d
75
+ const m3 = t.match(/matrix3d\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,([^,]+)\)/)
76
+ if (m3) return (parseFloat(m3[1]) + parseFloat(m3[2])) / 2
77
+
78
+ // scale(x, y) or scale(s)
79
+ const sm = t.match(/scale\(([^)]+)\)/)
80
+ if (sm) {
81
+ const vals = sm[1].split(',').map(v => parseFloat(v.trim()))
82
+ return vals.length >= 2 ? (vals[0] + vals[1]) / 2 : vals[0]
83
+ }
84
+
85
+ return 1
86
+ }
87
+
88
+ export function getInverseScale(el = '.share-page') {
89
+ const s = getPageScale(el)
90
+ return s > 0 ? 1 / s : 1
91
+ }
92
+
93
+ // ---------- scaler instance ----------
94
+
95
+ export function createScaler(el, options = {}) {
96
+ const target = resolveEl(el)
97
+ if (!target) throw new Error('[one-screen-scale] element not found')
98
+
99
+ const {
100
+ mode = 'contain',
101
+ visualWidth = 450,
102
+ origin = 'top center',
103
+ autoResize = true
104
+ } = options
105
+
106
+ let currentScale = 1
107
+ let handler = null
108
+
109
+ function compute() {
110
+ const sw = target.offsetWidth
111
+ const sh = target.offsetHeight
112
+ const tw = document.documentElement.clientWidth || window.innerWidth
113
+ const th = window.innerHeight
114
+
115
+ return calculateScale({
116
+ sourceWidth: sw,
117
+ sourceHeight: sh,
118
+ targetWidth: tw,
119
+ targetHeight: th,
120
+ mode,
121
+ visualWidth,
122
+ // screenWidth: window.screen.width
123
+ })
124
+ }
125
+
126
+ function refresh() {
127
+ const next = compute()
128
+ if (next !== currentScale) {
129
+ currentScale = next
130
+ applyScale(target, next, origin)
131
+ }
132
+
133
+ // fixed-width 模式:反向补偿高度
134
+ if (mode === 'fixed-width' && next > 0) {
135
+ // target.style.width = `${Math.max(window.innerWidth, window.screen.width)}px`
136
+ const viewportWidth = document.documentElement.clientWidth || window.innerWidth
137
+ target.style.width = `${viewportWidth}px`
138
+ target.style.height = `${Math.round(window.innerHeight / next)}px`
139
+ }
140
+ return currentScale
141
+ }
142
+
143
+ function start() {
144
+ refresh()
145
+ if (autoResize) {
146
+ handler = () => refresh()
147
+ window.addEventListener('resize', handler)
148
+ }
149
+ return currentScale
150
+ }
151
+
152
+ function stop() {
153
+ if (handler) {
154
+ window.removeEventListener('resize', handler)
155
+ handler = null
156
+ }
157
+ }
158
+
159
+ return { start, stop, refresh, getScale: () => currentScale }
160
+ }
package/src/directives.js CHANGED
@@ -1,34 +1,34 @@
1
- /**
2
- * Vue 3 指令
3
- * v-fit-screen 页面级缩放
4
- * v-compensate 组件级反缩放补偿
5
- */
6
- import { createScaler } from './core.js'
7
-
8
- // ---- v-fit-screen ----
9
-
10
- export const vFitScreen = {
11
- mounted(el, binding) {
12
- const options = binding.value || {}
13
- const scaler = createScaler(el, options)
14
- scaler.start()
15
- el.__fitScreenScaler = scaler
16
- },
17
-
18
- updated(el, binding) {
19
- const scaler = el.__fitScreenScaler
20
- if (scaler) {
21
- const options = binding.value || {}
22
- // 简单处理:重新 refresh
23
- scaler.refresh()
24
- }
25
- },
26
-
27
- unmounted(el) {
28
- const scaler = el.__fitScreenScaler
29
- if (scaler) {
30
- scaler.stop()
31
- delete el.__fitScreenScaler
32
- }
33
- }
34
- }
1
+ /**
2
+ * Vue 3 指令
3
+ * v-fit-screen 页面级缩放
4
+ * v-compensate 组件级反缩放补偿
5
+ */
6
+ import { createScaler } from './core.js'
7
+
8
+ // ---- v-fit-screen ----
9
+
10
+ export const vFitScreen = {
11
+ mounted(el, binding) {
12
+ const options = binding.value || {}
13
+ const scaler = createScaler(el, options)
14
+ scaler.start()
15
+ el.__fitScreenScaler = scaler
16
+ },
17
+
18
+ updated(el, binding) {
19
+ const scaler = el.__fitScreenScaler
20
+ if (scaler) {
21
+ const options = binding.value || {}
22
+ // 简单处理:重新 refresh
23
+ scaler.refresh()
24
+ }
25
+ },
26
+
27
+ unmounted(el) {
28
+ const scaler = el.__fitScreenScaler
29
+ if (scaler) {
30
+ scaler.stop()
31
+ delete el.__fitScreenScaler
32
+ }
33
+ }
34
+ }
package/src/index.d.ts CHANGED
@@ -1,46 +1,46 @@
1
- // ===== 核心 =====
2
-
3
- export interface ScaleOptions {
4
- sourceWidth: number
5
- sourceHeight: number
6
- targetWidth?: number
7
- targetHeight?: number
8
- mode?: 'contain' | 'fixed-width'
9
- visualWidth?: number
10
- screenWidth?: number
11
- }
12
-
13
- export interface ScalerOptions {
14
- mode?: 'contain' | 'fixed-width'
15
- visualWidth?: number
16
- origin?: string
17
- autoResize?: boolean
18
- }
19
-
20
- export interface ScalerInstance {
21
- start: () => number
22
- stop: () => void
23
- refresh: () => number
24
- getScale: () => number
25
- }
26
-
27
- export function calculateScale(options: ScaleOptions): number
28
- export function applyScale(el: HTMLElement | string, scale: number, origin?: string): void
29
- export function getPageScale(el?: HTMLElement | string): number
30
- export function getInverseScale(el?: HTMLElement | string): number
31
- export function createScaler(el: HTMLElement | string, options?: ScalerOptions): ScalerInstance
32
-
33
- // ===== Vue =====
34
-
35
- import type { Ref, Directive } from 'vue'
36
-
37
- export const vFitScreen: Directive<HTMLElement, ScalerOptions>
38
-
39
- export function useFitScreen(
40
- targetRef: Ref<HTMLElement | null> | HTMLElement,
41
- options?: ScalerOptions
42
- ): {
43
- scale: Ref<number>
44
- inverseScale: Ref<number>
45
- refresh: () => void
46
- }
1
+ // ===== 核心 =====
2
+
3
+ export interface ScaleOptions {
4
+ sourceWidth: number
5
+ sourceHeight: number
6
+ targetWidth?: number
7
+ targetHeight?: number
8
+ mode?: 'contain' | 'fixed-width'
9
+ visualWidth?: number
10
+ // screenWidth?: number
11
+ }
12
+
13
+ export interface ScalerOptions {
14
+ mode?: 'contain' | 'fixed-width'
15
+ visualWidth?: number
16
+ origin?: string
17
+ autoResize?: boolean
18
+ }
19
+
20
+ export interface ScalerInstance {
21
+ start: () => number
22
+ stop: () => void
23
+ refresh: () => number
24
+ getScale: () => number
25
+ }
26
+
27
+ export function calculateScale(options: ScaleOptions): number
28
+ export function applyScale(el: HTMLElement | string, scale: number, origin?: string): void
29
+ export function getPageScale(el?: HTMLElement | string): number
30
+ export function getInverseScale(el?: HTMLElement | string): number
31
+ export function createScaler(el: HTMLElement | string, options?: ScalerOptions): ScalerInstance
32
+
33
+ // ===== Vue =====
34
+
35
+ import type { Ref, Directive } from 'vue'
36
+
37
+ export const vFitScreen: Directive<HTMLElement, ScalerOptions>
38
+
39
+ export function useFitScreen(
40
+ targetRef: Ref<HTMLElement | null> | HTMLElement,
41
+ options?: ScalerOptions
42
+ ): {
43
+ scale: Ref<number>
44
+ inverseScale: Ref<number>
45
+ refresh: () => void
46
+ }
package/src/index.js CHANGED
@@ -1,14 +1,14 @@
1
- // ---- 核心(框架无关) ----
2
- export {
3
- calculateScale,
4
- applyScale,
5
- getPageScale,
6
- getInverseScale,
7
- createScaler
8
- } from './core.js'
9
-
10
- // ---- Vue 指令 ----
11
- export { vFitScreen } from './directives.js'
12
-
13
- // ---- Vue Composable ----
14
- export { useFitScreen } from './composables.js'
1
+ // ---- 核心(框架无关) ----
2
+ export {
3
+ calculateScale,
4
+ applyScale,
5
+ getPageScale,
6
+ getInverseScale,
7
+ createScaler
8
+ } from './core.js'
9
+
10
+ // ---- Vue 指令 ----
11
+ export { vFitScreen } from './directives.js'
12
+
13
+ // ---- Vue Composable ----
14
+ export { useFitScreen } from './composables.js'
package/src/react.d.ts CHANGED
@@ -1,19 +1,19 @@
1
- // ===== React =====
2
-
3
- import type { RefObject } from 'react'
4
-
5
- export interface ScalerOptions {
6
- mode?: 'contain' | 'fixed-width'
7
- visualWidth?: number
8
- origin?: string
9
- autoResize?: boolean
10
- }
11
-
12
- export function useFitScreen(
13
- targetRef: RefObject<HTMLElement | null> | HTMLElement,
14
- options?: ScalerOptions
15
- ): {
16
- scale: number
17
- inverseScale: number
18
- refresh: () => void
19
- }
1
+ // ===== React =====
2
+
3
+ import type { RefObject } from 'react'
4
+
5
+ export interface ScalerOptions {
6
+ mode?: 'contain' | 'fixed-width'
7
+ visualWidth?: number
8
+ origin?: string
9
+ autoResize?: boolean
10
+ }
11
+
12
+ export function useFitScreen(
13
+ targetRef: RefObject<HTMLElement | null> | HTMLElement,
14
+ options?: ScalerOptions
15
+ ): {
16
+ scale: number
17
+ inverseScale: number
18
+ refresh: () => void
19
+ }
package/src/react.js CHANGED
@@ -1,46 +1,46 @@
1
- /**
2
- * React hook
3
- * useFitScreen(ref, options) → { scale, inverseScale, refresh }
4
- */
5
- import { useRef, useState, useEffect, useCallback } from 'react'
6
- import { createScaler } from './core.js'
7
-
8
- export function useFitScreen(targetRef, options = {}) {
9
- const [scale, setScale] = useState(1)
10
- const [inverseScale, setInverseScale] = useState(1)
11
- const scalerRef = useRef(null)
12
-
13
- useEffect(() => {
14
- const el = targetRef?.current ?? targetRef
15
- if (!el) return
16
-
17
- const scaler = createScaler(el, options)
18
- scalerRef.current = scaler
19
- const s = scaler.start()
20
- setScale(s)
21
- setInverseScale(s > 0 ? 1 / s : 1)
22
-
23
- const onResize = () => {
24
- const s = scaler.getScale()
25
- setScale(s)
26
- setInverseScale(s > 0 ? 1 / s : 1)
27
- }
28
- window.addEventListener('resize', onResize)
29
-
30
- return () => {
31
- window.removeEventListener('resize', onResize)
32
- scaler.stop()
33
- scalerRef.current = null
34
- }
35
- }, [targetRef])
36
-
37
- const refresh = useCallback(() => {
38
- const el = targetRef?.current ?? targetRef
39
- if (!el || !scalerRef.current) return
40
- const s = scalerRef.current.refresh()
41
- setScale(s)
42
- setInverseScale(s > 0 ? 1 / s : 1)
43
- }, [targetRef])
44
-
45
- return { scale, inverseScale, refresh }
46
- }
1
+ /**
2
+ * React hook
3
+ * useFitScreen(ref, options) → { scale, inverseScale, refresh }
4
+ */
5
+ import { useRef, useState, useEffect, useCallback } from 'react'
6
+ import { createScaler } from './core.js'
7
+
8
+ export function useFitScreen(targetRef, options = {}) {
9
+ const [scale, setScale] = useState(1)
10
+ const [inverseScale, setInverseScale] = useState(1)
11
+ const scalerRef = useRef(null)
12
+
13
+ useEffect(() => {
14
+ const el = targetRef?.current ?? targetRef
15
+ if (!el) return
16
+
17
+ const scaler = createScaler(el, options)
18
+ scalerRef.current = scaler
19
+ const s = scaler.start()
20
+ setScale(s)
21
+ setInverseScale(s > 0 ? 1 / s : 1)
22
+
23
+ const onResize = () => {
24
+ const s = scaler.getScale()
25
+ setScale(s)
26
+ setInverseScale(s > 0 ? 1 / s : 1)
27
+ }
28
+ window.addEventListener('resize', onResize)
29
+
30
+ return () => {
31
+ window.removeEventListener('resize', onResize)
32
+ scaler.stop()
33
+ scalerRef.current = null
34
+ }
35
+ }, [targetRef])
36
+
37
+ const refresh = useCallback(() => {
38
+ const el = targetRef?.current ?? targetRef
39
+ if (!el || !scalerRef.current) return
40
+ const s = scalerRef.current.refresh()
41
+ setScale(s)
42
+ setInverseScale(s > 0 ? 1 / s : 1)
43
+ }, [targetRef])
44
+
45
+ return { scale, inverseScale, refresh }
46
+ }