前端大屏常用的适配方案

2023-11-20

假设我们正在开发一个可视化拖拽的搭建平台,可以拖拽生成工作台或可视化大屏,或者直接就是开发一个大屏,首先必须要考虑的一个问题就是页面如何适应屏幕,因为我们在搭建或开发时一般都会基于一个固定的宽高,但是实际的屏幕可能大小不一,接下来我们就尝试几种简单且常见的方案,并简单分析一下利弊。

demo

首先写个基础的demo给后续使用:

<script setup>
import { ref } from "vue";
import Widget from "./components/Widget.vue";
import LineChart from "./components/LineChart.vue";
import BarChart from "./components/BarChart.vue";
import PieChart from "./components/PieChart.vue";
import FunnelChart from "./components/FunnelChart.vue";

// 画布宽高
const canvasWidth = ref(1920);
const canvasHeight = ref(1080);
// 组件宽高
const widgetWidth = ref(960);
const widgetHeight = ref(540);
</script>

<template>
  <div class="canvasBox">
    <div
      class="canvas"
      :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
    >
      <Widget :width="widgetWidth" :height="widgetHeight" :left="0" :top="0">
        <LineChart></LineChart>
      </Widget>
      <Widget :width="widgetWidth" :height="widgetHeight" :left="widgetWidth" :top="0">
        <BarChart></BarChart>
      </Widget>
      <Widget :width="widgetWidth" :height="widgetHeight" :left="0" :top="widgetHeight">
        <PieChart></PieChart>
      </Widget>
      <Widget :width="widgetWidth" :height="widgetHeight" :left="widgetWidth" :top="widgetHeight">
        <FunnelChart></FunnelChart>
      </Widget>
    </div>
  </div>
</template>

<style scoped>
.canvasBox {
  width: 100vw;
  height: 100vh;
}
.canvas {
  position: relative;
}
</style>

每个图表组件的宽高都设为100%,然后都被Widget组件包裹,所以实际宽高是依赖Widget组件的,Widget组件为绝对定位,并且宽高、位置通过props传入,模拟我们的拖拽操作,简单起见,所有图表的宽高我们都设为了相同的。

Widget组件:

<script setup>
const props = defineProps({
  width: {
    type: Number,
    default: 0,
  },
  height: {
    type: Number,
    default: 0,
  },
  left: {
    type: Number,
    default: 0,
  },
  top: {
    type: Number,
    default: 0,
  },
});
</script>

<template>
  <div
    class="widgetBox"
    :style="{
      width: width + 'px',
      height: height + 'px',
      left: left + 'px',
      top: top + 'px',
    }"
  >
    <slot></slot>
  </div>
</template>

<style scoped>
.widgetBox {
  position: absolute;
}
</style>

组件整体的容器为类名为canvas的元素,相对定位,宽高也是动态设置的,canvas元素的父级canvasBox元素宽高设为和屏幕宽高一致。

固定尺寸

即宽度、高度固定,如果宽高小于屏幕宽高则在屏幕居中。

这个是最简单的方案了,相当于不适配屏幕,画布配置了多大实际就是多大,不随屏幕的变化而变化,所以各个组件的宽高也是在配置后不会改变,一般用于尺寸固定且后期不会改变的可视化大屏。

我们前面的demo初始就是这种方式:

当然,如果宽高小于屏幕的话居中的逻辑需要加一下,居中的方法有很多,通过cssjs都可,根据自己的喜好来就行:

// 画布的位置
const canvasLeft = ref(0);
const canvasTop = ref(0);
// 如果屏幕的宽或高比画布的大,那么居中显示
let windowWidth = window.innerWidth;
let windowHeight = window.innerHeight;
if (windowWidth > canvasWidth.value) {
    canvasLeft.value = (windowWidth - canvasWidth.value) / 2;
}
if (windowHeight > canvasHeight.value) {
    canvasTop.value = (windowHeight - canvasHeight.value) / 2;
}

