数据大屏最简单适配方案
根据本文内容,开发了以下三个
npm
包,希望大家能用得到
- @fit-screen/shared: 提供计算自适应比例相关内容的工具包
- @fit-screen/vue:Vue 自适应组件
- @fit-screen/react:React 自适应组件
如果本文对你有帮助,希望大佬能给个 star~
前言
最近公司有个大屏的项目,之前没咋接触过。
就在掘金上看了许多大佬各种方案,最常见的方案无外乎一下 3 种👇,优缺点呢也比较明显
方案 | 实现方式 | 优点 | 缺点 |
---|---|---|---|
vw, vh | 按照设计稿的尺寸,将px 按比例计算转为vw 和vh | 1.可以动态计算图表的宽高,字体等,灵活性较高 2.当屏幕比例跟 ui 稿不一致时,不会出现两边留白情况 | 1.需要编写公共转换函数,为每个图表都单独做字体、间距、位移的适配,比较麻烦 |
scale | 通过 scale 属性,根据屏幕大小,对图表进行整体的等比缩放 | 1.代码量少,适配简单 2.一次处理后不需要在各个图表中再去单独适配 | 1.因为是根据 ui 稿等比缩放,当大屏跟 ui 稿的比例不一样时,会出现周边留白情况 2.当缩放比例过大时候,字体和图片会有一点点失真. 3.当缩放比例过大时候,事件热区会偏移。 |
rem + vw vh | 1.获得 rem 的基准值 2.动态的计算 html根元素的font-size 3.图表中通过 vw vh 动态计算字体、间距、位移等 | 1.布局的自适应代码量少,适配简单 | 1.因为是根据 ui 稿等比缩放,当大屏跟 ui 稿的比例不一样时,会出现周边留白情况 2.图表需要单个做字体、间距、位移的适配 |
这 3 种方案中,最简单的也最容易抽离为下次使用的当属 scale
方案了。
它优点是:
- 代码量少,编写公共组件,套用即可,可以做到一次编写,任何地方可用,无需重复编写。
- 使用
flex
grid
百分比 还有position
定位或者完全按照设计稿的px
单位进行布局,都可以,不需要考虑单位使用失误导致适配不完全。实现数据大屏在任何分辨率的电脑上均可安然运作。
至于说缺点:
比例不一样的时候,会存在留白,开发大屏基本上都是为对应分辨率专门开发,我觉得这个缺点可以基本忽略,因为我们可以将背景色设置为大屏的基础色,这样留白部分不是太大基本没影响啦,哈哈
关于失真,失真 是在你设置的 分辨率比例 与 屏幕分辨率比例 不同的情况下,依然采用 铺满全屏 出现 拉伸 的时候,才会出现,正常是不会出现的。
电视看电影比例不对,不也会出现上下黑边吗,你设置拉伸,他也会失真,是一个道理
🚀 开发
让我们先来看下效果吧!👇
既然选择了 scale
方案,那么我们来看看它的原理,以及如何实现吧!
原理
scale
方案是通过 css 的 transform 的 scale 属性来进行一个 等比例缩放 来实现屏幕适配的,既然如此我们要知道一下几个前提:
- 设设计稿的 宽高比 为 1,则在任意显示屏中,只要展示内容的容器的 宽高比 也是 1,则二者为 1:1 只要 等比缩放/放大 就可以做到完美展示并且没有任何白边。
- 如果设计稿的 宽高比 为 1, 而展示容器 宽高比 不是
1
的时候,则存在两种情况。- 宽高比大于 1,此时宽度过长,计算时基准值采用高度,计算出维持 1 宽高比的宽度。
- 宽高比小于 1,此时高度过长,计算时基准值采用宽度,计算出维持 1 宽高比的高度。
代码实现
有了以上前提,我们可以得出以下代码
const el = document.querySelector('#xxx')
// * 需保持的比例
const baseProportion = parseFloat((width / height).toFixed(5))
// * 当前屏幕宽高比
const currentRate = parseFloat((window.innerWidth / window.innerHeight).toFixed(5))
const scale = {
widthRatio: 1,
heightRatio: 1,
}
// 宽高比大,宽度过长
if(currentRate > baseProportion) {
// 求出维持比例需要的宽度,进行计算得出宽度对应比例
scale.widthRatio = parseFloat(((window.innerHeight * baseProportion) / baseWidth).toFixed(5))
// 得出高度对应比例
scale.heightRatio = parseFloat((window.innerHeight / baseHeight).toFixed(5))
}
// 宽高比小,高度过长
else {
// 求出维持比例需要的高度,进行计算得出高度对应比例
scale.heightRatio = parseFloat(((window.innerWidth / baseProportion) / baseHeight).toFixed(5))
// 得出宽度比例
scale.widthRatio = parseFloat((window.innerWidth / baseWidth).toFixed(5))
}
// 设置等比缩放或者放大
el.style.transform = `scale(${scale.widthRatio}, ${scale.heightRatio})`
OK,搞定了。
哇!这也太简单了吧。
好,为了下次一次编写到处使用,我们对它进行封装,然后集成到我们常用的框架中,作为通用组件
function useFitScreen(options) {
const {
// * 画布尺寸(px)
width = 1920,
height = 1080,
el
} = options
// * 默认缩放值
let scale = {
widthRatio: 1,
heightRatio: 1,
}
// * 需保持的比例
const baseProportion = parseFloat((width / height).toFixed(5))
const calcRate = () => {
if (el) {
// 当前比例
const currentRate = parseFloat((window.innerWidth / window.innerHeight).toFixed(5))
// 比例越大,则越宽,基准值采用高度,计算出宽度
// 反之,则越高,基准值采用宽度,计算出高度
scale = currentRate > baseProportion
? calcRateByHeight(width, height, baseProportion)
: calcRateByWidth(width, height, baseProportion)
}
el.style.transform = `scale(${scale.widthRatio}, ${scale.heightRatio})`
}
// * 改变窗口大小重新绘制
const resize = () => {
window.addEventListener('resize', calcRate)
}
// * 改变窗口大小重新绘制
const unResize = () => {
window.removeEventListener('resize', calcRate)
}
return {
calcRate,
resize,
unResize,
}
}
其实一个基本的共用方法已经写好了,但是我们实际情况中,有可能会出现奇怪比例的大屏。
例如:
- 超长屏,我们需要 x 轴滚动条。
- 超高屏,我们需要 y 轴滚动条。
- 还有一种情况,比如需要占满屏幕,不需要留白,适当拉伸失真也无所谓的情况呢。
所以,我们需要进行扩展这个方法,像 节流 节约性能,对上面是那种情况做适配等,文章篇幅有限,源码已经开源并且工具包已经上传了 npm
需要的可以去看源码或者下载使用
集成到 Vue
通过以上的的原理和工具包实现,接下来我们接入 Vue 将会变得非常简单了,只需要我们用 Vue 的 ref 将对应的 dom 元素提供给工具包,就可以实现啦~
不过在这个过程中我遇到的问题是,既然是一次编写,任意使用,我们需要集成 Vue2 和 Vue3,如何做呢?
说道这一点想必各位大佬也知道我要用什么了吧,那就是偶像 Anthony Fu 的 vueuse
中使用的插件 vue-demi
。
好的,开发完毕之后,一样将它上传到 npm
,这样以后就可以直接下载使用了
大家也可以这样使用
npm install @fit-screen/vue @vue/composition-api
# or
yarn add @fit-screen/vue @vue/composition-api
# or
pnpm install @fit-screen/vue @vue/composition-api
当做全局组件使用
// In main.[jt]s
import { createApp } from 'vue'
import FitScreen from '@fit-screen/vue'
import App from './App.vue'
const app = createApp(App)
app.use(FitScreen)
app.mount('#app')
Use in any component
<template>
<FitScreen :width="1920" :height="1080" mode="fit">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo">
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo">
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</FitScreen>
</template>
在 SFC 中单独使用
<script setup>
import FitScreen from '@fit-screen/vue'
</script>
<template>
<FitScreen :width="1920" :height="1080" mode="fit">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo">
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo">
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</FitScreen>
</template>
集成到 React
集成到 React 也是完全没毛病,而且好像更简单,不存在 vue2 和 vue3 这样版本兼容问题
大佬们可以这样使用:
npm install @fit-screen/react
# or
yarn add @fit-screen/react
# or
pnpm install @fit-screen/react
import { useState } from 'react'
import FitScreen from '@fit-screen/react'
function App() {
const [count, setCount] = useState(0)
return (
<FitScreen width={1920} height={1080} mode="fit">
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank" rel="noreferrer">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank" rel="noreferrer">
React logo
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount(count => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
</FitScreen>
)
}
export default App
结尾
- 通过工具包可以在无框架和任意前端框架中开发自己的组件,比如说 Svelte,我也做了一个 Svelte 的版本示例,可以去 示例仓库 中查看。
- 目前就开发了 Vue 和 React 版本的自适应方案,大家可以根据需要进行使用。
感谢大家的阅读,希望大家能用得上,并且给上 star~
来源:juejin.cn/post/7202598910337138748