Vue 可视化大屏适配方案 :postcss-plugin-px2rem, flexible autofitjs element-resize-detector

Vue 可视化大屏适配方案 :postcss-plugin-px2rem, flexible autofitjs element-resize-detector 目录一、postcss-plugin-px2rem 适配方案二、flexible 自动化适配方案三、autofit.js : 一行搞定大屏自适应适配四、其他适配方案五、常用单位之间的计算一、postcss-plugin-px2rem 适配方案1、安装 postcss-plugin-px2rem 工具npm install postcss-plugin-px2rem -D2、在根目录下 新建 postcss.config.cjs 文件1、配置postcss-plugin-px2rem 可在vue.config.js、.postcssrc.js、postcss.config.js其中之一配置权重从左到右降低没有则新建文件2、安装 postcss-plugin-px2rem : npm install postcss-plugin-px2rem -D (也可以使用 postcss-px2rem-exclude : npm install postcss-px2rem-exclude )3、适配手机 可使用 amfe-flexible 是配置可伸缩布局方案主要是将1rem设为viewWidth/10安装 : npm install amfe-flexible -D(4)、 [vite] Internal server error: Failed to load PostCSS config 将postcss.config.js文件后缀 改为 .cjs 即 postcss.config.cjsmodule.exports { plugins: [ require(autoprefixer)({ overrideBrowserslist: [ Android 4.1, iOS 7.1, Chrome 31, not ie 11, ff 30, 1%, last 2 versions, // 所有主流浏览器最近2个版本 ], grid: true , }), require(postcss-plugin-px2rem)({ //remUnit: 192, rootValue: 192, // 100 换算基数1rem相当于10px,值为37.5时,1rem为20px,淘宝的flex默认为1rem为10px unitPrecision: 5, //允许REM单位增长到的十进制数字。 //propWhiteList: [], //默认值是一个空数组这意味着禁用白名单并启用所有属性。 propBlackList: [font-size, border], //黑名单 exclude: /(node_module)/, //默认false可以reg利用正则表达式排除某些文件夹的方法例如/(node_module)\/如果想把前端UI框架内的px也转换成rem请把此属性设为默认值 // selectorBlackList: [], //要忽略并保留为px的选择器 // ignoreIdentifier: false, //boolean/string忽略单个属性的方法启用ignoreidentifier后replace将自动设置为true。 // replace: true, // 布尔值替换包含REM的规则而不是添加回退。 mediaQuery: false, //布尔值允许在媒体查询中转换px。 minPixelValue: 3, //设置要替换的最小像素值(3px会被转rem)。 默认 0 }), /*postcss-px2rem-exclude: { remUnit: 192 // exclude: /node_modules|folder_name/i, }*/ ], };二、flexible 自动化适配方案1、安装npm install lib-flexible2、使用 “这里建议使用以下内容有修改”在main.js 中引用 import flexible.js/** * Date 2023/7/11 15:11 * Description: flexible.js 修改了 lib-flexible库 refreshRem() 方法 * npm install lib-flexible */ (function(win, lib) { var doc win.document var docEl doc.documentElement var metaEl doc.querySelector(meta[nameviewport]) var flexibleEl doc.querySelector(meta[nameflexible]) var dpr 0 var scale 0 var tid var flexible lib.flexible || (lib.flexible {}) if (metaEl) { console.warn(将根据已有的meta标签来设置缩放比例) var match metaEl .getAttribute(content) // eslint-disable-next-line no-useless-escape .match(/initial\-scale([\d\.])/) if (match) { scale parseFloat(match[1]) dpr parseInt(1 / scale) } } else if (flexibleEl) { var content flexibleEl.getAttribute(content) if (content) { // eslint-disable-next-line no-useless-escape var initialDpr content.match(/initial\-dpr([\d\.])/) // eslint-disable-next-line no-useless-escape var maximumDpr content.match(/maximum\-dpr([\d\.])/) if (initialDpr) { dpr parseFloat(initialDpr[1]) scale parseFloat((1 / dpr).toFixed(2)) } if (maximumDpr) { dpr parseFloat(maximumDpr[1]) scale parseFloat((1 / dpr).toFixed(2)) } } } if (!dpr !scale) { // var isAndroid win.navigator.appVersion.match(/android/gi); var isIPhone win.navigator.appVersion.match(/iphone/gi) var devicePixelRatio win.devicePixelRatio if (isIPhone) { // iOS下对于2和3的屏用2倍的方案其余的用1倍方案 if (devicePixelRatio 3 (!dpr || dpr 3)) { dpr 3 } else if (devicePixelRatio 2 (!dpr || dpr 2)) { dpr 2 } else { dpr 1 } } else { // 其他设备下仍旧使用1倍的方案 dpr 1 } scale 1 / dpr } docEl.setAttribute(data-dpr, dpr) if (!metaEl) { metaEl doc.createElement(meta) metaEl.setAttribute(name, viewport) metaEl.setAttribute(content, initial-scale scale , maximum-scale scale , minimum-scale scale , user-scalableno) if (docEl.firstElementChild) { docEl.firstElementChild.appendChild(metaEl) } else { var wrap doc.createElement(div) wrap.appendChild(metaEl) doc.write(wrap.innerHTML) } } function refreshRem_() { let width docEl.getBoundingClientRect().width if (width / dpr 540) { width width * dpr } let rem width / 10 docEl.style.fontSize rem px flexible.rem win.rem rem //console.log([flexible].[refreshRem]: dprdpr) //console.log([flexible].[refreshRem]: widthwidth) //console.log([flexible].[refreshRem]: width / dpr 540 width) //console.log([flexible].[refreshRem]: remrem) //console.log([flexible].[refreshRem]: fontSizedocEl.style.fontSize ) //console.log([flexible].[refreshRem]: flexible.rem flexible.rem ) } function refreshRem() { let width docEl.getBoundingClientRect().width let screenRatioByDesign 16/9 let screenRatio width / docEl.clientHeight; let fontSize ( screenRatio screenRatioByDesign ? (screenRatioByDesign / screenRatio) : 1 ) * width / 10; let remfontSize.toFixed(3) docEl.style.fontSize rem px; flexible.rem win.rem rem console.log([flexible].[refreshRem]: widthwidth) console.log([flexible].[refreshRem]: screenRatioByDesignscreenRatioByDesign) console.log([flexible].[refreshRem]: screenRatioscreenRatio) console.log([flexible].[refreshRem]: fontSizefontSize) console.log([flexible].[refreshRem]: remrem) console.log([flexible].[refreshRem]: fontSize(rem)fontSize) } //动态获取实际文档宽高并设置body缩放系数 function refreshScale() { let docWidth docEl.clientWidth; let docHeight docEl.clientHeight; let designWidth 1920, designHeight 1080, widthRatio docWidth / designWidth, heightRatio docHeight / designHeight; document.body.style transform:scale(${widthRatio},${heightRatio});transform-origin:left top; // 应对浏览器全屏切换前后窗口因短暂滚动条问题出现未占满情况 setTimeout(function() { let lateWidth document.documentElement.clientWidth, lateHeight document.documentElement.clientHeight; if (lateWidth docWidth) return; widthRatio lateWidth / designWidth heightRatio lateHeight / designHeight document.body.style transform:scale( widthRatio , heightRatio );transform-origin:left top; }, 0) } win.addEventListener( resize, function() { clearTimeout(tid) tid setTimeout(refreshRem, 300) // 缩放 //refreshScale() }, false ) win.addEventListener( pageshow, function(e) { if (e.persisted) { clearTimeout(tid) tid setTimeout(refreshRem, 300) //缩放计算 //refreshScale() } }, false ) if (doc.readyState complete) { doc.body.style.fontSize 12 * dpr px } else { doc.addEventListener( DOMContentLoaded, function() { doc.body.style.fontSize 12 * dpr px }, false ) } refreshRem() flexible.dpr win.dpr dpr flexible.refreshRem refreshRem flexible.rem2px function(d) { var val parseFloat(d) * this.rem if (typeof d string d.match(/rem$/)) { val px } return val } flexible.px2rem function(d) { var val parseFloat(d) / this.rem if (typeof d string d.match(/px$/)) { val rem } return val } })(window, window[lib] || (window[lib] {}))三、autofit.js : 一行搞定大屏自适应适配1、安装 autofit.jsnpm i autofit.js2、使用import autofit from autofit.js 初始化 autofit.init() // App.vue 需要在renderDom挂载到dom之后才可以生效 export default { mounted() { autofit.init({ designHeight: 1080,//设计稿的高度默认是 929如果项目以全屏展示则可以设置为1080 designWidth: 1920, //设计稿的宽度默认是 1920 renderDom:#app, //渲染的dom默认是 #app必须使用id选择器 resize: true,//是否监听resize事件默认是 true }) }, }四、其他适配方案1、页面布局适配使用响应式布局 Flex利用媒体查询 media (min-width: 1200px)使用rem或vw单位scale缩放、引入第三方库2、整体适配,Echarts适配按1920*1080的设计稿可以使用vw,vh scale, remvw/vh1).vw/vh 单位按照设计稿的尺寸将px按比例计算转为vw和vha.可以动态计算图表的宽高字体等灵活性较高 b.当屏幕比例跟UI稿不一致移的适配不会出现两边留白情况每个图表都需要单独做字体、间距、位移的适配比较麻烦假设设计稿尺寸为 1920*1080做之前一定问清楚 ui 设计稿的尺寸即 网页宽度1920px 网页高度1080px我们都知道 网页宽度100vw 网页宽度100vh 所以在 1920px*1080px 的屏幕分辨率下 1920px 100vw 1080px 100vh这样一来以一个宽 300px 和 200px 的 div 来说其所占的宽高以 vw 和 vh 为单位计算方式如下: vwDiv (300px / 1920px ) * 100vw vhDiv (200px / 1080px ) * 100vh所以就在 1920*1080 的屏幕分辨率下计算出了单个 div 的宽高 当屏幕放大或者缩小时div 还是以 vw 和 vh 作为宽高的就会自动适应不同分辨率的屏幕css代码中可以使用scss//util.scss // 使用 scss 的 math 函数https://sass-lang.com/documentation/breaking-changes/slash-div use sass:math; // 默认设计稿的宽度 $designWidth: 1920; // 默认设计稿的高度 $designHeight: 1080; // px 转为 vw 的函数 function vw($px) { return math.div($px, $designWidth) * 100vw; } // px 转为 vh 的函数 function vh($px) { return math.div($px, $designHeight) * 100vh; }路径配置: 只需在vue.config.js里配置一下utils.scss的路径就可以全局使用了// vue.config.js 文件 const path require(path); function resolve(dir) { return path.join(__dirname, dir); } module.exports { publicPath: , configureWebpack: { name: app name, resolve: { alias: { : resolve(src), }, }, }, css: { // 全局配置 utils.scs详细配置参考 vue-cli 官网 loaderOptions: { sass: { prependData: import /styles/utils.scss;, }, }, }, };如果是vite.config.ts得这样配置return { resolve: { // Vite路径别名配置 alias: { : pathSrc, }, }, css: { // CSS 预处理器 preprocessorOptions: { //define global scss variable scss: { javascriptEnabled: true, additionalData: use /styles/variables.scss as *; // 必须这样配置才能生效 use /styles/until.scss as *; , }, }, }, };在.vue文件中直接使用template div classbox /div /template script export default{ name: Box, } /script style langscss scopedscoped /* 直接使用 vw 和 vh 函数将像素值传进去得到的就是具体的 vw vh 单位 */ .box{ width: vw(300); height: vh(100); font-size: vh(16); background-color: black; margin-left: vw(10); margin-top: vh(10); border: vh(2) solid red; } /style定义 js 样式处理函数// 定义设计稿的宽高 const designWidth 1920; const designHeight 1080; // px转vw export const px2vw (_px) { return (_px * 100.0) / designWidth vw; }; export const px2vh (_px) { return (_px * 100.0) / designHeight vh; }; export const px2font (_px) { return (_px * 100.0) / designWidth vw; };屏幕变化 图表自动调整方案1.安装 element-resize-detectornpm install element-resize-detector --save2、引入工具包在组件中使用或者在单独的 js 中使用import resizeDetector from element-resize-detector3、封装 directive// directive.js import * as ECharts from echarts; import elementResizeDetectorMaker from element-resize-detector; import Vue from vue; const HANDLER _vue_resize_handler; function bind(el, binding) { el[HANDLER] binding.value ? binding.value : () { let chart ECharts.getInstanceByDom(el); if (!chart) { return; } chart.resize(); }; // 监听绑定的div大小变化更新 echarts 大小 elementResizeDetectorMaker().listenTo(el, el[HANDLER]); } function unbind(el) { // window.removeEventListener(resize, el[HANDLER]); elementResizeDetectorMaker().removeListener(el, el[HANDLER]); delete el[HANDLER]; } // 自定义指令v-chart-resize 示例v-chart-resizefn Vue.directive(chart-resize, { bind, unbind });4、main.js 中引用// main.js 中使用 import /directive/directive; //vue 中使用 template div classlinechart div refchart v-chart-resize classchart/div /div /template如果echarts 图标需要配合 echarts 的 disposeresize setOption 方法使用图表字体、间距、位移等尺寸自适应//echarts 的字体大小只支持具体数值像素不能用百分比或者 vw 等尺寸一般字体不会去做自适应当宽高比跟 ui 稿比例出入太大时会出现文字跟图表重叠的情况 // 定义 dataUtil.js 工具函数 // Echarts图表字体、间距自适应 export const fitChartSize (size,defalteWidth 1920) { let clientWidth window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth; if (!clientWidth) return size; let scale (clientWidth / defalteWidth); return Number((size*scale).toFixed(3)); } //将函数挂载到原型上 import {fitChartSize} from dataUtil.js Vue.prototype.fitChartFont fitChartSize; //在.vue文件中使用this.fitChartSize() template div classchartsdom refchart v-chart-resize/div /template script export default { ..... methods: { getEchart() { let myChart this.$echarts.init(this.$refs.chart); const option { backgroundColor: transparent, tooltip: { trigger: item, formatter: {a} br/{b} : {c}%, }, grid: { left: this.fitChartSize(10), right: this.fitChartSize(20), top: this.fitChartSize(20), bottom: this.fitChartSize(10), containLabel: true, }, calculable: true, series: [ { ........... itemStyle: { normal: { borderWidth: 0, shadowBlur: this.fitChartSize(20), shadowOffsetX: 0, shadowOffsetY: this.fitChartSize(5), shadowColor: rgba(0, 0, 0, 0.3), }, }, }, { .......... itemStyle: { normal: { borderWidth: 0, shadowBlur: this.fitChartSize(20), shadowOffsetX: 0, shadowOffsetY: this.fitChartSize(5), shadowColor: rgba(0, 0, 0, 0.3), }, }, }, ], }; myChart.setOption(option, true); }, }, beforeDestroy() {}, }; /script style langscss scoped .chartsdom { width: 100%; height: 100%; } /style2).scale通过scale属性根据屏幕大小对图表进行整体的等比缩放代码量少适配简单一次处理后不需要在各个图表中再去单独适配因为是根据ui稿等比缩放当大屏跟ui稿的比例不一样时会出现周边留白当缩放比例过大时候字体会模糊事件热区会偏移//如果留白 可以使用背景图片填充 transform:scale(X轴缩放的倍数Y轴缩放的倍数); //注意一般情况下我们只输入一个值表示X轴和Y轴等比例缩放。 transform:scale(缩放倍数);使用template div classcontainer !-- 数据大屏展示内容区域 -- div classscreen refscreen div classtop Top/Top /div div classbottom div classleft左侧/div div classcenter中间/div div classright右侧/div /div /div /div /template script setup langts import Top from ./components/top/index.vue import { ref, onMounted } from vue //获取数据大屏展示内容盒子的DOM元素 let screen ref() onMounted(() { screen.value.style.transform scale(${getScale()}) translate(-50%,-50%) // screen.value.style.transform translate(-50%,-50%) scale(${getScale().ww},${getScale().wh}); }) //定义大屏缩放比例 function getScale(w 1920, h 1080) { const ww window.innerWidth / w const wh window.innerHeight / h return ww wh ? ww : wh; // return { ww, wh }; } // 根据浏览器大小推断缩放比例 // 首先要确定设计稿尺寸默认是 1920 x 1080 // 分别计算浏览器和设计图宽高比 // 如果浏览器的宽比大于设计稿的高比就取浏览器高度和设计稿高度之比 // 如果浏览器的宽比小于设计稿的高比就取浏览器宽度和设计稿宽度之比 //监听视口变化 window.onresize () { screen.value.style.transform scale(${getScale()}) translate(-50%,-50%) } /script style scoped langscss .container { width: 100vw; height: 100vh; background: url(./images/bg.png) no-repeat; background-size: cover; .screen { /* 注意widthheight 原稿多大就设多大 */ width: 1920px; height: 1080px; position: fixed; left: 50%; top: 50%; // transform-origin: left top; transform: translate(-50%, -50%); .top { width: 100%; height: 40px; } .bottom { display: flex; .left { flex: 1; height: 1040px; display: flex; flex-direction: column; .tourist { flex: 1.2; } .sex { flex: 1; } .age { flex: 1; } } .center { flex: 2; } .right { flex: 1; } } } } /style简单封装function debounce(fn, delay 100) { let timer null return function () { clearTimeout(timer) timer setTimeout(() { fn.apply(this, arguments) }, delay) } } export function autoScale(selector, options) { const el document.querySelector(selector) // 设计稿尺寸默认是 1920 x 1080 const { width 1920, height 1080 } options el.style.transformOrigin top left el.style.transition all 0.2s function init() { const scaleX window.innerWidth / width const scaleY window.innerHeight / height const scale Math.min(scaleX, scaleY) const left (innerWidth - width * scale) / 2 const top (innerHeight - height * scale) / 2 el.style.transform translate(${left}px, ${top}px) scale(${scale}, ${scale}) } init() addEventListener(resize, debounce(init, 200)) }使用v-scale-screen安装// vue2请使用v-scale-screen1.0.0版本vue3请使用v-scale-screen2.0.0版本 npm install v-scale-screen1.0.0 -save # or yarn add v-scale-screen使用//使用-vue2中使用插件导入vue3以组件导入 //vue2 // main.js import VScaleScreen from v-scale-screen Vue.use(VScaleScreen) 组件内使用 //html v-scale-screen width1920 height1080 :boxStyleboxStyle div v-chart..../v-chart v-chart..../v-chart v-chart..../v-chart v-chart..../v-chart v-chart..../v-chart /div /v-scale-screen //js data() { return { boxStyle: { backgroundColor: green }, } } // //vue3 中使用 v-scale-screen width1920 height1080 div v-chart..../v-chart v-chart..../v-chart v-chart..../v-chart v-chart..../v-chart v-chart..../v-chart /div /v-scale-screen script import VScaleScreen from v-scale-screen export default { components:{ VScaleScreen } } /script3).rem vw vh获得rem 的基准值, 动态的计算html根元素的font-size图表中通过ww vh动态计算字体、间距、位移等布局的自适应代码量少适配简单因为是根据ui稿等比缩放当大屏跟ui稿的比例不一样时会出现周边留白,图表需要单个做字体、间距、位移的适配可以使用 autofit.js五、常用单位之间的计算在 1920×1080 设计稿下rem、vh、vw 与 px 的转换公式如下vh 是一种相对长度单位表示视口高度的百分比。具体来说1vh 等于视口高度的 1%。视口是指浏览器窗口的可见区域。vw表示视口宽度的百分比1vw 等于视口宽度的 1%。vmin表示视口宽度和高度中较小的那个值的百分比。vmax表示视口宽度和高度中较大的那个值的百分比。1、px 转 vw基于视口宽度公式‌vw(目标px值 / 1920) * 100‌示例‌144px 转换为 vw(144 / 1920) * 100 ≈ 7.5vw2、 ‌px 转 vh基于视口高度公式‌vh(目标px值 / 1080) * 100‌示例‌144px 转换为 vh(144 / 1080) * 100 ≈ 13.33vh3、px 转 rem(基于根字体大小)默认基准‌1rem 16px浏览器默认值但可通过动态设置 html 的 font-size 调整动态基准推荐‌若设定 1rem 100px简化计算则公式‌rem 目标px值 / 100示例144px 转换为 rem:144 / 100 1.44rem4. ‌rem 与 vw/vh 的关联‌‌动态根字体方案‌设置 html 的 font-size 为 5.208vw基于 1920px 设计稿‌推导‌100vw 1920px ⇒ 1vw 19.2px若 1rem 100px则1rem (100 / 19.2) vw ≈ 5.208vw效果‌:所有 rem 单位将随视口宽度等比缩放5、综合换算表注意事项若设计稿尺寸非 1920×1080需替换公式中的分母为实际设计稿宽度或高度移动端适配时可结合媒体查询调整基准值如 375px 屏幕下 1rem 50px )