<div
      class="canvas"
      :style="{
        width: canvasWidth + 'px',
        height: canvasHeight + 'px',
        left: canvasLeft + 'px',
        top: canvasTop + 'px',
      }"
    >
</div>

判断窗口宽度和高度是否大于画布的宽高,是的话通过lefttop来调整:

自适应宽度

即宽度适应屏幕,高度不变,这种方案的缺点是垂直方向上会出现滚动条。

比如画布设置的宽度为1920,但是实际上屏幕的宽度为1280,那么缩小了1.5倍,那么画布和每个组件的宽度也需要同步缩小1.5倍,并且每个组件的left值也需要进行动态调整。

首先实现一下容器元素canvas的尺寸调整:

// 保存原始画布的宽度
const originCanvasWidth = ref(canvasWidth.value);
// 宽度缩放比例
const ratioWidth = ref(1);

// 当前窗口的宽度
let windowWidth = window.innerWidth;
// 将画布宽度设置为当前窗口的宽度
canvasWidth.value = windowWidth;
// 计算当前宽度和原始宽度的比例
ratioWidth.value = windowWidth / originCanvasWidth.value;

然后再把这个比例传给Widget组件进行调整:

<Widget :ratioWidth="ratioWidth">
    <LineChart></LineChart>
</Widget>
<Widget :ratioWidth="ratioWidth">
    <BarChart></BarChart>
</Widget>
<Widget :ratioWidth="ratioWidth">
    <PieChart></PieChart>
</Widget>
<Widget :ratioWidth="ratioWidth">
    <FunnelChart></FunnelChart>
</Widget>

Widget组件里我们只要把宽度和left都乘以这个比例即可,为什么是乘,很简单:

newWidth / width = ratioWidth = windowWidth / originCanvasWidth
newWidth = width * ratioWidth

// left同样看做是一个距左侧的宽度即可

<div
    class="widgetBox"
    :style="{
      width: width * ratioWidth + 'px',
      height: height + 'px',
      left: left * ratioWidth + 'px',
      top: top + 'px',
    }"
  >
    <slot></slot>
</div>

自适应屏幕

即宽高都自适应,和上一种方案相比,这种横竖都不会出现滚动条,且能完全铺满屏幕。

实现也很简单,在上一个【自适应宽度】的基础上加上高度自适应即可。

// 画布原始宽高
const originCanvasWidth = ref(canvasWidth.value);
const originCanvasHeight = ref(canvasHeight.value);
// 缩放比例
const ratioWidth = ref(1);
const ratioHeight = ref(1);

// 当前窗口的宽高
let windowWidth = window.innerWidth;
let windowHeight = window.innerHeight;
// 将画布宽高设置为当前窗口的宽高
canvasWidth.value = windowWidth;
canvasHeight.value = windowHeight;
// 计算当前宽高和原始宽高的比例
ratioWidth.value = windowWidth / originCanvasWidth.value;
ratioHeight.value = windowHeight / originCanvasHeight.value;

同样再把比例传给Widget组件进行调整:

<Widget :ratioWidth="ratioWidth" :ratioHeight="ratioHeight">
    <LineChart></LineChart>
</Widget>
<Widget :ratioWidth="ratioWidth" :ratioHeight="ratioHeight">
    <BarChart></BarChart>
</Widget>
<Widget :ratioWidth="ratioWidth" :ratioHeight="ratioHeight">
    <PieChart></PieChart>
</Widget>
<Widget :ratioWidth="ratioWidth" :ratioHeight="ratioHeight">
    <FunnelChart></FunnelChart>
</Widget>

<div
    class="widgetBox"
    :style="{
      width: width * ratioWidth + 'px',
      height: height * ratioHeight + 'px',
      left: left * ratioWidth + 'px',
      top: top * ratioHeight + 'px',
    }"
  >
    <slot></slot>
</div>

整体等比例缩放

