设我们正在开发一个可视化拖拽的搭建平台,可以拖拽生成工作台或可视化大屏,或者直接就是开发一个大屏,首先必须要考虑的一个问题就是页面如何适应屏幕,因为我们在搭建或开发时一般都会基于一个固定的宽高,但是实际的屏幕可能大小不一,接下来我们就尝试几种简单且常见的方案,并简单分析一下利弊。
首先写个基础的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初始就是这种方式:
当然,如果宽高小于屏幕的话居中的逻辑需要加一下,居中的方法有很多,通过css、js都可,根据自己的喜好来就行:
// 画布的位置
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>判断窗口宽度和高度是否大于画布的宽高,是的话通过left或top来调整:
即宽度适应屏幕,高度不变,这种方案的缺点是垂直方向上会出现滚动条。
比如画布设置的宽度为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>即通过css的transform属性来对组件容器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计算出来为100,scaleX为0.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地址:https://wanglin2.github.io/visual-drag-platform-fit-demo/
demo仓库地址:https://github.com/wanglin2/visual-drag-platform-fit-demo
信很多自学的小伙伴都想学习web前端,可以关注小编后私信【学习】可以免费领取学习地址/案例教程/2018最新的一套学习教程,让学习有方向。
1、三角形列表项目符号
ul {
margin: 0.75em 0;
padding: 0 1em;
list-style: none;
}
li:before {
content: "";
border-color: transparent #111;
border-style: solid;
border-width: 0.35em 0 0.35em 0.45em;
display: block;
height: 0;
width: 0;
left: -1em;
top: 0.9em;
position: relative;
}
2、外部CSS3 盒阴影
#mydiv {
-webkit-box-shadow: 0 2px 2px -2px rgba(0, 0, 0, 0.52);
-moz-box-shadow: 0 2px 2px -2px rgba(0, 0, 0, 0.52);
box-shadow: 0 2px 2px -2px rgba(0, 0, 0, 0.52);
}
3、内部CSS3 盒阴影
#mydiv {
-moz-box-shadow: inset 2px 0 4px #000;
-webkit-box-shadow: inset 2px 0 4px #000;
box-shadow: inset 2px 0 4px #000;
}
4、CSS3 斑马线
tbody tr:nth-child(odd) {
background-color: #ccc;
}
5、强制出现垂直滚动条
html { height: 101% }
6、内容垂直居中
.container {
min-height: 6.5em;
display: table-cell;
vertical-align: middle;
}
7、CSS3:全屏背景
html {
background: url('images/bg.jpg') no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
8、锚链接伪类
a:link { color: blue; }
a:visited { color: purple; }
a:hover { color: red; }
a:active { color: yellow; }
9、为logo隐藏H1
h1 {
text-indent: -9999px;
margin: 0 auto;
width: 320px;
height: 85px;
background: transparent url("images/logo.png") no-repeat scroll;
}
10、自定义文本选择
::selection { background: #e2eae2; }
::-moz-selection { background: #e2eae2; }
::-webkit-selection { background: #e2eae2; }
11、跨浏览器的透明
.transparent {
filter: alpha(opacity=50); /* internet explorer */
-khtml-opacity: 0.5; /* khtml, old safari */
-moz-opacity: 0.5; /* mozilla, netscape */
opacity: 0.5; /* fx, safari, opera */
}
12、新版清除浮动(2011)
.clearfix:before, .container:after { content: ""; display: table; }
.clearfix:after { clear: both; }
/* IE 6/7 */
.clearfix { zoom: 1; }
13、典型的CSS清除浮动
.clearfix:after {
content: ".";
display: block;
clear: both;
visibility: hidden;
line-height: 0;
height: 0;
}
.clearfix { display: inline-block; }
html[xmlns] .clearfix { display: block; }
* html .clearfix { height: 1%; }
14、制造模糊文本
想要让文本模糊?可以使用color透明和text-shadow实现
.blurry-text {
color: transparent;
text-shadow: 0 0 5px rgba(0,0,0,0.5);
}
15、CSS:表格列宽自适用
对于表格,当谈到调整列宽时,是比较痛苦的。然后,这里有一个可以使用的技巧:给td元素添加white-space: nowrap;能让文本正确的换行
td {
white-space: nowrap;
}
端开发过程中,尺寸单位是我们必须用到的,下面我们对css中常见的几种尺寸单位px,em,rem,rpx进行逐一介绍
在这之前,需要先对几个概念进行普及介绍
基本概念
(以下概念读起来可能有些晦涩,如果看不懂也没关系)
像素
它不是自然界的物理长度,指基本原色素及其灰度的基本编码。
css中的像素只是一个抽象的单位,在不同的设备或不同的环境中,css中的1px所代表的设备物理像素是不同的。
在为桌面浏览器设计的网页中,我们无需对这个津津计较,但在移动设备上,必须弄明白这点。
在早先的移动设备中,屏幕像素密度都比较低,如iphone3,它的分辨率为320x480,在iphone3上,一个css像素确实是等于一个屏幕物理像素的。
后来随着技术的发展,移动设备的屏幕像素密度越来越高,从iphone4开始,苹果公司便推出了所谓的Retina屏,分辨率提高了一倍,变成640x960,但屏幕尺寸却没变化,这就意味着同样大小的屏幕上,像素却多了一倍,这时,一个css像素是等于两个物理像素的。其他品牌的移动设备也是这个道理。
物理像素
它是显示器(电脑、手机屏幕)最小的物理显示单位,物理像素指的是显示器上最小的点。物理像素的大小取决于屏幕。是一个无法改变的属性。
设备独立像素
我上一张图,你就会理解什么是设备独立像素
就是我们开发过程中使用的css中的px
设备像素比(device pixel radio)
设备像素比=物理像素 / 设备独立像素,单位是dpr!(device pixel radio)
Retina屏幕
所谓“Retina”是一种显示标准,是把更多的像素点压缩至一块屏幕里,从而达到更高的分辨率并提高屏幕显示的细腻程度。也被称为视网膜显示屏 ——百度百科
因为Retina屏幕的出现,在pc端默认情况下,css中的1px等于1物理像素,但在移动端1px不一定等于1物理像素,比如说iPhone的设备独立像素是375 667,因为它使用了Retina屏幕,他的dpr是2,所以iPhone 6 的物理像素为 750 1334
在不同的屏幕上(普通屏幕 vs retina屏幕),css中1px所呈现的大小(物理尺寸)是一致的,不同的是1px所对应的物理像素个数是不一致的。
在普通屏幕下,1px 对应 1个物理像素(1:1)。 在Retina屏幕下(dpr=2),1px对应 2x2个物理像素(1:4)。
你会发现,在移动端开发中使用了图片(img标签),2倍图要比1倍图清晰,就是这个缘故
px
默认情况下像素px是相对于屏幕分辨率而言,比如说我们的屏幕分辨率是1440 X 900,说的就是像素1440px X 900px;
这里会遇到另一种情况
### 浏览器缩放
缩放是缩放CSS像素(缩放比例为1时,一个CSS像素等于一个屏幕像素),就是在屏幕分辨率不变的情况下,用户对浏览进行了缩放
强调一点,用户的缩放行为是对浏览器进行的,缩放的是css像素,而非分辨率,分辨率是屏幕的分辨率,不论你怎么缩放当前页面,屏幕分辨率都不会改变
我们知道在移动端可以在一定程度上控制用户的缩放行为,也可以禁止用户缩放
<meta name="viewport" content="width=device-width,initial-scale=1.0">
content属性值 :
但是在pc端就麻烦了
windows:
mac:
由于浏览器菜单栏属于系统软件权限,没发控制,我们可以通过js控制ctrl/cammond + +/- 或 Windows下ctrl + 滚轮 缩放页面的情况
em
本人在实际开发过程中并没有使用过em单位,但是后面要说的rem是基于em的,所以,对em进行简单介绍
em 是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。
看个栗子吧
<body>
<style>
html {
font-size: 50px;
}
.my-div {
width: 100%;
height: 500px;
margin-top: 50px;
background-color: gray;
font-size: 40px;
}
.my-div .parent-font {
font-size: 30px;
}
.my-div .parent-font .child-font {
font-size: 0.5em;
}
</style>
<div class="my-div">
<p class="parent-font">
我是父级文字
<span class="child-font">我是子级文字</span>
</p>
</div>
</body>
html代码中,
第一级,html的 font-size: 50px;
第二级,my-div 的 font-size: 40px;
第三级,parent-font 的 font-size:30px;
第四级,child-font 的 font-size: 0.5em;
我们通过浏览器查看,发现第四级的fong-size为15px;
我们取消第三级parent-font 的字体大小
我们通过浏览器查看,发现第四级的fong-size为20px;
当我们取消第三级font-size后,第三级的字体大小为40px;
所以我们说em的字体大小不是固定的,em的大小取决于父级的字体大小
当父级的字体大小为20px,子级的1em就是20px
当父级的字体大小为30px,子级的1em就是30px
那么说font-size存在着继承父级的特点
我们在第一级html中设置font-size,第二级继承第一级,第三级继承第二级,第四级继承第三级,以此类推
每一级都继承自它的父级,也就是说每一级的em所代表的px大小都不是固定的,因为他们的父级不是同一个,所以em的应用场景并不多。
那么如果是em的都继承自同一个地方,是不是可以解决很多问题呢?
这时候rem出现了
rem
rem 是CSS3的一个相对单位(root em,根em)
使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素
只要html的font-size大小不变,1rem所代表的font-size大小就不会变,rem只取决于html的font-size
rem解决了哪些问题
移动设备的宽度是各种各样的,每个设备的dpr也不同,换句话说就是不同设备每一行的物理像素数不同,能显示的css的px数也不同,
如果我们写一个div,宽度是375px,375px在这个屏幕(iPhone6)上是刚刚满屏,因为这个屏幕宽度刚刚是375px( 设备独立像素),
当我们换另一个宽度是414px的设备(iPhone6Plus)时,这个宽度375px的div就无法铺满这个屏幕,同样的当换一个iPhone5(320px),又会出现滚动条,安卓机的宽度更是五花八门,使用media媒体查询不靠谱,因为它不能覆盖所有的机型宽度
我们之前说rem的大小是相对于html的font-size的,如果html的font-size根据不同设备的宽度做动态计算,问题就会得到解决
我们写页面都是根据UI设计稿来做的,我们假设UI设计稿的宽度是750px(750px是常规宽度,当然也可以是640px或是其他宽度,但是整个项目,宽度必须统一),唯一不变就是就屏幕宽度,我们的html的font-size(rem)只取决于设备宽度
于是
document.documentElement.style.fontSize=100 * ( document.documentElement.clientWidth / 750) + 'px'
html的font-size:document.documentElement.style.fontSize
设备的宽度:document.documentElement.clientWidth
750:UI设计稿的宽度
为了方便计算我们将font-size x 100,方便计算(乘100不是必须的,我接触过一些项目就不是乘以100,但是UI设计稿中使用了sketch做了动态计算,但我还是建议乘100,不然遇到psd的设计图就很麻烦了)
对上面的js做些完善
const fontFun=function () {
let docEl=document.documentElement
let resizeEvt='orientationchange' in window ? 'orientationchange' : 'resize'
const recalc=function () {
let clientWidth=docEl.clientWidth
if (!clientWidth) return
docEl.style.fontSize=100 * (clientWidth / 750) + 'px'
}
if (!document.addEventListener) return
window.addEventListener(resizeEvt, recalc, false)
window.addEventListener('pageshow', recalc, false)
document.addEventListener('DOMContentLoaded', recalc, false)
}
export {
fontFun
}
对以上代码不做过多解释
也可以这样写
(function(doc, win) {
var docEl=doc.documentElement,
resizeEvt='orientationchange' in window ? 'orientationchange' : 'resize',
recalc=function() {
var clientWidth=docEl.clientWidth
if (!clientWidth) return
docEl.style.fontSize=100 * (clientWidth / 750) + 'px'
}
if (!doc.addEventListener) return
win.addEventListener(resizeEvt, recalc, false)
win.addEventListener('pageshow', recalc, false)
doc.addEventListener('DOMContentLoaded', recalc, false)
})(document, window)
iPhone5(320px)下html的font-size:42.6667px,1rem=42.6667px
iPhone6(375px)下html的font-size:50px,1rem=50px
iPhone6Plus(414px)下html的font-size:55.2px,1rem=55.2px
rem是继承自html的font-size,但是小程序中没有html,那怎么办呢?
rpx
我不基于html的font-size了,我基于一个别的值就行了,你也不需要计算这个值,我给你计算了,这就是rpx。
最终的效果就是,你开发时在iphon6的设计稿上量了多少px,就写多少rpx就行了,完美适配,perfect!
*请认真填写需求信息,我们会在24小时内与您取得联系。