数据大屏最简单适配方案

34,345 阅读7分钟

根据本文内容,开发了以下三个 npm 包,希望大家能用得到

  1. @fit-screen/shared: 提供计算自适应比例相关内容的工具包
  2. @fit-screen/vue:Vue 自适应组件
  3. @fit-screen/react:React 自适应组件

如果本文对你有帮助,希望大佬能给个 star~

前言

最近公司有个大屏的项目,之前没咋接触过。

就在掘金上看了许多大佬各种方案,最常见的方案无外乎一下 3 种👇,优缺点呢也比较明显

方案实现方式优点缺点
vw, vh按照设计稿的尺寸,将px按比例计算转为vwvh1.可以动态计算图表的宽高,字体等,灵活性较高
2.当屏幕比例跟 ui 稿不一致时,不会出现两边留白情况
1.需要编写公共转换函数,为每个图表都单独做字体、间距、位移的适配,比较麻烦
scale通过 scale 属性,根据屏幕大小,对图表进行整体的等比缩放1.代码量少,适配简单
2.一次处理后不需要在各个图表中再去单独适配
1.因为是根据 ui 稿等比缩放,当大屏跟 ui 稿的比例不一样时,会出现周边留白情况
2.当缩放比例过大时候,字体和图片会有一点点失真.
3.当缩放比例过大时候,事件热区会偏移。
rem + vw vh1.获得 rem 的基准值
2.动态的计算html根元素的font-size
3.图表中通过 vw vh 动态计算字体、间距、位移等
1.布局的自适应代码量少,适配简单1.因为是根据 ui 稿等比缩放,当大屏跟 ui 稿的比例不一样时,会出现周边留白情况
2.图表需要单个做字体、间距、位移的适配

这 3 种方案中,最简单的也最容易抽离为下次使用的当属 scale 方案了。

它优点是:

  1. 代码量少,编写公共组件,套用即可,可以做到一次编写,任何地方可用,无需重复编写。
  2. 使用 flex grid 百分比 还有 position 定位或者完全按照设计稿的 px 单位进行布局,都可以,不需要考虑单位使用失误导致适配不完全。实现数据大屏在任何分辨率的电脑上均可安然运作。

至于说缺点:

  1. 比例不一样的时候,会存在留白,开发大屏基本上都是为对应分辨率专门开发,我觉得这个缺点可以基本忽略,因为我们可以将背景色设置为大屏的基础色,这样留白部分不是太大基本没影响啦,哈哈

  2. 关于失真失真 是在你设置的 分辨率比例屏幕分辨率比例 不同的情况下,依然采用 铺满全屏 出现 拉伸 的时候,才会出现,正常是不会出现的。

    电视看电影比例不对,不也会出现上下黑边吗,你设置拉伸,他也会失真,是一个道理

🚀 开发

让我们先来看下效果吧!👇

既然选择了 scale 方案,那么我们来看看它的原理,以及如何实现吧!

原理

scale 方案是通过 css 的 transform 的 scale 属性来进行一个 等比例缩放 来实现屏幕适配的,既然如此我们要知道一下几个前提:

  1. 设设计稿的 宽高比1,则在任意显示屏中,只要展示内容的容器的 宽高比 也是 1,则二者为 1:1 只要 等比缩放/放大 就可以做到完美展示并且没有任何白边。
  2. 如果设计稿的 宽高比1, 而展示容器 宽高比 不是 1 的时候,则存在两种情况。
    1. 宽高比大于 1,此时宽度过长,计算时基准值采用高度,计算出维持 1 宽高比的宽度。
    2. 宽高比小于 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,
  }
}

其实一个基本的共用方法已经写好了,但是我们实际情况中,有可能会出现奇怪比例的大屏。

例如:

  1. 超长屏,我们需要 x 轴滚动条。
  2. 超高屏,我们需要 y 轴滚动条。
  3. 还有一种情况,比如需要占满屏幕,不需要留白,适当拉伸失真也无所谓的情况呢。

所以,我们需要进行扩展这个方法,像 节流 节约性能,对上面是那种情况做适配等,文章篇幅有限,源码已经开源并且工具包已经上传了 npm 需要的可以去看源码或者下载使用

  • 工具包源码:使用文档在这里,希望大佬们给一个小小的 star~
  • 工具包NPM: 你可以通过 npm install @fit-screen/shared 下载使用

Vue logo 集成到 Vue

通过以上的的原理和工具包实现,接下来我们接入 Vue 将会变得非常简单了,只需要我们用 Vue 的 ref 将对应的 dom 元素提供给工具包,就可以实现啦~

不过在这个过程中我遇到的问题是,既然是一次编写,任意使用,我们需要集成 Vue2 和 Vue3,如何做呢?

说道这一点想必各位大佬也知道我要用什么了吧,那就是偶像 Anthony Fuvueuse 中使用的插件 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 logo 集成到 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

结尾

  1. 通过工具包可以在无框架和任意前端框架中开发自己的组件,比如说 Svelte,我也做了一个 Svelte 的版本示例,可以去 示例仓库 中查看。
  2. 目前就开发了 Vue 和 React 版本的自适应方案,大家可以根据需要进行使用。

感谢大家的阅读,希望大家能用得上,并且给上 star~