即通过csstransform属性来对组件容器canvas进行整体的缩放,保持原比例,在屏幕居中显示,当然你可以选择只缩放宽度或高度,但是这样会变形。

前面的两种方案,我们的组件开发时都必须要考虑容器的宽高,即需要进行适配,但是宽高比太极限了说实话很难处理,显示效果肯定是比较差的,但是这种整体等比例适配就无需考虑这种情况。

实际项目中如果有大屏需要适应屏幕,我一般都通过这种方法,优点是简单,缺点是水平或垂直空间上可能会留白,但是背景是全屏的,所以效果也不会很差。

实现也很简单,计算一下画布原始比例,再计算一下屏幕的比例,然后再判断是宽度和屏幕一致,高度自适应,还是高度和屏幕一致,宽度自适应:

// 当前窗口宽高比例
let windowWidth = window.innerWidth;
let windowHeight = window.innerHeight;
let windowRatio = windowWidth / windowHeight;
// 画布原始宽高比例
const canvasRatio = canvasWidth.value / canvasHeight.value;
// 计算画布适应后的新宽高
let newCanvasWidth = 0;
let newCanvasHeight = 0;
if (canvasRatio > windowRatio) {// 画布的宽高比大于屏幕的宽高比
    // 画布的宽度调整为屏幕的宽度
    newCanvasWidth = windowWidth;
    // 画布的高度根据画布原比例进行缩放
    newCanvasHeight = windowWidth / canvasRatio;
} else {// 画布的宽高比小于屏幕的宽高比
    // 画布的高度调整为屏幕的高度
    newCanvasHeight = windowHeight;
    // 画布的宽度根据画布原比例进行缩放
    newCanvasWidth = windowHeight * canvasRatio;
}
// ...

假设屏幕的宽高相同,那么比例为1

第一种情况,假设画布的宽是高的两倍,那么比例为2,要保持原比例2适应屏幕,显然只能宽度和屏幕一致,高度自适应,因为如果高度和屏幕一致,那么宽度需要是高度的两倍,屏幕显然显示不下:

第二种情况,假设画布的高是宽的两倍,那么比例为0.5,要保持比例为0.5适应屏幕,需要高度和屏幕一致,宽度自适应:

计算完了画布适应屏幕后的新宽高,接下来就可以计算它相对于画布原始宽高的缩放比例:

// ...
// 相对于画布原始宽高的缩放比例
const canvasStyle = reactive({
    transform: "",
});
const scaleX = newCanvasWidth / canvasWidth.value;
const scaleY = newCanvasHeight / canvasHeight.value;
canvasStyle.transform = `scale(${scaleX}, ${scaleY})`

把样式添加到容器元素canvas上即可:

<div
      class="canvas"
      :style="{
        width: canvasWidth + 'px',
        height: canvasHeight + 'px',
        ...canvasStyle
      }"
    >
</div>

显示的位置似乎有点问题,这其实是因为默认情况下元素的变换都是以自身的中心点为原点进行变换的:

我们只要改成以左上角为原点即可:

const canvasStyle = reactive({
  transform: "",
  transformOrigin: `left top`// 改成以左上角为变换原点
});

最后再来让它居中:

// 居中
const translateX = (windowWidth - newCanvasWidth) / 2 / scaleX;
const translateY = (windowHeight - newCanvasHeight) / 2 / scaleY;
canvasStyle.transform = `scale(${scaleX}, ${scaleY}) translate(${translateX}px, ${translateY}px)`;

窗口的宽高减去画布适应后的新宽高,即剩余的空间,再除以2进行居中显示,为什么还要除以缩放值呢,因为translate的值也会随scale进行缩放,比如translateX计算出来为100scaleX0.5,那么实际上最终的偏移量为100*0.5=50,这显然不对,所以我们除一个缩放值进行抵消。

