前言
當接到可視化大屏需求時,你是否會有以下疑問👇
如何做一款定製化的數據大屏?
開發可視化數據大屏如何做自適應?
vw vh、rem、scale 到底哪種比較好?
時間不夠,有沒有偷懶的方法?
最近在公司開發了一個可視化大屏,開發定製化大屏,大家可能都一個感受,開發大屏主要是兩方面的工作:
大屏之關鍵-前期的自適應適配
根據 ui 稿繪製圖表,調細節
而解決了適配問題後,後面就只是一個慢工出細活,耗時間的事情了。
適配方案分析
看了網上的各種方案,目前大家採用的大概有 3 種👇
以上 3 種方案在實際應用中該怎麼選擇視具體情況而定,也有看到大家說自適應在地圖的適配中會有一些兼容問題,我這邊還沒有實踐過。
- 如果想簡單,客戶能同意留白,選用
scale
即可 - 如果需要兼容不同比例的大屏,並且想在不同比例中都有比較好的效果,圖表佔滿屏幕,類似於移動端的響應式,可以採用 vw vh 的方案
- 至於 rem,個人覺得就是 scale 和 vw vh 的綜合,最終的效果跟
scale
差不多
接下來介紹下三種方案的具體實現,方案中的代碼都以 vue2.0 和 vue-cli3 搭建的 vue 項目爲例,因爲是 demo,圖表的一些細節就沒有過多細緻的調整了
方案一:vw vh
上效果
當屏幕的尺寸比例剛好是 16:9 時
當屏幕的尺寸比例大於 16:9 時
當屏幕的尺寸比例小於 16:9 時
實現思路
按照設計稿的尺寸,將px
按比例計算轉爲vw
和vh
,轉換公式如下
假設設計稿尺寸爲 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 方案 - sass
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";`, }, }, }, };
在 .vue 中使用
<template> <div class="box"> </div> </template> <script> export default{ name: "Box", } </script> <style lang="scss" scoped="scoped"> /* 直接使用 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>
css 方案 - less
utils.less
@charset "utf-8"; // 默認設計稿的寬度 @designWidth: 1920; // 默認設計稿的高度 @designHeight: 1080; .px2vw(@name, @px) { @{name}: (@px / @designWidth) * 100vw; } .px2vh(@name, @px) { @{name}: (@px / @designHeight) * 100vh; } .px2font(@px) { font-size: (@px / @designWidth) * 100vw; }
路徑配置
在vue.config.js
裏配置一下utils.less
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.less loaderOptions: { less: { additionalData: `@import "@/styles/utils.less";`, }, }, }, };
在 .vue 文件中使用
<template> <div class="box"> </div> </template> <script> export default{ name: "Box", } </script> <style lang="less" scoped="scoped"> /* 直接使用 vw 和 vh 函數,將像素值傳進去,得到的就是具體的 vw vh單位 */ .box{ .px2vw(width, 300); .px2vh(height, 100); .px2font(16); .px2vw(margin-left, 300); .px2vh(margin-top, 100); background-color: black; } </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'; };
屏幕變化後,圖表自動調整
這種使用方式有個弊端,就是屏幕尺寸發生變化後,需要手動刷新一下才能完成自適應調整
爲了解決這個問題,你需要在各個圖表中監聽頁面尺寸變化,重新調整圖表,在 vue 項目中,也可以藉助element-resize-detector
,最好封裝個 resize 的指令,在各圖表中就只要使用該指令就可以了,畢竟作爲程序員,能偷懶就偷懶
- 安裝 element-resize-detector
npm install element-resize-detector --save
- 引入工具包在組件中使用或者在單獨的 js 中使用
import resizeDetector from 'element-resize-detector'
- 封裝 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-resize="fn" Vue.directive("chart-resize", { bind, unbind });
- main.js 中引入
import '@/directive/directive';
- html 代碼
<template> <div class="linechart"> <div ref="chart" v-chart-resize class="chart"></div> </div> </template>
這裏要注意的是,圖表中如果需要 tab 切換動態更新圖表數據,在更新數據時一定不要用 echarts 的 dispose 方法先將圖表移除,再重新繪製,因爲 resize 指令中掛載到的圖表實例還是舊的,就監聽不到新的 chart 元素的 resize 了,更新數據只需要用 chart 的 setOption 方法重新設置配置項即可。
圖表字體、間距、位移等尺寸自適應
echarts 的字體大小隻支持具體數值(像素),不能用百分比或者 vw 等尺寸,一般字體不會去做自適應,當寬高比跟 ui 稿比例出入太大時,會出現文字跟圖表重疊的情況
這裏我們就需要封裝一個工具函數,來處理圖表中文字自適應了👇
-
默認情況下,這裏以你的設計稿是 1920*1080 爲例,即網頁寬度是 1920px (做之前一定問清楚 ui 設計稿的尺寸)
-
把這個函數寫在一個單獨的工具文件
dataUtil.js
裏面,在需要的時候調用 -
其原理是計算出當前屏幕寬度和默認設計寬度的比值,將原始的尺寸乘以該值
-
另外,其它 echarts 的配置項,比如間距、定位、邊距也可以用該函數
- 編寫 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 '@src/utils/dataUtil.js'
Vue.prototype.fitChartFont = fitChartSize;
- 這樣你可以在
.vue
文件中直接使用this.fitChartSize()
調用
<template> <div class="chartsdom" ref="chart" v-chart-resize></div> </template> <script> export default { name: "dashboardChart", data() { return { option: null, }; }, mounted() { this.getEchart(); }, 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: [ { color: ["#0db1cdcc"], name: "計劃投入", type: "funnel", width: "45%", height: "70%", x: "5%", minSize: "10%", funnelAlign: "right", center: ["50%", "50%"], // for pie data: [ { value: 30, name: "下單30%", }, { value: 55, name: "諮詢55%", }, { value: 65, name: "點擊65%", }, { value: 60, name: "訪問62%", }, { value: 80, name: "展現80%", }, ].sort(function (a, b) { return a.value - b.value; }), roseType: true, label: { normal: { formatter: function () {}, position: "inside", }, }, itemStyle: { normal: { borderWidth: 0, shadowBlur: this.fitChartSize(20), shadowOffsetX: 0, shadowOffsetY: this.fitChartSize(5), shadowColor: "rgba(0, 0, 0, 0.3)", }, }, }, { color: ["#0C66FF"], name: "實際投入", type: "funnel", width: "45%", height: "70%", x: "50%", minSize: "10%", funnelAlign: "left", center: ["50%", "50%"], // for pie data: [ { value: 35, name: "下單35%", }, { value: 40, name: "諮詢40%", }, { value: 70, name: "訪問70%", }, { value: 90, name: "點擊90%", }, { value: 95, name: "展現95%", }, ].sort(function (a, b) { return a.value - b.value; }), roseType: true, label: { normal: { position: "inside", }, }, 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 lang="scss" scoped> .chartsdom { width: 100%; height: 100%; } </style>
方案二:scale
通過 css 的 scale 屬性,根據屏幕大小,對圖表進行整體的等比縮放,從而達到自適應效果
上效果
當屏幕的尺寸比例剛好是 16:9 時,頁面能剛好全屏展示,內容佔滿顯示器
當屏幕的尺寸比例小於 16:9 時,頁面上下留白,左右佔滿並上下居中,顯示比例保持 16:9
當屏幕尺寸比例大於 16:9 時,頁面左右留白,上下佔滿並居中,顯示比例保持 16:9
html 部分
<div className="screen-wrapper"> <div className="screen" id="screen"> </div> </div>
js 部分
<script> export default { mounted() { // 初始化自適應 ----在剛顯示的時候就開始適配一次 handleScreenAuto(); // 綁定自適應函數 ---防止瀏覽器欄變化後不再適配 window.onresize = () => handleScreenAuto(); }, deleted() { window.onresize = null; }, methods: { // 數據大屏自適應函數 handleScreenAuto() { const designDraftWidth = 1920; //設計稿的寬度 const designDraftHeight = 960; //設計稿的高度 // 根據屏幕的變化適配的比例 const scale = document.documentElement.clientWidth / document.documentElement.clientHeight < designDraftWidth / designDraftHeight ? document.documentElement.clientWidth / designDraftWidth : document.documentElement.clientHeight / designDraftHeight; // 縮放比例 document.querySelector( '#screen', ).style.transform = `scale(${scale}) translate(-50%, -50%)`; }, }, }; </script>
css部分
/* 除了設計稿的寬高是根據您自己的設計稿決定以外,其他複製粘貼就完事 */ .screen-root { height: 100%; width: 100%; .screen { display: inline-block; width: 1920px; //設計稿的寬度 height: 960px; //設計稿的高度 transform-origin: 0 0; position: absolute; left: 50%; top: 50%; } }
實現思路
如何縮放
當屏幕寬高比 < 設計稿寬高比
,我們需要縮放的比例是屏幕寬度 / 設計稿寬度
當屏幕寬高比 > 設計稿寬高比
,我們需要縮放的比例是屏幕高度 / 設計稿高度
const scale = document.documentElement.clientWidth / document.documentElement.clientHeight < designDraftWidth / designDraftHeight ? (document.documentElement.clientWidth / designDraftWidth) : (document.documentElement.clientHeight / designDraftHeight);
如果我們拿到的設計稿寬高爲: 1920 * 960 px ,而我們的屏幕大小是 1440 * 900 px,那麼 1440/900 = 1.6,920/960 = 2
因爲 1.6 < 2 (當前屏幕寬高比小於設計稿寬高比)
所以我們需要縮放的比例是:屏幕寬度除以設計稿寬度 = 1440/1920 = 0.75
如何居中
首先我們利用 transform:translate(-50%,-50%)
,將動畫的基點設爲左上角
transform-origin:設置動畫的基點(中心點),默認點是元素的中心點
語法
transform-origin: x-axis y-axis z-axis;
然後利用transform:translate(-50%,-50%)
,將圖表沿 x,y 軸移動 50%
接下來利用絕對定位
將圖表定位到中間位置
position: absolute;
left: 50%;
top: 50%;
偷懶方法-插件
v-scale-screen
是使用 css 屬性 transform 實現縮放效果的一個大屏自適應組件,通過 scale 進行等比例計算,達到等比例縮放的效果,同時也支持鋪滿全屏,寬度等比,高度等比,等自適應方案,具體可查大屏自適應終極解決方案
方案三:rem + vw wh
上效果
當屏幕的尺寸比例剛好是 16:9 時,頁面能剛好全屏展示,內容佔滿顯示器
當屏幕的尺寸比例小於 16:9 時,頁面上下留白,左右佔滿並上下居中,顯示比例保持 16:9
當屏幕尺寸比例大於 16:9 時,頁面左右留白,上下佔滿並居中,顯示比例保持 16:9
實現思路
關於 remrem(font size of the root element)
,是 css3 中新增的一個大小單位,即相對於根元素 font-size 值的大小。
自適應思路
動態的計算出頁面的 fontsize 從而改變 rem 的大小。
- 拿 1920 * 1080 的標準屏幕大小爲例,將屏幕分爲
10
份,先計算rem 的基準值:
1920 / 10 = 192; - 把所有元素的長、寬、位置、字體大小等原來的 px 單位全部轉換成 rem;
- 網頁加載後,用 js 去計算當前瀏覽器的寬度,並設置 html 的 font-size 爲 (
當前瀏覽器窗口寬度 / 10
) 。
這樣的話 10rem 就剛好等於瀏覽器窗口的寬度,也就可以保證 100% 寬度,等比例縮放設計稿的頁面了。
因此 rem + vw vh 方案要解決三件事
- 獲得 rem 的基準值;
- 頁面內寫一段 js 代碼,動態的計算
html根元素的font-size
; - 屏幕變化後,圖表自動調整和圖表字體、間距、位移等的自適應。
實現方案
第一點:獲得 rem 的基準值
- 首先安裝
@njleonzhang/postcss-px-to-rem
這個包
npm i @njleonzhang/postcss-px-to-rem -D
- 在項目根目錄新建
.postcssrc.js
配置文件
module.exports = { plugins: { autoprefixer: {}, "@njleonzhang/postcss-px-to-rem": { unitToConvert: 'px', // (String) 要轉換的單位,默認是 px。 widthOfDesignLayout: 1920, // (Number) 設計佈局的寬度。對於pc儀表盤,一般是 1920. unitPrecision: 3, // (Number) 允許 rem 單位增長到的十進制數字. selectorBlackList: ['.ignore', '.hairlines'], // (Array) 要忽略並保留爲 px 的選擇器. minPixelValue: 1, // (Number) 設置要替換的最小像素值. mediaQuery: false // (Boolean) 允許在媒體查詢中轉換 px. } } }
- 配置完成後,頁面內的 px 就會被轉換成 rem 了
第二點:動態的計算html根元素的font-size
- 在工具函數文件中新建一個 rem.js 文件,用於動態計算 font-size
(function init(screenRatioByDesign = 16 / 9) { let docEle = document.documentElement function setHtmlFontSize() { var screenRatio = docEle.clientWidth / docEle.clientHeight; var fontSize = ( screenRatio > screenRatioByDesign ? (screenRatioByDesign / screenRatio) : 1 ) * docEle.clientWidth / 10; docEle.style.fontSize = fontSize.toFixed(3) + "px"; console.log(docEle.style.fontSize); } setHtmlFontSize() window.addEventListener('resize', setHtmlFontSize) })()
- 在入口文件 main.js 中引入 rem.js 文件
import './utils/rem.js';
至此,頁面就已經可以實現 16:9 自適應了。
第三點:屏幕變化,圖表自適應
屏幕變化後,圖表自動調整字體、間距、位移等,此處參考上面 vw vh 的實現方式即可,在此就不重複贅述了
參考資料
-
推薦一個echarts 的案列網站,需要什麼直接圖表直接在上面去找,可以省去很多查 echarts 配置的時間
全網echarts案例資源大總結和echarts的高效使用技巧(細節版) -
scale 方案參考: 數據大屏最簡單自適應方案,無需適配rem單位
-
vw vh 方案參考: Vue+Echarts企業級大屏項目適配方案
-
rem 方案參考:數據大屏rem適配方案