本文正在参与「金石计划 . 分割6万现金大奖」

假设咱们正在开发一个可视化拖拽的建立渠道,能够拖拽生成工作台或可视化大屏,或者直接便是开发一个大屏,首要必须要考虑的一个问题便是页面怎么习惯屏幕,由于咱们在建立或开发时一般都会依据一个固定的宽高,可是实践的屏幕或许巨细不一,接下来咱们就测验几种简略且常见的计划,并简略剖析一下利害。

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地址:wanglin2.github.io/visual-drag…

demo库房地址:github.com/wanglin2/vi…