这个方案似乎很完美,那么还有没有问题呢,显然是有的,一个小问题是缩放后文字可能会模糊,这个问题不大,笔者遇到的另一个问题是如果使用了getBoundingClientRect方法获取元素信息,本意是获取元素原始的尺寸数据,但是缩放后返回的就是缩放后的数据,那么可能会和我们的原始意图出现偏差,比如有一个如下的div

<div ref="el1" style="width: 200px; height: 200px; background: red; position: absolute; left: 50px; top: 50px;"></div>

我们想要动态根据这个div大小和位置复制一个div

<div ref="el2" style="background: green; position: absolute"></div>

const { width, height } = el1.value.getBoundingClientRect();
const { left, top } = window.getComputedStyle(el1.value);
el2.value.style.width = `${width}px`;
el2.value.style.height = `${height}px`;
el2.value.style.left = left;
el2.value.style.top = top;

可以看到获取到的宽高比实际的小了一点,这显然不是我们需要的,解决方法是要么不要使用getBoundingClientRect方法,使用offsetWdith等不会被缩放影响的方法或属性获取元素尺寸,要么把获取到的数据除以缩放值。

当然可能还会存在其他一些属性或方法也会存在这个问题,这就需要各位在实际的开发时进行测试了。

总结

本文简单总结了一下大屏适配的几种方法,没有哪一种是最好的,也没有哪一种是非常完美的,没办法,很多时候都是需要进行一定妥协的。

demo地址:Vite + Vue

demo仓库地址:GitHub - skyWangZhenDong/palteform-fit-demo: 大屏的自适应

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

前端大屏常用的适配方案 的相关文章

