Suppose we are developing a visual drag-and-drop construction platform, which can generate a workbench or a large-scale visualization screen by dragging and dropping, or directly develop a large-scale screen. One of the first issues that must be considered is how to adapt the page to the screen, because we are building or developing Generally, it is based on a fixed width and height, but the actual screen size may vary. Next, we will try several simple and common solutions, and briefly analyze the pros and cons.
demo
First write a basic one demo
for subsequent use:
<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>
The width and height of each chart component are set 100%
and then Widget
wrapped by the component, so the actual width and height depend on Widget
the component, Widget
the component is absolutely positioned, and the width, height, and position are passed props
in to simulate our drag and drop operation. For simplicity, We set the width and height of all charts to be the same.
Widget
Components:
<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>
The overall container of the component is the element with the class name canvas
, which is relatively positioned, and the width and height are also dynamically set. The width and height canvas
of the parent canvasBox
element of the element are set to be consistent with the screen width and height.
fixed size
That is, the width and height are fixed, and if the width and height are smaller than the screen width and height, it will be centered on the screen.
This is the simplest solution, which is equivalent to not fitting the screen. The size of the canvas configuration is actually the size, and does not change with the change of the screen. Therefore, the width and height of each component will not change after configuration. It is generally used for fixed size and A large visual screen that will not change later.
Our previous demo
initial is this way:
Of course, if the width and height are smaller than the screen, the logic of centering needs to be added. There are many methods of centering, both can be passed css
, js
and you can do it according to your preferences:
// 画布的位置
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>
Determine whether the width and height of the window are greater than the width and height of the canvas, if yes, adjust by left
or :top
adaptive width
That is, the width adapts to the screen, and the height remains unchanged. The disadvantage of this solution is that there will be scroll bars in the vertical direction.
For example, if the width of the canvas is set to 1920
, but the actual screen width is 1280
, 1.5
then the width of the canvas and each component needs to be reduced by 1.5
a factor of 2, and left
the value of each component also needs to be adjusted dynamically.
First implement canvas
the size adjustment of the container element:
// 保存原始画布的宽度
const originCanvasWidth = ref(canvasWidth.value);
// 宽度缩放比例
const ratioWidth = ref(1);
// 当前窗口的宽度
let windowWidth = window.innerWidth;
// 将画布宽度设置为当前窗口的宽度
canvasWidth.value = windowWidth;
// 计算当前宽度和原始宽度的比例
ratioWidth.value = windowWidth / originCanvasWidth.value;
Then pass this ratio to Widget
the component for adjustment:
<Widget :ratioWidth="ratioWidth">
<LineChart></LineChart>
</Widget>
<Widget :ratioWidth="ratioWidth">
<BarChart></BarChart>
</Widget>
<Widget :ratioWidth="ratioWidth">
<PieChart></PieChart>
</Widget>
<Widget :ratioWidth="ratioWidth">
<FunnelChart></FunnelChart>
</Widget>
In Widget
the component, we only left
need to multiply the width and sum by this ratio. Why is it multiplied? It's very simple:
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>
adaptive screen
That is, the width and height are both adaptive. Compared with the previous solution, there will be no scroll bars in this way, and it can completely cover the screen.
The implementation is also very simple, just add height adaptation on the basis of the previous [Adaptive Width].
// 画布原始宽高
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;
Also pass the ratio to Widget
the component for adjustment:
<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>
Overall scaling
That is, the component container is scaled as a whole through the attribute, keeping the original proportion, and displaying it in the center of the screen. Of course, you can choose to only scale the width or height, but this will css
deform transform
.canvas
For the previous two solutions, we must consider the width and height of the container when developing components, that is, we need to adapt, but the aspect ratio is too extreme to be honest, it is difficult to deal with, and the display effect is definitely relatively poor, but this kind of The overall proportional adaptation does not need to consider this situation.
In actual projects, if there is a large screen that needs to adapt to the screen, I usually use this method. The advantage is that it is simple, and the disadvantage is that there may be blank space in the horizontal or vertical space, but the background is full screen, so the effect will not be bad.
The implementation is also very simple. Calculate the original ratio of the canvas, then calculate the ratio of the screen, and then judge whether the width is consistent with the screen and the height is adaptive, or the height is consistent with the screen and the width is adaptive:
// 当前窗口宽高比例
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;
}
// ...
Assuming the screen has the same width and height, then the ratio is 1
.
In the first case, assuming that the width of the canvas is twice the height, then the ratio is 2
, to keep the original ratio 2
to fit the screen, obviously only the width is the same as the screen, and the height is adaptive, because if the height is the same as the screen, then the width needs to be the height Twice the size, the screen obviously cannot display:
In the second case, assuming that the height of the canvas is twice the width, then the ratio is 0.5
, to keep the ratio to 0.5
fit the screen, the height needs to be consistent with the screen, and the width is adaptive:
After calculating the new width and height of the canvas adapted to the screen, you can then calculate its scaling ratio relative to the original width and height of the canvas:
// ...
// 相对于画布原始宽高的缩放比例
const canvasStyle = reactive({
transform: "",
});
const scaleX = newCanvasWidth / canvasWidth.value;
const scaleY = newCanvasHeight / canvasHeight.value;
canvasStyle.transform = `scale(${
scaleX}, ${
scaleY})`
Just add the style canvas
to the container element:
<div
class="canvas"
:style="{
width: canvasWidth + 'px',
height: canvasHeight + 'px',
...canvasStyle
}"
>
</div>
The displayed position seems to be a bit problematic. This is actually because by default, the transformation of the element is transformed with its own center point as the origin:
We just need to change the upper left corner as the origin:
const canvasStyle = reactive({
transform: "",
transformOrigin: `left top`// 改成以左上角为变换原点
});
Finally let's center it:
// 居中
const translateX = (windowWidth - newCanvasWidth) / 2 / scaleX;
const translateY = (windowHeight - newCanvasHeight) / 2 / scaleY;
canvasStyle.transform = `scale(${
scaleX}, ${
scaleY}) translate(${
translateX}px, ${
translateY}px)`;
The width and height of the window minus the new width and height after the canvas is adapted, that is, the remaining space, and then divided by the 2
centered display, why should it be divided by the zoom value, because translate
the value of will also scale
be scaled accordingly, for example, translateX
calculated as 100
, scaleX
for 0.5
, then the actual final offset is 100*0.5=50
, which is obviously wrong, so we divide a scaling value to offset.
This solution seems to be perfect, so is there any problem? Obviously, there is a small problem that the text may be blurred after zooming, which is not a big problem. Another problem I encountered is that if the method is used to obtain element information getBoundingClientRect
, The original intention is to get the original size data of the element, but after zooming, the zoomed data is returned, so it may deviate from our original intention, for example, there is one as follows div
:
<div ref="el1" style="width: 200px; height: 200px; background: red; position: absolute; left: 50px; top: 50px;"></div>
We want to dynamically div
make a copy based on this size and position 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;
It can be seen that the obtained aspect ratio is a little smaller than the actual one, which is obviously not what we need. The solution is to either not use the getBoundingClientRect
method, use offsetWdith
a method or attribute that will not be affected by scaling to obtain the element size, or use the acquired Data divided by the scaling value.
Of course, there may be other attributes or methods that also have this problem, which requires you to test it during actual development.
Summarize
This article briefly summarizes several methods of large-screen adaptation, none of which is the best, and no one is perfect. There is no way, and in many cases a certain compromise is required.
demo
Address: https://wanglin2.github.io/visual-drag-platform-fit-demo/
demo
Warehouse address: https://github.com/wanglin2/visual-drag-platform-fit-demo