@cherrywind/flexible 0.1.1 → 0.1.3

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/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D=(e={})=>{const{breakpoints:r=[768],layouts:o,basicLayout:l,scope:i,immediate:p=!1,orientationchange:u=!0,ratio:y,resizeOption:E,onInitialized:d}=e,b=r,t=o,L=l??(t==null?void 0:t.at(-1)),W="--local-scope-rem",x=q();let n=y;n&&(t==null?void 0:t.length)!==n.length&&t&&(n=[...n,...Array.from({length:t.length-n.length},()=>1)]);const A=f=>!t||!L?1:t.length-1===b.length?L/t[f]:1,m=()=>{(()=>{var k;const h=(k=window.visualViewport)==null?void 0:k.width,C=h?h+x:window.innerWidth;let a=(window.innerWidth-x)/100,T=!1,v=0;if(t){for(let s=0;s<b.length;s++)if(C<=b[s]){a=a*A(s)*((n==null?void 0:n[s])??1),T=!0,v=s;break}T||(a=a*A(t.length-1)*((n==null?void 0:n[t.length-1])??1),v=t.length-1)}if(i)if(Array.isArray(i))i.forEach(s=>{const{element:g=document.documentElement,cssVarName:V=W,ratio:w}=s,B=(w==null?void 0:w[v])??(n==null?void 0:n[v])??1;g.style.setProperty(V,a*B+"px")});else{const{element:s=document.documentElement,cssVarName:g=W}=i;s.style.setProperty(g,a+"px")}else document.documentElement.style.fontSize=a+"px"})()};let c=m;if(E){const{type:f,delay:h=150}=E;f==="debounce"?c=N(m,h):f==="throttle"&&(c=P(m,h))}p?(m(),requestAnimationFrame(()=>{d==null||d()})):window.addEventListener("load",()=>{m(),requestAnimationFrame(()=>{d==null||d()})});const S=()=>{document.visibilityState==="visible"&&c()};return document.addEventListener("visibilitychange",S),window.addEventListener("resize",c),u&&screen.orientation.addEventListener("change",c),()=>{window.removeEventListener("resize",c),document.removeEventListener("visibilitychange",S),u&&screen.orientation.removeEventListener("change",c)}};function N(e,r){let o=null;return function(...l){const i=this;o&&clearTimeout(o),o=setTimeout(()=>{o=null,e.apply(i,l)},r)}}function P(e,r){let o=0,l=null,i=null;return function(...p){const u=Date.now(),y=r-(u-o);i=p,y<=0?(l&&(clearTimeout(l),l=null),o=u,e.apply(this,p)):l||(l=setTimeout(()=>{l=null,o=Date.now(),i&&(e.apply(this,i),i=null)},y))}}function q(){const e=document.createElement("div");e.style.width="100px",e.style.height="100px",e.style.overflow="scroll",e.style.position="absolute",e.style.top="-9999px",document.body.appendChild(e);const r=e.offsetWidth-e.clientWidth;return document.body.removeChild(e),r}exports.flexible=D;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D=(e={})=>{var y,k;const{breakpoints:c=[768],layouts:i,basicLayout:o,scope:l,immediate:h=!1,orientationchange:p=!0,ratio:v,resizeOption:E,onInitialized:m}=e,g=c,t=i,W=o??(t==null?void 0:t.at(-1)),L="--local-scope-rem",A=q();let n=v;n&&(t==null?void 0:t.length)!==n.length&&t&&(n=[...n,...Array.from({length:t.length-n.length},()=>1)]);const S=s=>!t||!W?1:t.length-1===g.length?W/t[s]:1,f=()=>{(()=>{var C;const a=(C=window.visualViewport)==null?void 0:C.width,N=a?a+A:window.innerWidth;let u=(window.innerWidth-A)/100,V=!1,b=0;if(t){for(let r=0;r<g.length;r++)if(N<=g[r]){u=u*S(r)*((n==null?void 0:n[r])??1),V=!0,b=r;break}V||(u=u*S(t.length-1)*((n==null?void 0:n[t.length-1])??1),b=t.length-1)}if(l)if(Array.isArray(l))l.forEach(r=>{const{element:w=document.documentElement,cssVarName:R=L,ratio:x}=r,B=(x==null?void 0:x[b])??(n==null?void 0:n[b])??1;w.style.setProperty(R,u*B+"px")});else{const{element:r=document.documentElement,cssVarName:w=L}=l;r.style.setProperty(w,u+"px")}else document.documentElement.style.fontSize=u+"px"})()};let d=f;if(E){const{type:s,delay:a=150}=E;s==="debounce"?d=F(f,a):s==="throttle"&&(d=P(f,a))}h?(f(),requestAnimationFrame(()=>{m==null||m()})):window.addEventListener("load",()=>{f(),requestAnimationFrame(()=>{m==null||m()})});const T=()=>{document.visibilityState==="visible"&&d()};return document.addEventListener("visibilitychange",T),window.addEventListener("resize",d),p&&((k=(y=screen.orientation)==null?void 0:y.addEventListener)==null||k.call(y,"change",d)),()=>{var s,a;window.removeEventListener("resize",d),document.removeEventListener("visibilitychange",T),p&&((a=(s=screen.orientation)==null?void 0:s.removeEventListener)==null||a.call(s,"change",d))}};function F(e,c){let i=null;return function(...o){const l=this;i&&clearTimeout(i),i=setTimeout(()=>{i=null,e.apply(l,o)},c)}}function P(e,c){let i=0,o=null,l=null;return function(...h){const p=Date.now(),v=c-(p-i);l=h,v<=0?(o&&(clearTimeout(o),o=null),i=p,e.apply(this,h)):o||(o=setTimeout(()=>{o=null,i=Date.now(),l&&(e.apply(this,l),l=null)},v))}}function q(){const e=document.createElement("div");e.style.width="100px",e.style.height="100px",e.style.overflow="scroll",e.style.position="absolute",e.style.top="-9999px",document.body.appendChild(e);const c=e.offsetWidth-e.clientWidth;return document.body.removeChild(e),c}function z(e,c){const{baseWidth:i=19.2,localVarName:o="--local-scope-rem"}=c;return`calc(${(e/i).toFixed(4)} * var(${o}, 1rem))`}exports.flexible=D;exports.px2Response=z;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["/**\n * 弹性布局函数的配置选项。\n */\nexport interface FlexibleOptions {\n /**\n * 一个以像素为单位的断点数组,从大到小排列。\n * 默认为 [768]。\n */\n breakpoints?: number[];\n /**\n * 一个与断点相对应的布局宽度数组。\n * 其项目数量必须比断点数组多一个。\n * 例如,如果 breakpoints 是 [768],则 layouts 可以是 [375, 1920],\n * 其中 375 是视口宽度 <= 768px 时的布局宽度,1920 是视口宽度 > 768px 时的布局宽度。\n */\n layouts?: number[];\n /**\n * 用作计算参考的基础布局宽度。\n * 仅在提供了 layouts 时有效。\n * 用作比例计算的基准布局宽度。\n * 默认为 layouts 数组中的最后一项 (layouts?.at(-1)),\n * 这通常代表最大的视口宽度。\n */\n basicLayout?: number;\n /**\n * 是否在初始化时立即应用布局。\n * 默认为 false。\n */\n immediate?: boolean;\n /**\n * 是否监听屏幕方向变化事件。\n * 默认为 true。\n */\n orientationchange?: boolean;\n\n /**\n * 是否在特定的作用域元素上设置 CSS 变量。\n * 默认为 false,表示在 document 元素上设置字体大小。\n * 如果提供一个对象,可以指定元素和 CSS 变量名。\n */\n scope?:\n | false\n | {\n /**\n * 设置 CSS 变量的作用域元素。\n * 默认为 document.documentElement。\n */\n element: HTMLElement;\n /**\n * 用于 rem 基础值的 CSS 变量名。\n * 默认为 \"--local-scope-rem\"。\n */\n cssVarName?: string;\n }\n | {\n /**\n * 设置 CSS 变量的作用域元素。\n * 默认为 document.documentElement。\n */\n element: HTMLElement;\n /**\n * 每个布局的比例因子数组,用于海报模式或自定义缩放。\n * 长度必须与 layouts 数组相同。\n * 默认为 [1, 1, ...] (无额外缩放)。\n */\n ratio?: number[];\n /**\n * 用于 rem 基础值的 CSS 变量名。\n * 默认为 \"--local-scope-rem\"。\n */\n cssVarName?: string;\n }[];\n\n /**\n * 每个布局的比例因子数组,用于海报模式或自定义缩放。\n * 长度必须与 layouts 数组相同。\n * 默认为 [1, 1, ...] (无额外缩放)。\n */\n ratio?: number[];\n /**\n * 用于控制 resize 行为的选项。\n */\n resizeOption?:\n | {\n type: 'debounce' | 'throttle';\n delay?: number;\n }\n | false;\n onInitialized?: () => void;\n}\n\n/**\n * 初始化一个弹性布局系统,该系统会根据视口宽度设置一个用于 rem 单位的 CSS 变量,\n * 并根据断点自适应缩放。\n *\n * @param options - 弹性布局的配置选项\n * @returns 一个用于移除事件监听器的清理函数\n */\nexport const flexible = (options: FlexibleOptions = {}): (() => void) => {\n const {\n breakpoints: propBreakpoints = [768],\n layouts: propLayouts,\n basicLayout: propBasicLayout,\n scope,\n immediate = false,\n orientationchange = true,\n ratio: propRatio,\n resizeOption,\n onInitialized,\n } = options;\n const breakpoints = propBreakpoints;\n const layouts = propLayouts;\n const basicLayout = propBasicLayout ?? layouts?.at(-1);\n const defaultScopeCssVarName = '--local-scope-rem';\n // 对于相同设备来说,滚动条要么永远占据宽度,要么永远不占据宽度\n const scrollbarWidth = getScrollbarWidth();\n\n // 确保 ratio 数组的长度与 layouts 匹配,默认为 1\n let ratio = propRatio;\n if (ratio) {\n if (layouts?.length !== ratio.length && layouts) {\n ratio = [...ratio, ...Array.from({ length: layouts.length - ratio.length }, () => 1)];\n }\n }\n\n /**\n * 计算特定断点的比例因子\n * @param index - 断点索引\n * @returns 比例因子,默认为 1\n */\n const getBreakpointRatio = (index: number): number => {\n if (!layouts || !basicLayout) return 1;\n if (layouts.length - 1 === breakpoints.length) {\n return basicLayout / layouts[index];\n }\n return 1; // 默认为 1 的比例因子\n };\n\n /**\n * 响应窗口大小变化并更新 CSS 变量\n */\n const responsive = (): void => {\n // 内部核心计算逻辑,可以被重复调用\n const recalculate = () => {\n //FIX: meta viewport 设置的 width 为 device-width 时,window.innerWidth 会比 window.visualViewport?.width 大\n // https://stackoverflow.com/questions/36297612/window-innerwidth-in-chromes-device-mode\n const visualWidth = window.visualViewport?.width;\n const viewportWidth = visualWidth ? visualWidth + scrollbarWidth : window.innerWidth; // 用于断点匹配\n // const effectiveWidth = document.documentElement.clientWidth; // 用于rem计算\n //TODO: ?没办法去判断滚动条的状态\n const effectiveWidth = window.innerWidth - scrollbarWidth;\n\n let vw = effectiveWidth / 100;\n let matched = false;\n let matchedIndex = 0;\n if (layouts) {\n for (let i = 0; i < breakpoints.length; i++) {\n if (viewportWidth <= breakpoints[i]) {\n vw = vw * getBreakpointRatio(i) * (ratio?.[i] ?? 1);\n matched = true;\n matchedIndex = i;\n break;\n }\n }\n if (!matched) {\n vw = vw * getBreakpointRatio(layouts.length - 1) * (ratio?.[layouts.length - 1] ?? 1);\n matchedIndex = layouts.length - 1;\n }\n }\n if (scope) {\n if (Array.isArray(scope)) {\n scope.forEach((item) => {\n const { element = document.documentElement, cssVarName = defaultScopeCssVarName, ratio: scopeRatio } = item;\n const computedRatio = scopeRatio?.[matchedIndex] ?? ratio?.[matchedIndex] ?? 1;\n element.style.setProperty(cssVarName, vw * computedRatio + 'px');\n });\n } else {\n const { element = document.documentElement, cssVarName = defaultScopeCssVarName } = scope;\n element.style.setProperty(cssVarName, vw + 'px');\n }\n } else {\n document.documentElement.style.fontSize = vw + 'px';\n }\n };\n\n // 第一次计算:使用当前的clientWidth值,这会触发浏览器的重绘\n recalculate();\n // 第二次计算:请求在浏览器下一次重绘前再次计算。\n // 此时,clientWidth已经是重排后的稳定值。\n // if (recalibrate && isScrollbarPresent) {\n // requestAnimationFrame(recalculate);\n // }\n };\n\n let resizeHandler: () => void = responsive;\n if (resizeOption) {\n // 默认延迟150ms\n const { type, delay = 150 } = resizeOption;\n if (type === 'debounce') {\n resizeHandler = debounce(responsive, delay);\n } else if (type === 'throttle') {\n resizeHandler = throttle(responsive, delay);\n }\n }\n if (immediate) {\n responsive();\n requestAnimationFrame(() => {\n onInitialized?.();\n });\n } else {\n window.addEventListener('load', () => {\n responsive();\n requestAnimationFrame(() => {\n onInitialized?.();\n });\n });\n }\n const visibilityHandler = () => {\n if (document.visibilityState === 'visible') {\n resizeHandler();\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n window.addEventListener('resize', resizeHandler);\n if (orientationchange) {\n screen.orientation.addEventListener('change', resizeHandler);\n }\n // 返回清理函数\n return () => {\n window.removeEventListener('resize', resizeHandler);\n document.removeEventListener('visibilitychange', visibilityHandler);\n if (orientationchange) {\n screen.orientation.removeEventListener('change', resizeHandler);\n }\n };\n};\n\n/**\n * 防抖函数:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。\n * @param func 要执行的函数\n * @param wait 延迟时间(毫秒)\n * @returns 防抖后的函数\n */\nfunction debounce<F extends (...args: any[]) => any>(func: F, wait: number): F {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return function (this: any, ...args: Parameters<F>) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const context = this;\n if (timeout) {\n clearTimeout(timeout);\n }\n timeout = setTimeout(() => {\n timeout = null;\n func.apply(context, args);\n }, wait);\n } as F;\n}\n\n/**\n * 节流函数:限制 func 在 delay 时间内只执行一次,确保最后一次也能被执行\n * @param func 要执行的函数\n * @param delay 节流间隔(毫秒)\n * @returns 节流后的函数\n */\nfunction throttle<T extends (...args: any[]) => void>(func: T, delay: number): (...args: Parameters<T>) => void {\n let lastCallTime = 0;\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let lastArgs: Parameters<T> | null = null;\n\n return function (...args: Parameters<T>) {\n const now = Date.now();\n const remaining = delay - (now - lastCallTime);\n lastArgs = args;\n\n if (remaining <= 0) {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n lastCallTime = now;\n //@ts-ignore\n func.apply(this, args);\n } else if (!timeout) {\n timeout = setTimeout(() => {\n timeout = null;\n lastCallTime = Date.now();\n if (lastArgs) {\n //@ts-ignore\n func.apply(this, lastArgs);\n lastArgs = null;\n }\n }, remaining);\n }\n };\n}\n\n/**\n * 计算浏览器滚动条的宽度。\n * @returns {number} 滚动条的宽度(像素)\n */\nfunction getScrollbarWidth(): number {\n const scrollDiv = document.createElement('div');\n scrollDiv.style.width = '100px';\n scrollDiv.style.height = '100px';\n scrollDiv.style.overflow = 'scroll';\n scrollDiv.style.position = 'absolute';\n scrollDiv.style.top = '-9999px';\n document.body.appendChild(scrollDiv);\n const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;\n document.body.removeChild(scrollDiv);\n return scrollbarWidth;\n}\n"],"names":["flexible","options","propBreakpoints","propLayouts","propBasicLayout","scope","immediate","orientationchange","propRatio","resizeOption","onInitialized","breakpoints","layouts","basicLayout","defaultScopeCssVarName","scrollbarWidth","getScrollbarWidth","ratio","getBreakpointRatio","index","responsive","visualWidth","_a","viewportWidth","vw","matched","matchedIndex","i","item","element","cssVarName","scopeRatio","computedRatio","resizeHandler","type","delay","debounce","throttle","visibilityHandler","func","wait","timeout","args","context","lastCallTime","lastArgs","now","remaining","scrollDiv"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["/**\n * 弹性布局函数的配置选项。\n */\nexport interface FlexibleOptions {\n /**\n * 一个以像素为单位的断点数组,从大到小排列。\n * 默认为 [768]。\n */\n breakpoints?: number[];\n /**\n * 一个与断点相对应的布局宽度数组。\n * 其项目数量必须比断点数组多一个。\n * 例如,如果 breakpoints 是 [768],则 layouts 可以是 [375, 1920],\n * 其中 375 是视口宽度 <= 768px 时的布局宽度,1920 是视口宽度 > 768px 时的布局宽度。\n */\n layouts?: number[];\n /**\n * 用作计算参考的基础布局宽度。\n * 仅在提供了 layouts 时有效。\n * 用作比例计算的基准布局宽度。\n * 默认为 layouts 数组中的最后一项 (layouts?.at(-1)),\n * 这通常代表最大的视口宽度。\n */\n basicLayout?: number;\n /**\n * 是否在初始化时立即应用布局。\n * 默认为 false。\n */\n immediate?: boolean;\n /**\n * 是否监听屏幕方向变化事件。\n * 默认为 true。\n */\n orientationchange?: boolean;\n\n /**\n * 是否在特定的作用域元素上设置 CSS 变量。\n * 默认为 false,表示在 document 元素上设置字体大小。\n * 如果提供一个对象,可以指定元素和 CSS 变量名。\n */\n scope?:\n | false\n | {\n /**\n * 设置 CSS 变量的作用域元素。\n * 默认为 document.documentElement。\n */\n element: HTMLElement;\n /**\n * 用于 rem 基础值的 CSS 变量名。\n * 默认为 \"--local-scope-rem\"。\n */\n cssVarName?: string;\n }\n | {\n /**\n * 设置 CSS 变量的作用域元素。\n * 默认为 document.documentElement。\n */\n element: HTMLElement;\n /**\n * 每个布局的比例因子数组,用于海报模式或自定义缩放。\n * 长度必须与 layouts 数组相同。\n * 默认为 [1, 1, ...] (无额外缩放)。\n */\n ratio?: number[];\n /**\n * 用于 rem 基础值的 CSS 变量名。\n * 默认为 \"--local-scope-rem\"。\n */\n cssVarName?: string;\n }[];\n\n /**\n * 每个布局的比例因子数组,用于海报模式或自定义缩放。\n * 长度必须与 layouts 数组相同。\n * 默认为 [1, 1, ...] (无额外缩放)。\n */\n ratio?: number[];\n /**\n * 用于控制 resize 行为的选项。\n */\n resizeOption?:\n | {\n type: 'debounce' | 'throttle';\n delay?: number;\n }\n | false;\n onInitialized?: () => void;\n}\n\n/**\n * 初始化一个弹性布局系统,该系统会根据视口宽度设置一个用于 rem 单位的 CSS 变量,\n * 并根据断点自适应缩放。\n *\n * @param options - 弹性布局的配置选项\n * @returns 一个用于移除事件监听器的清理函数\n */\nexport const flexible = (options: FlexibleOptions = {}): (() => void) => {\n const {\n breakpoints: propBreakpoints = [768],\n layouts: propLayouts,\n basicLayout: propBasicLayout,\n scope,\n immediate = false,\n orientationchange = true,\n ratio: propRatio,\n resizeOption,\n onInitialized,\n } = options;\n const breakpoints = propBreakpoints;\n const layouts = propLayouts;\n const basicLayout = propBasicLayout ?? layouts?.at(-1);\n const defaultScopeCssVarName = '--local-scope-rem';\n // 对于相同设备来说,滚动条要么永远占据宽度,要么永远不占据宽度\n const scrollbarWidth = getScrollbarWidth();\n\n // 确保 ratio 数组的长度与 layouts 匹配,默认为 1\n let ratio = propRatio;\n if (ratio) {\n if (layouts?.length !== ratio.length && layouts) {\n ratio = [...ratio, ...Array.from({ length: layouts.length - ratio.length }, () => 1)];\n }\n }\n\n /**\n * 计算特定断点的比例因子\n * @param index - 断点索引\n * @returns 比例因子,默认为 1\n */\n const getBreakpointRatio = (index: number): number => {\n if (!layouts || !basicLayout) return 1;\n if (layouts.length - 1 === breakpoints.length) {\n return basicLayout / layouts[index];\n }\n return 1; // 默认为 1 的比例因子\n };\n\n /**\n * 响应窗口大小变化并更新 CSS 变量\n */\n const responsive = (): void => {\n // 内部核心计算逻辑,可以被重复调用\n const recalculate = () => {\n //FIX: meta viewport 设置的 width 为 device-width 时,window.innerWidth 会比 window.visualViewport?.width 大\n // https://stackoverflow.com/questions/36297612/window-innerwidth-in-chromes-device-mode\n const visualWidth = window.visualViewport?.width;\n const viewportWidth = visualWidth ? visualWidth + scrollbarWidth : window.innerWidth; // 用于断点匹配\n // const effectiveWidth = document.documentElement.clientWidth; // 用于rem计算\n //TODO: ?没办法去判断滚动条的状态\n const effectiveWidth = window.innerWidth - scrollbarWidth;\n\n let vw = effectiveWidth / 100;\n let matched = false;\n let matchedIndex = 0;\n if (layouts) {\n for (let i = 0; i < breakpoints.length; i++) {\n if (viewportWidth <= breakpoints[i]) {\n vw = vw * getBreakpointRatio(i) * (ratio?.[i] ?? 1);\n matched = true;\n matchedIndex = i;\n break;\n }\n }\n if (!matched) {\n vw = vw * getBreakpointRatio(layouts.length - 1) * (ratio?.[layouts.length - 1] ?? 1);\n matchedIndex = layouts.length - 1;\n }\n }\n if (scope) {\n if (Array.isArray(scope)) {\n scope.forEach((item) => {\n const { element = document.documentElement, cssVarName = defaultScopeCssVarName, ratio: scopeRatio } = item;\n const computedRatio = scopeRatio?.[matchedIndex] ?? ratio?.[matchedIndex] ?? 1;\n element.style.setProperty(cssVarName, vw * computedRatio + 'px');\n });\n } else {\n const { element = document.documentElement, cssVarName = defaultScopeCssVarName } = scope;\n element.style.setProperty(cssVarName, vw + 'px');\n }\n } else {\n document.documentElement.style.fontSize = vw + 'px';\n }\n };\n\n // 第一次计算:使用当前的clientWidth值,这会触发浏览器的重绘\n recalculate();\n // 第二次计算:请求在浏览器下一次重绘前再次计算。\n // 此时,clientWidth已经是重排后的稳定值。\n // if (recalibrate && isScrollbarPresent) {\n // requestAnimationFrame(recalculate);\n // }\n };\n\n let resizeHandler: () => void = responsive;\n if (resizeOption) {\n // 默认延迟150ms\n const { type, delay = 150 } = resizeOption;\n if (type === 'debounce') {\n resizeHandler = debounce(responsive, delay);\n } else if (type === 'throttle') {\n resizeHandler = throttle(responsive, delay);\n }\n }\n if (immediate) {\n responsive();\n requestAnimationFrame(() => {\n onInitialized?.();\n });\n } else {\n window.addEventListener('load', () => {\n responsive();\n requestAnimationFrame(() => {\n onInitialized?.();\n });\n });\n }\n const visibilityHandler = () => {\n if (document.visibilityState === 'visible') {\n resizeHandler();\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n window.addEventListener('resize', resizeHandler);\n if (orientationchange) {\n screen.orientation?.addEventListener?.('change', resizeHandler);\n }\n // 返回清理函数\n return () => {\n window.removeEventListener('resize', resizeHandler);\n document.removeEventListener('visibilitychange', visibilityHandler);\n if (orientationchange) {\n screen.orientation?.removeEventListener?.('change', resizeHandler);\n }\n };\n};\n\n/**\n * 防抖函数:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。\n * @param func 要执行的函数\n * @param wait 延迟时间(毫秒)\n * @returns 防抖后的函数\n */\nfunction debounce<F extends (...args: any[]) => any>(func: F, wait: number): F {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return function (this: any, ...args: Parameters<F>) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const context = this;\n if (timeout) {\n clearTimeout(timeout);\n }\n timeout = setTimeout(() => {\n timeout = null;\n func.apply(context, args);\n }, wait);\n } as F;\n}\n\n/**\n * 节流函数:限制 func 在 delay 时间内只执行一次,确保最后一次也能被执行\n * @param func 要执行的函数\n * @param delay 节流间隔(毫秒)\n * @returns 节流后的函数\n */\nfunction throttle<T extends (...args: any[]) => void>(func: T, delay: number): (...args: Parameters<T>) => void {\n let lastCallTime = 0;\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let lastArgs: Parameters<T> | null = null;\n\n return function (...args: Parameters<T>) {\n const now = Date.now();\n const remaining = delay - (now - lastCallTime);\n lastArgs = args;\n\n if (remaining <= 0) {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n lastCallTime = now;\n //@ts-ignore\n func.apply(this, args);\n } else if (!timeout) {\n timeout = setTimeout(() => {\n timeout = null;\n lastCallTime = Date.now();\n if (lastArgs) {\n //@ts-ignore\n func.apply(this, lastArgs);\n lastArgs = null;\n }\n }, remaining);\n }\n };\n}\n\n/**\n * 计算浏览器滚动条的宽度。\n * @returns {number} 滚动条的宽度(像素)\n */\nfunction getScrollbarWidth(): number {\n const scrollDiv = document.createElement('div');\n scrollDiv.style.width = '100px';\n scrollDiv.style.height = '100px';\n scrollDiv.style.overflow = 'scroll';\n scrollDiv.style.position = 'absolute';\n scrollDiv.style.top = '-9999px';\n document.body.appendChild(scrollDiv);\n const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;\n document.body.removeChild(scrollDiv);\n return scrollbarWidth;\n}\n\n/**\n * 将像素值转换为响应式单位\n * @param px 像素值\n * @param options 配置选项\n * @returns 响应式单位字符串\n */\nexport function px2Response(\n px: number,\n options: {\n baseWidth?: number;\n localVarName?: string;\n },\n) {\n const { baseWidth = 19.2, localVarName = '--local-scope-rem' } = options;\n return `calc(${(px / baseWidth).toFixed(4)} * var(${localVarName}, 1rem))`;\n}\n"],"names":["flexible","options","propBreakpoints","propLayouts","propBasicLayout","scope","immediate","orientationchange","propRatio","resizeOption","onInitialized","breakpoints","layouts","basicLayout","defaultScopeCssVarName","scrollbarWidth","getScrollbarWidth","ratio","getBreakpointRatio","index","responsive","visualWidth","_a","viewportWidth","vw","matched","matchedIndex","i","item","element","cssVarName","scopeRatio","computedRatio","resizeHandler","type","delay","debounce","throttle","visibilityHandler","_b","func","wait","timeout","args","context","lastCallTime","lastArgs","now","remaining","scrollDiv","px2Response","px","baseWidth","localVarName"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -91,4 +91,15 @@ export declare interface FlexibleOptions {
91
91
  onInitialized?: () => void;
92
92
  }
93
93
 
94
+ /**
95
+ * 将像素值转换为响应式单位
96
+ * @param px 像素值
97
+ * @param options 配置选项
98
+ * @returns 响应式单位字符串
99
+ */
100
+ export declare function px2Response(px: number, options: {
101
+ baseWidth?: number;
102
+ localVarName?: string;
103
+ }): string;
104
+
94
105
  export { }
package/dist/index.js CHANGED
@@ -1,88 +1,95 @@
1
- const F = (e = {}) => {
1
+ const H = (e = {}) => {
2
+ var y, V;
2
3
  const {
3
- breakpoints: r = [768],
4
- layouts: o,
5
- basicLayout: l,
6
- scope: i,
7
- immediate: p = !1,
8
- orientationchange: u = !0,
4
+ breakpoints: c = [768],
5
+ layouts: i,
6
+ basicLayout: o,
7
+ scope: l,
8
+ immediate: f = !1,
9
+ orientationchange: h = !0,
9
10
  ratio: v,
10
11
  resizeOption: E,
11
- onInitialized: d
12
- } = e, w = r, t = o, L = l ?? (t == null ? void 0 : t.at(-1)), W = "--local-scope-rem", x = q();
12
+ onInitialized: m
13
+ } = e, b = c, t = i, W = o ?? (t == null ? void 0 : t.at(-1)), L = "--local-scope-rem", A = q();
13
14
  let n = v;
14
15
  n && (t == null ? void 0 : t.length) !== n.length && t && (n = [...n, ...Array.from({ length: t.length - n.length }, () => 1)]);
15
- const A = (h) => !t || !L ? 1 : t.length - 1 === w.length ? L / t[h] : 1, m = () => {
16
+ const k = (s) => !t || !W ? 1 : t.length - 1 === b.length ? W / t[s] : 1, p = () => {
16
17
  (() => {
17
- var C;
18
- const f = (C = window.visualViewport) == null ? void 0 : C.width, S = f ? f + x : window.innerWidth;
19
- let a = (window.innerWidth - x) / 100, T = !1, y = 0;
18
+ var N;
19
+ const a = (N = window.visualViewport) == null ? void 0 : N.width, S = a ? a + A : window.innerWidth;
20
+ let u = (window.innerWidth - A) / 100, C = !1, w = 0;
20
21
  if (t) {
21
- for (let s = 0; s < w.length; s++)
22
- if (S <= w[s]) {
23
- a = a * A(s) * ((n == null ? void 0 : n[s]) ?? 1), T = !0, y = s;
22
+ for (let r = 0; r < b.length; r++)
23
+ if (S <= b[r]) {
24
+ u = u * k(r) * ((n == null ? void 0 : n[r]) ?? 1), C = !0, w = r;
24
25
  break;
25
26
  }
26
- T || (a = a * A(t.length - 1) * ((n == null ? void 0 : n[t.length - 1]) ?? 1), y = t.length - 1);
27
+ C || (u = u * k(t.length - 1) * ((n == null ? void 0 : n[t.length - 1]) ?? 1), w = t.length - 1);
27
28
  }
28
- if (i)
29
- if (Array.isArray(i))
30
- i.forEach((s) => {
31
- const { element: g = document.documentElement, cssVarName: V = W, ratio: b } = s, B = (b == null ? void 0 : b[y]) ?? (n == null ? void 0 : n[y]) ?? 1;
32
- g.style.setProperty(V, a * B + "px");
29
+ if (l)
30
+ if (Array.isArray(l))
31
+ l.forEach((r) => {
32
+ const { element: g = document.documentElement, cssVarName: B = L, ratio: x } = r, D = (x == null ? void 0 : x[w]) ?? (n == null ? void 0 : n[w]) ?? 1;
33
+ g.style.setProperty(B, u * D + "px");
33
34
  });
34
35
  else {
35
- const { element: s = document.documentElement, cssVarName: g = W } = i;
36
- s.style.setProperty(g, a + "px");
36
+ const { element: r = document.documentElement, cssVarName: g = L } = l;
37
+ r.style.setProperty(g, u + "px");
37
38
  }
38
39
  else
39
- document.documentElement.style.fontSize = a + "px";
40
+ document.documentElement.style.fontSize = u + "px";
40
41
  })();
41
42
  };
42
- let c = m;
43
+ let d = p;
43
44
  if (E) {
44
- const { type: h, delay: f = 150 } = E;
45
- h === "debounce" ? c = D(m, f) : h === "throttle" && (c = N(m, f));
45
+ const { type: s, delay: a = 150 } = E;
46
+ s === "debounce" ? d = F(p, a) : s === "throttle" && (d = R(p, a));
46
47
  }
47
- p ? (m(), requestAnimationFrame(() => {
48
- d == null || d();
48
+ f ? (p(), requestAnimationFrame(() => {
49
+ m == null || m();
49
50
  })) : window.addEventListener("load", () => {
50
- m(), requestAnimationFrame(() => {
51
- d == null || d();
51
+ p(), requestAnimationFrame(() => {
52
+ m == null || m();
52
53
  });
53
54
  });
54
- const k = () => {
55
- document.visibilityState === "visible" && c();
55
+ const T = () => {
56
+ document.visibilityState === "visible" && d();
56
57
  };
57
- return document.addEventListener("visibilitychange", k), window.addEventListener("resize", c), u && screen.orientation.addEventListener("change", c), () => {
58
- window.removeEventListener("resize", c), document.removeEventListener("visibilitychange", k), u && screen.orientation.removeEventListener("change", c);
58
+ return document.addEventListener("visibilitychange", T), window.addEventListener("resize", d), h && ((V = (y = screen.orientation) == null ? void 0 : y.addEventListener) == null || V.call(y, "change", d)), () => {
59
+ var s, a;
60
+ window.removeEventListener("resize", d), document.removeEventListener("visibilitychange", T), h && ((a = (s = screen.orientation) == null ? void 0 : s.removeEventListener) == null || a.call(s, "change", d));
59
61
  };
60
62
  };
61
- function D(e, r) {
62
- let o = null;
63
- return function(...l) {
64
- const i = this;
65
- o && clearTimeout(o), o = setTimeout(() => {
66
- o = null, e.apply(i, l);
67
- }, r);
63
+ function F(e, c) {
64
+ let i = null;
65
+ return function(...o) {
66
+ const l = this;
67
+ i && clearTimeout(i), i = setTimeout(() => {
68
+ i = null, e.apply(l, o);
69
+ }, c);
68
70
  };
69
71
  }
70
- function N(e, r) {
71
- let o = 0, l = null, i = null;
72
- return function(...p) {
73
- const u = Date.now(), v = r - (u - o);
74
- i = p, v <= 0 ? (l && (clearTimeout(l), l = null), o = u, e.apply(this, p)) : l || (l = setTimeout(() => {
75
- l = null, o = Date.now(), i && (e.apply(this, i), i = null);
72
+ function R(e, c) {
73
+ let i = 0, o = null, l = null;
74
+ return function(...f) {
75
+ const h = Date.now(), v = c - (h - i);
76
+ l = f, v <= 0 ? (o && (clearTimeout(o), o = null), i = h, e.apply(this, f)) : o || (o = setTimeout(() => {
77
+ o = null, i = Date.now(), l && (e.apply(this, l), l = null);
76
78
  }, v));
77
79
  };
78
80
  }
79
81
  function q() {
80
82
  const e = document.createElement("div");
81
83
  e.style.width = "100px", e.style.height = "100px", e.style.overflow = "scroll", e.style.position = "absolute", e.style.top = "-9999px", document.body.appendChild(e);
82
- const r = e.offsetWidth - e.clientWidth;
83
- return document.body.removeChild(e), r;
84
+ const c = e.offsetWidth - e.clientWidth;
85
+ return document.body.removeChild(e), c;
86
+ }
87
+ function P(e, c) {
88
+ const { baseWidth: i = 19.2, localVarName: o = "--local-scope-rem" } = c;
89
+ return `calc(${(e / i).toFixed(4)} * var(${o}, 1rem))`;
84
90
  }
85
91
  export {
86
- F as flexible
92
+ H as flexible,
93
+ P as px2Response
87
94
  };
88
95
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * 弹性布局函数的配置选项。\n */\nexport interface FlexibleOptions {\n /**\n * 一个以像素为单位的断点数组,从大到小排列。\n * 默认为 [768]。\n */\n breakpoints?: number[];\n /**\n * 一个与断点相对应的布局宽度数组。\n * 其项目数量必须比断点数组多一个。\n * 例如,如果 breakpoints 是 [768],则 layouts 可以是 [375, 1920],\n * 其中 375 是视口宽度 <= 768px 时的布局宽度,1920 是视口宽度 > 768px 时的布局宽度。\n */\n layouts?: number[];\n /**\n * 用作计算参考的基础布局宽度。\n * 仅在提供了 layouts 时有效。\n * 用作比例计算的基准布局宽度。\n * 默认为 layouts 数组中的最后一项 (layouts?.at(-1)),\n * 这通常代表最大的视口宽度。\n */\n basicLayout?: number;\n /**\n * 是否在初始化时立即应用布局。\n * 默认为 false。\n */\n immediate?: boolean;\n /**\n * 是否监听屏幕方向变化事件。\n * 默认为 true。\n */\n orientationchange?: boolean;\n\n /**\n * 是否在特定的作用域元素上设置 CSS 变量。\n * 默认为 false,表示在 document 元素上设置字体大小。\n * 如果提供一个对象,可以指定元素和 CSS 变量名。\n */\n scope?:\n | false\n | {\n /**\n * 设置 CSS 变量的作用域元素。\n * 默认为 document.documentElement。\n */\n element: HTMLElement;\n /**\n * 用于 rem 基础值的 CSS 变量名。\n * 默认为 \"--local-scope-rem\"。\n */\n cssVarName?: string;\n }\n | {\n /**\n * 设置 CSS 变量的作用域元素。\n * 默认为 document.documentElement。\n */\n element: HTMLElement;\n /**\n * 每个布局的比例因子数组,用于海报模式或自定义缩放。\n * 长度必须与 layouts 数组相同。\n * 默认为 [1, 1, ...] (无额外缩放)。\n */\n ratio?: number[];\n /**\n * 用于 rem 基础值的 CSS 变量名。\n * 默认为 \"--local-scope-rem\"。\n */\n cssVarName?: string;\n }[];\n\n /**\n * 每个布局的比例因子数组,用于海报模式或自定义缩放。\n * 长度必须与 layouts 数组相同。\n * 默认为 [1, 1, ...] (无额外缩放)。\n */\n ratio?: number[];\n /**\n * 用于控制 resize 行为的选项。\n */\n resizeOption?:\n | {\n type: 'debounce' | 'throttle';\n delay?: number;\n }\n | false;\n onInitialized?: () => void;\n}\n\n/**\n * 初始化一个弹性布局系统,该系统会根据视口宽度设置一个用于 rem 单位的 CSS 变量,\n * 并根据断点自适应缩放。\n *\n * @param options - 弹性布局的配置选项\n * @returns 一个用于移除事件监听器的清理函数\n */\nexport const flexible = (options: FlexibleOptions = {}): (() => void) => {\n const {\n breakpoints: propBreakpoints = [768],\n layouts: propLayouts,\n basicLayout: propBasicLayout,\n scope,\n immediate = false,\n orientationchange = true,\n ratio: propRatio,\n resizeOption,\n onInitialized,\n } = options;\n const breakpoints = propBreakpoints;\n const layouts = propLayouts;\n const basicLayout = propBasicLayout ?? layouts?.at(-1);\n const defaultScopeCssVarName = '--local-scope-rem';\n // 对于相同设备来说,滚动条要么永远占据宽度,要么永远不占据宽度\n const scrollbarWidth = getScrollbarWidth();\n\n // 确保 ratio 数组的长度与 layouts 匹配,默认为 1\n let ratio = propRatio;\n if (ratio) {\n if (layouts?.length !== ratio.length && layouts) {\n ratio = [...ratio, ...Array.from({ length: layouts.length - ratio.length }, () => 1)];\n }\n }\n\n /**\n * 计算特定断点的比例因子\n * @param index - 断点索引\n * @returns 比例因子,默认为 1\n */\n const getBreakpointRatio = (index: number): number => {\n if (!layouts || !basicLayout) return 1;\n if (layouts.length - 1 === breakpoints.length) {\n return basicLayout / layouts[index];\n }\n return 1; // 默认为 1 的比例因子\n };\n\n /**\n * 响应窗口大小变化并更新 CSS 变量\n */\n const responsive = (): void => {\n // 内部核心计算逻辑,可以被重复调用\n const recalculate = () => {\n //FIX: meta viewport 设置的 width 为 device-width 时,window.innerWidth 会比 window.visualViewport?.width 大\n // https://stackoverflow.com/questions/36297612/window-innerwidth-in-chromes-device-mode\n const visualWidth = window.visualViewport?.width;\n const viewportWidth = visualWidth ? visualWidth + scrollbarWidth : window.innerWidth; // 用于断点匹配\n // const effectiveWidth = document.documentElement.clientWidth; // 用于rem计算\n //TODO: ?没办法去判断滚动条的状态\n const effectiveWidth = window.innerWidth - scrollbarWidth;\n\n let vw = effectiveWidth / 100;\n let matched = false;\n let matchedIndex = 0;\n if (layouts) {\n for (let i = 0; i < breakpoints.length; i++) {\n if (viewportWidth <= breakpoints[i]) {\n vw = vw * getBreakpointRatio(i) * (ratio?.[i] ?? 1);\n matched = true;\n matchedIndex = i;\n break;\n }\n }\n if (!matched) {\n vw = vw * getBreakpointRatio(layouts.length - 1) * (ratio?.[layouts.length - 1] ?? 1);\n matchedIndex = layouts.length - 1;\n }\n }\n if (scope) {\n if (Array.isArray(scope)) {\n scope.forEach((item) => {\n const { element = document.documentElement, cssVarName = defaultScopeCssVarName, ratio: scopeRatio } = item;\n const computedRatio = scopeRatio?.[matchedIndex] ?? ratio?.[matchedIndex] ?? 1;\n element.style.setProperty(cssVarName, vw * computedRatio + 'px');\n });\n } else {\n const { element = document.documentElement, cssVarName = defaultScopeCssVarName } = scope;\n element.style.setProperty(cssVarName, vw + 'px');\n }\n } else {\n document.documentElement.style.fontSize = vw + 'px';\n }\n };\n\n // 第一次计算:使用当前的clientWidth值,这会触发浏览器的重绘\n recalculate();\n // 第二次计算:请求在浏览器下一次重绘前再次计算。\n // 此时,clientWidth已经是重排后的稳定值。\n // if (recalibrate && isScrollbarPresent) {\n // requestAnimationFrame(recalculate);\n // }\n };\n\n let resizeHandler: () => void = responsive;\n if (resizeOption) {\n // 默认延迟150ms\n const { type, delay = 150 } = resizeOption;\n if (type === 'debounce') {\n resizeHandler = debounce(responsive, delay);\n } else if (type === 'throttle') {\n resizeHandler = throttle(responsive, delay);\n }\n }\n if (immediate) {\n responsive();\n requestAnimationFrame(() => {\n onInitialized?.();\n });\n } else {\n window.addEventListener('load', () => {\n responsive();\n requestAnimationFrame(() => {\n onInitialized?.();\n });\n });\n }\n const visibilityHandler = () => {\n if (document.visibilityState === 'visible') {\n resizeHandler();\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n window.addEventListener('resize', resizeHandler);\n if (orientationchange) {\n screen.orientation.addEventListener('change', resizeHandler);\n }\n // 返回清理函数\n return () => {\n window.removeEventListener('resize', resizeHandler);\n document.removeEventListener('visibilitychange', visibilityHandler);\n if (orientationchange) {\n screen.orientation.removeEventListener('change', resizeHandler);\n }\n };\n};\n\n/**\n * 防抖函数:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。\n * @param func 要执行的函数\n * @param wait 延迟时间(毫秒)\n * @returns 防抖后的函数\n */\nfunction debounce<F extends (...args: any[]) => any>(func: F, wait: number): F {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return function (this: any, ...args: Parameters<F>) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const context = this;\n if (timeout) {\n clearTimeout(timeout);\n }\n timeout = setTimeout(() => {\n timeout = null;\n func.apply(context, args);\n }, wait);\n } as F;\n}\n\n/**\n * 节流函数:限制 func 在 delay 时间内只执行一次,确保最后一次也能被执行\n * @param func 要执行的函数\n * @param delay 节流间隔(毫秒)\n * @returns 节流后的函数\n */\nfunction throttle<T extends (...args: any[]) => void>(func: T, delay: number): (...args: Parameters<T>) => void {\n let lastCallTime = 0;\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let lastArgs: Parameters<T> | null = null;\n\n return function (...args: Parameters<T>) {\n const now = Date.now();\n const remaining = delay - (now - lastCallTime);\n lastArgs = args;\n\n if (remaining <= 0) {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n lastCallTime = now;\n //@ts-ignore\n func.apply(this, args);\n } else if (!timeout) {\n timeout = setTimeout(() => {\n timeout = null;\n lastCallTime = Date.now();\n if (lastArgs) {\n //@ts-ignore\n func.apply(this, lastArgs);\n lastArgs = null;\n }\n }, remaining);\n }\n };\n}\n\n/**\n * 计算浏览器滚动条的宽度。\n * @returns {number} 滚动条的宽度(像素)\n */\nfunction getScrollbarWidth(): number {\n const scrollDiv = document.createElement('div');\n scrollDiv.style.width = '100px';\n scrollDiv.style.height = '100px';\n scrollDiv.style.overflow = 'scroll';\n scrollDiv.style.position = 'absolute';\n scrollDiv.style.top = '-9999px';\n document.body.appendChild(scrollDiv);\n const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;\n document.body.removeChild(scrollDiv);\n return scrollbarWidth;\n}\n"],"names":["flexible","options","propBreakpoints","propLayouts","propBasicLayout","scope","immediate","orientationchange","propRatio","resizeOption","onInitialized","breakpoints","layouts","basicLayout","defaultScopeCssVarName","scrollbarWidth","getScrollbarWidth","ratio","getBreakpointRatio","index","responsive","_a","visualWidth","viewportWidth","vw","matched","matchedIndex","i","item","element","cssVarName","scopeRatio","computedRatio","resizeHandler","type","delay","debounce","throttle","visibilityHandler","func","wait","timeout","args","context","lastCallTime","lastArgs","now","remaining","scrollDiv"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * 弹性布局函数的配置选项。\n */\nexport interface FlexibleOptions {\n /**\n * 一个以像素为单位的断点数组,从大到小排列。\n * 默认为 [768]。\n */\n breakpoints?: number[];\n /**\n * 一个与断点相对应的布局宽度数组。\n * 其项目数量必须比断点数组多一个。\n * 例如,如果 breakpoints 是 [768],则 layouts 可以是 [375, 1920],\n * 其中 375 是视口宽度 <= 768px 时的布局宽度,1920 是视口宽度 > 768px 时的布局宽度。\n */\n layouts?: number[];\n /**\n * 用作计算参考的基础布局宽度。\n * 仅在提供了 layouts 时有效。\n * 用作比例计算的基准布局宽度。\n * 默认为 layouts 数组中的最后一项 (layouts?.at(-1)),\n * 这通常代表最大的视口宽度。\n */\n basicLayout?: number;\n /**\n * 是否在初始化时立即应用布局。\n * 默认为 false。\n */\n immediate?: boolean;\n /**\n * 是否监听屏幕方向变化事件。\n * 默认为 true。\n */\n orientationchange?: boolean;\n\n /**\n * 是否在特定的作用域元素上设置 CSS 变量。\n * 默认为 false,表示在 document 元素上设置字体大小。\n * 如果提供一个对象,可以指定元素和 CSS 变量名。\n */\n scope?:\n | false\n | {\n /**\n * 设置 CSS 变量的作用域元素。\n * 默认为 document.documentElement。\n */\n element: HTMLElement;\n /**\n * 用于 rem 基础值的 CSS 变量名。\n * 默认为 \"--local-scope-rem\"。\n */\n cssVarName?: string;\n }\n | {\n /**\n * 设置 CSS 变量的作用域元素。\n * 默认为 document.documentElement。\n */\n element: HTMLElement;\n /**\n * 每个布局的比例因子数组,用于海报模式或自定义缩放。\n * 长度必须与 layouts 数组相同。\n * 默认为 [1, 1, ...] (无额外缩放)。\n */\n ratio?: number[];\n /**\n * 用于 rem 基础值的 CSS 变量名。\n * 默认为 \"--local-scope-rem\"。\n */\n cssVarName?: string;\n }[];\n\n /**\n * 每个布局的比例因子数组,用于海报模式或自定义缩放。\n * 长度必须与 layouts 数组相同。\n * 默认为 [1, 1, ...] (无额外缩放)。\n */\n ratio?: number[];\n /**\n * 用于控制 resize 行为的选项。\n */\n resizeOption?:\n | {\n type: 'debounce' | 'throttle';\n delay?: number;\n }\n | false;\n onInitialized?: () => void;\n}\n\n/**\n * 初始化一个弹性布局系统,该系统会根据视口宽度设置一个用于 rem 单位的 CSS 变量,\n * 并根据断点自适应缩放。\n *\n * @param options - 弹性布局的配置选项\n * @returns 一个用于移除事件监听器的清理函数\n */\nexport const flexible = (options: FlexibleOptions = {}): (() => void) => {\n const {\n breakpoints: propBreakpoints = [768],\n layouts: propLayouts,\n basicLayout: propBasicLayout,\n scope,\n immediate = false,\n orientationchange = true,\n ratio: propRatio,\n resizeOption,\n onInitialized,\n } = options;\n const breakpoints = propBreakpoints;\n const layouts = propLayouts;\n const basicLayout = propBasicLayout ?? layouts?.at(-1);\n const defaultScopeCssVarName = '--local-scope-rem';\n // 对于相同设备来说,滚动条要么永远占据宽度,要么永远不占据宽度\n const scrollbarWidth = getScrollbarWidth();\n\n // 确保 ratio 数组的长度与 layouts 匹配,默认为 1\n let ratio = propRatio;\n if (ratio) {\n if (layouts?.length !== ratio.length && layouts) {\n ratio = [...ratio, ...Array.from({ length: layouts.length - ratio.length }, () => 1)];\n }\n }\n\n /**\n * 计算特定断点的比例因子\n * @param index - 断点索引\n * @returns 比例因子,默认为 1\n */\n const getBreakpointRatio = (index: number): number => {\n if (!layouts || !basicLayout) return 1;\n if (layouts.length - 1 === breakpoints.length) {\n return basicLayout / layouts[index];\n }\n return 1; // 默认为 1 的比例因子\n };\n\n /**\n * 响应窗口大小变化并更新 CSS 变量\n */\n const responsive = (): void => {\n // 内部核心计算逻辑,可以被重复调用\n const recalculate = () => {\n //FIX: meta viewport 设置的 width 为 device-width 时,window.innerWidth 会比 window.visualViewport?.width 大\n // https://stackoverflow.com/questions/36297612/window-innerwidth-in-chromes-device-mode\n const visualWidth = window.visualViewport?.width;\n const viewportWidth = visualWidth ? visualWidth + scrollbarWidth : window.innerWidth; // 用于断点匹配\n // const effectiveWidth = document.documentElement.clientWidth; // 用于rem计算\n //TODO: ?没办法去判断滚动条的状态\n const effectiveWidth = window.innerWidth - scrollbarWidth;\n\n let vw = effectiveWidth / 100;\n let matched = false;\n let matchedIndex = 0;\n if (layouts) {\n for (let i = 0; i < breakpoints.length; i++) {\n if (viewportWidth <= breakpoints[i]) {\n vw = vw * getBreakpointRatio(i) * (ratio?.[i] ?? 1);\n matched = true;\n matchedIndex = i;\n break;\n }\n }\n if (!matched) {\n vw = vw * getBreakpointRatio(layouts.length - 1) * (ratio?.[layouts.length - 1] ?? 1);\n matchedIndex = layouts.length - 1;\n }\n }\n if (scope) {\n if (Array.isArray(scope)) {\n scope.forEach((item) => {\n const { element = document.documentElement, cssVarName = defaultScopeCssVarName, ratio: scopeRatio } = item;\n const computedRatio = scopeRatio?.[matchedIndex] ?? ratio?.[matchedIndex] ?? 1;\n element.style.setProperty(cssVarName, vw * computedRatio + 'px');\n });\n } else {\n const { element = document.documentElement, cssVarName = defaultScopeCssVarName } = scope;\n element.style.setProperty(cssVarName, vw + 'px');\n }\n } else {\n document.documentElement.style.fontSize = vw + 'px';\n }\n };\n\n // 第一次计算:使用当前的clientWidth值,这会触发浏览器的重绘\n recalculate();\n // 第二次计算:请求在浏览器下一次重绘前再次计算。\n // 此时,clientWidth已经是重排后的稳定值。\n // if (recalibrate && isScrollbarPresent) {\n // requestAnimationFrame(recalculate);\n // }\n };\n\n let resizeHandler: () => void = responsive;\n if (resizeOption) {\n // 默认延迟150ms\n const { type, delay = 150 } = resizeOption;\n if (type === 'debounce') {\n resizeHandler = debounce(responsive, delay);\n } else if (type === 'throttle') {\n resizeHandler = throttle(responsive, delay);\n }\n }\n if (immediate) {\n responsive();\n requestAnimationFrame(() => {\n onInitialized?.();\n });\n } else {\n window.addEventListener('load', () => {\n responsive();\n requestAnimationFrame(() => {\n onInitialized?.();\n });\n });\n }\n const visibilityHandler = () => {\n if (document.visibilityState === 'visible') {\n resizeHandler();\n }\n };\n document.addEventListener('visibilitychange', visibilityHandler);\n window.addEventListener('resize', resizeHandler);\n if (orientationchange) {\n screen.orientation?.addEventListener?.('change', resizeHandler);\n }\n // 返回清理函数\n return () => {\n window.removeEventListener('resize', resizeHandler);\n document.removeEventListener('visibilitychange', visibilityHandler);\n if (orientationchange) {\n screen.orientation?.removeEventListener?.('change', resizeHandler);\n }\n };\n};\n\n/**\n * 防抖函数:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。\n * @param func 要执行的函数\n * @param wait 延迟时间(毫秒)\n * @returns 防抖后的函数\n */\nfunction debounce<F extends (...args: any[]) => any>(func: F, wait: number): F {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return function (this: any, ...args: Parameters<F>) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const context = this;\n if (timeout) {\n clearTimeout(timeout);\n }\n timeout = setTimeout(() => {\n timeout = null;\n func.apply(context, args);\n }, wait);\n } as F;\n}\n\n/**\n * 节流函数:限制 func 在 delay 时间内只执行一次,确保最后一次也能被执行\n * @param func 要执行的函数\n * @param delay 节流间隔(毫秒)\n * @returns 节流后的函数\n */\nfunction throttle<T extends (...args: any[]) => void>(func: T, delay: number): (...args: Parameters<T>) => void {\n let lastCallTime = 0;\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let lastArgs: Parameters<T> | null = null;\n\n return function (...args: Parameters<T>) {\n const now = Date.now();\n const remaining = delay - (now - lastCallTime);\n lastArgs = args;\n\n if (remaining <= 0) {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n lastCallTime = now;\n //@ts-ignore\n func.apply(this, args);\n } else if (!timeout) {\n timeout = setTimeout(() => {\n timeout = null;\n lastCallTime = Date.now();\n if (lastArgs) {\n //@ts-ignore\n func.apply(this, lastArgs);\n lastArgs = null;\n }\n }, remaining);\n }\n };\n}\n\n/**\n * 计算浏览器滚动条的宽度。\n * @returns {number} 滚动条的宽度(像素)\n */\nfunction getScrollbarWidth(): number {\n const scrollDiv = document.createElement('div');\n scrollDiv.style.width = '100px';\n scrollDiv.style.height = '100px';\n scrollDiv.style.overflow = 'scroll';\n scrollDiv.style.position = 'absolute';\n scrollDiv.style.top = '-9999px';\n document.body.appendChild(scrollDiv);\n const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;\n document.body.removeChild(scrollDiv);\n return scrollbarWidth;\n}\n\n/**\n * 将像素值转换为响应式单位\n * @param px 像素值\n * @param options 配置选项\n * @returns 响应式单位字符串\n */\nexport function px2Response(\n px: number,\n options: {\n baseWidth?: number;\n localVarName?: string;\n },\n) {\n const { baseWidth = 19.2, localVarName = '--local-scope-rem' } = options;\n return `calc(${(px / baseWidth).toFixed(4)} * var(${localVarName}, 1rem))`;\n}\n"],"names":["flexible","options","_a","_b","propBreakpoints","propLayouts","propBasicLayout","scope","immediate","orientationchange","propRatio","resizeOption","onInitialized","breakpoints","layouts","basicLayout","defaultScopeCssVarName","scrollbarWidth","getScrollbarWidth","ratio","getBreakpointRatio","index","responsive","visualWidth","viewportWidth","vw","matched","matchedIndex","i","item","element","cssVarName","scopeRatio","computedRatio","resizeHandler","type","delay","debounce","throttle","visibilityHandler","func","wait","timeout","args","context","lastCallTime","lastArgs","now","remaining","scrollDiv","px2Response","px","baseWidth","localVarName"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cherrywind/flexible",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "flexible"