随机推荐

  • 小程序开发——微信外环境静态h5跳转小程序

    亲测有效 微信外环境静态h5跳转小程序
  • draw.io---一款免费、开源的流程图绘制工具

    draw io 是一款非常强大实用的免费 开源流程图绘制工具 功能众多 例如通过流程图来可视化您的工作流程 创建组织结构图来来显示人员结构 创建网络图来可视化网络等等 适用于商务 工程 电气 网络设计 软件设计等诸多领域 同时还内置了相当丰
  • ddos攻击怎么防?防御ddos的几种方法

    现在的互联网行业大多数企业基本上都依靠网络来获取流量和订单或者服务 但是却对网络的利弊知之甚少 特别是网站的较大威胁DDOS攻击 很多互联网企业基本都遭遇过DDOS攻击的勒索和恶性竞争 大部分企业网站一旦被DDOS攻击就立马挂了 从而影响了
  • Linux下printk输出

    由于Linux内核的级别控制 导致printk打印的内容不一定都能从控制台正常输出 可以使用dmesg命令来输出所有级别的信息 输入 sudo dmesg n 8 可以把所有日志信息输出来 Linux命令 dmesg 功能说明 显示开机信息
  • Tips系列之飞书日历

    最近收到很多小伙伴的问题 咨询飞书日历如何使用 今天就给大家呈上几个小Tips 让你更全面的了解飞书日历 免费使用 飞书官网 1 订阅日历 你可以订阅同事的日历 查看对方的忙闲状态 省去反复沟通的麻烦 快速找到空闲时间并邀约会议 如何订阅日
  • 卷积神经网络之-VGGNet

    更多内容请关注 机器视觉 CV 公众号 原文地址 VGGNet 是由牛津大学视觉几何小组 Visual Geometry Group VGG 提出的一种深层卷积网络结构 他们以7 32 的错误率赢得了2014年ILSVRC分类任务的亚军 冠
  • Alpha多样性之箱线图绘制

    Alpha多样性 通常用Richness Chao1 Shannon Simpson Dominance和Equitability等指数来评估样本的物种多样性 所以这个节学习主要分两个部分 1 计算 生物多样性指数 软件 R软件中vegan
  • 副业是刚需?分享几个程序员接外包私活的网站

    经常看到某某程序员接了个项目开发 工作之余轻轻松松赚了钱还顺带提升了技术 或者看到某大佬又发表了一篇程序员技术提升稿件 阅读点赞收藏三连发 这个月的零花钱又不愁了 但自己只是一名普普通通的程序员 能找到这样的副业吗 这些项目又在哪里接的呢
  • 网站为什么建议用https协议

    HTTPS 全称 Hyper Text Transfer Protocol over SecureSocket Layer 是以安全为目标的 HTTP 通道 在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 HTTPS 在HT
  • STM32f103增加ESP8266模块,通过Wifi用手机TCP服务端远程与STM32通信

    STM32f103增加ESP8266模块 通过Wifi用手机TCP服务端远程与STM32通信 提示 esp8266开启透传模式 连上路由器Wifi 电脑热点 接入STM32串口引脚 另外手机TCP服务端是手机IP地址 串口号 大于8000滴
  • 仅需三步就可以把代码块完美插入到word中

    还在为在word中呈现代码而烦恼吗 如何在word中插入c 代码 如何在word中插入代码块 如何在word中完美的呈现代码 如何在word中对代码进行排版 来开始表演了 1 打开网站 http word wd1x com 支持c c ja
  • keil提示找不到芯片库

    keil提示找不到芯片库 有几种可能 一种是没安装芯片软件包 下载相应芯片包即可 还有一种可能是工程本身是用keil4创建的 而你用的是keil5打开的 此种情况将后缀名uvproj改成uvprojx即可 图1
  • java获取method_从Method中获取完整类名和方法名

    需求假设 假设在包com zhoutao controller下有方法getKey 方法 在JavaEE中 通过AOP获得该方法的的对象method 现在通过该对象的getName方法 仅仅只能获得getKey的结果 现在我需要的是com
  • 性别年龄识别器

    心情 最近比较郁闷 自己的小论文已经投了快8个月了 从去年的AAAI CVPR 到现在在审的TCSVT AAAI由于页数超限被拒 CVPR因为一个正面和2个负面评价被拒 TCSVT还前途未卜 真的很郁闷 心情不好 论文也不太想看 撸撸代码放
  • Idea快速开发SpringBoot项目的方法

    一 常用插件 1 Free Mybatis plugins 针对Mybatis框架提供的插件 自动完成Mapper接口和Mapper xml之间的跳转 2 RestfulTool 自动显示工程中的所有URL接口 进行测试 并且提供检索功能
  • Elasticsearch(十三) ElasticSearch搜索附近的人

    需求 通过指定点搜索附近的人 要求可以过滤年龄 结果按照距离进行排序 并且展示她 他距离你多远 本文参考 es官网文档 https www elastic co guide cn elasticsearch guide current so
  • 面试题:StringBuilder 和 StringBuffer 区别(通俗易懂易背)

    StringBuffer是同步的 线程安全 StringBuilder不是 什么时候用哪一个 StringBuilder 当您需要一个可以修改的字符串 并且只有一个线程正在访问和修改它时 StringBuffer 当你需要一个可以修改的字符
  • sql: Compare Tables

    使用 UNION INTERSECT 或 EXCEPT 运算符合并的所有查询必须在其目标列表中有相同数目的表达式 select from BookInfoList 存在不同的 select BookInfoID BookInfoBarCod
  • VMware安装OpenEuler

    VMware安装OpenEuler 需要考虑的因素 安装系统的用途 桌面开发 服务器 Linux学习使用 磁盘分区规划 最小120G 划分 boot swap 根分区等 网络 是否对外提供服务 NAT模式 桥接模式 仅主机模式 1 下载镜像
  • 前端大屏常用的适配方案

    假设我们正在开发一个可视化拖拽的搭建平台 可以拖拽生成工作台或可视化大屏 或者直接就是开发一个大屏 首先必须要考虑的一个问题就是页面如何适应屏幕 因为我们在搭建或开发时一般都会基于一个固定的宽高 但是实际的屏幕可能大小不一 接下来我们就尝试