对于前端来说,要想把大屏做的好看,屏幕适配、css的灵活使用、图表的效果都是必不可少的,这里我打算通过一个实例来详细介绍一下相关的一些技术,因为内容较多,所以分为两篇:布局篇和图表篇。
布局篇,也就是本篇,主要介绍适配、布局和其他涉及到的一些css相关的内容。
图表篇则着重讨论一下使用ECharts绘制各类图表。
本案例使用VUE + Element-UI构建。
源码:https://gitee.com/moonlight_lin/display-demo
本期实现的效果:
一、适配方案
1.简述
大屏开发中的适配问题:
- 设计稿按照1920*1080的分辨率,16:9的比例设计,而在实际开发中,document窗口不足16:9(高度减掉顶部tab及导航栏,地址栏等)。
- 不同分辨率:实际应用场景中,显示场景不同,不能固定写死px单位。
- 不同比例:不同的显示器宽高比与设计稿不一致。
常用的方案是使用rem适配。rem是CSS3中的一种相对长度单位,它的单位长度取决于根标签html的字体大小。比如:html设置font-size: 20px;,那么1rem = 20px;如果设置font-size: 30px;,那么1rem = 30px。因此,在不同的屏幕尺寸下,设置不同的根字体大小,并且在需要适配的地方使用rem单位,就可以达到适配的目的。
我们可以自己编写代码实现这样的功能,但难免有些繁琐,而且已经有两个库为我们做了这些事:amfe-flexible、postcss-pxtorem,我们在项目里使用它们来进行适配。
2.安装依赖
npm install amfe-flexible
npm install postcss-pxtorem@5.1.1
amfe-flexible
amfe-flexible是lib-flexible的升级版,它会根据设备宽度,修改根字体大小。
amfe-flexible实际上做了这几件事情:
- 动态改写 <meta> 标签
- 给 <html> 元素添加 data-dpr 属性,并且动态改写 data-dpr 的值
- 给 < html> 元素添加 font-size 属性,并且动态改写 font-size 的值
直接在main.js中引入amfe-flexible即可:
import "amfe-flexible"
postcss-pxtorem
postcss-pxtorem是PostCSS的插件,可以自动将px转换成rem,并且可以对一些特殊情况进行处理。
postcss-pxtorem的配置可以在vue.config.js、postcsssrc.js、postcss.config.js其中任意一个文件中进行,权重从左到右降低,项目中没有对应文件的话直接新建就可以了。
具体配置项参见:https://www.npmjs.com/package/postcss-pxtorem。
另外需要注意:行内样式中的px不会被转换为rem,所以对于需要自适应的样式不要写在行内。对于不需要转化的样式就可以写在行内,或者使用PX(大写)作为单位。
// vue.congif.js
/***
注意点:
(1)rootValue根据设计稿宽度除以10进行设置,假设设计稿为1920,rootValue设为192;
(2)propList是设置需要转换的属性,*为所有都进行转换。
***/
module.exports = function(){
devServer:{
// ...
},
css: {
loaderOptions: {
postcss: {
plugins: [
require('postcss-pxtorem')({
rootValue: 192, // 根元素字体大小
propList: ['*'], // 可以从px转换为rem的属性,匹配正则
// unitPrecision: 5, // 允许rem单位增长的十进制数字
// replace: true, // 替换包含rems的规则,而不添加后备
// mediaQuery: false, // 允许在媒体查询中转换px
// minPixelValue: 0, // 设置要替换的最小像素值
// selectorBlackList: [], // 忽略转换正则匹配项
// exclude: /node_modules/i // 要忽略并保留为px的文件路径
})
]
}
}
},
}
二、整体布局
接着我们看一下整体的布局及其相关的css。页面划分如下图所示,顶部区域为系统名称、日期等信息,下方左右两侧区域用来放置各类图表,中间区域用来放置中国地图。这里我们先用背景色填充示意。
首先看一下页面结构,与上图的结构是完全对应的:
<div class="display">
<div class="display-header"></div>
<div class="display-body">
<div class="display-body-left">
<div class="display-item"></div>
<div class="display-item"></div>
<div class="display-item"></div>
</div>
<div class="display-body-middle"></div>
<div class="display-body-right">
<div class="display-item"></div>
<div class="display-item"></div>
<div class="display-item"></div>
</div>
</div>
</div>
1.视口单位
接着我们从整体到局部依次看一下它们的样式。
<style lang="scss">
.display {
width: 100vw;
height: 100vh;
background: url("~@/assets/bg.png");
background-size: 100%;
}
</style>
.display 是最外层的容器,大屏需要占据整个视口,所以我们使用视口单位vw和vh。这里视口指浏览器的可视区域,CSS3中的视口单位包括以下4个:
- vw:1vw等于视口宽度的1%。
- vh:1vh等于视口高度的1%。
- vmin:取vw和vh中较小的那个。
- vmax:取vw和vh中较大的那个。
- vmin和vmax在移动端横屏、竖屏切换中非常有用。举个例子:
div {
height: 100vmin;
width: 100vmin;
}
2.flex布局
接着往下看,整个页面分为上、下两部分,下面又分为左中右三部分。
.display-header {
height: 74px;
background: #C0C4CCCC;
}
.display-body {
height: calc(100% - 74px);
padding: 20px;
display: flex;
gap: 20px;
.display-body-left, .display-body-right {
width: 420px;
background: #C0C4CCCC;
}
.display-body-middle {
flex: 1;
background: #C0C4CCCC;
}
}
.display-body 使用弹性布局( display: flex; ),左右两块固定宽度( width: 420px; ),中间部分填充剩余空间( flex: 1; )。gap属性设置列之间的间隙大小( gap: 20px; )。
下面以图示的方式简要介绍一下flex布局中的相关属性。
flex布局中的容器存在主轴和交叉轴,默认情况下水平方向是主轴,垂直方向为交叉轴,子元素沿主轴排列。
容器属性
/* 测试 */
.test-flex-box {
width: 170px;
height: 200px;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
align-content: stretch;
background: #dedede;
.test-flex-item {
width: 40px;
height: 40px;
background: #ffe6e6;
font-size: 12px;
border: 1px solid;
display: flex;
align-items: center;
justify-content: center;
}
}
1)flex-direction:定义主轴方向,决定了子元素如何排列。
- row(默认):主轴为水平方向,起点在左端。
- row-reverse:主轴为水平方向,起点在右端。
- column:主轴为垂直方向,起点在上沿。
- column-reverse:主轴为垂直方向,起点在下沿。
2)flex-wrap:定义子元素超出容器宽度或高度后是否换行,如果不换行,元素会缩小。
- nowrap(默认):不换行。
- wrap:换行,第一行在上方。
- wrap-reverse:换行,第一行在下方。
3)flex-flow:flex-direction属性和flex-wrap属性的简写形式,默认为row nowrap。
4)justify-content:定义子元素在主轴上的对齐方式,与主轴方向有关。
- flex-start(默认):在容器的起点对齐。
- flex-end:在容器的末端对齐。
- center: 在容器的中心对齐。
- space-between:两端对齐,子元素之间的间隔都相等。
- space-around:每个子元素两侧的间隔都相等。
5)align-items:定义项目在交叉轴上的对齐方式,与交叉轴的方向有关。
- stretch(默认):如果子元素未设置高度或设为auto,将占满整个容器的高度。
- flex-start:交叉轴的起点对齐。
- flex-end:交叉轴的末端对齐。
- center:交叉轴的中心对齐。
- baseline: 子元素的第一行文字的基线对齐。如果没有文字,则基线为子元素底部边缘。交叉轴为水平方向时,与flex-start一致。
6)align-content:定义多根轴的对齐方式。即存在多根主轴或多根交叉轴的情况下该属性才生效。
- stretch(默认):轴占满整个交叉轴。
- flex-start:与交叉轴的起点对齐。
- flex-end:与交叉轴的末端对齐。
- center: 与交叉轴的中心对齐
- space-between:两端对齐,轴与轴之间的间隔都相等。
- space-around:每根轴两侧的间隔都相等。
子元素属性
- order:定义子元素的排列顺序。数值越小,排列越靠前,默认为0。
- flex-grow:定义项目的放大比例,默认为0(即如果存在剩余空间也不放大)。
- flex-shrink:定义项目的缩小比例,默认为1(即如果空间不足,该项目将缩小)。为0时不缩小。
- flex-basis:定义了在分配多余空间之前,子元素占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即子元素的本来大小。
- flex:是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。 该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
- align-self:允许单个子元素有与其他子元素不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。其他取值与align-items完全一致。
3.grid布局
现在布局还剩下最后一部分:左右两侧区域中的3个容器。
.display-body-left, .display-body-right {
width: 420px;
display: grid;
grid-template-rows: repeat(3, 1fr);
gap: 20px;
.display-item {
background: #C0C4CCCC;
}
}
这里我使用了网格布局 display: grid; ,通过 grid-template-rows: repeat(3, 1fr); 将其高度分为3等份。当然,这里完全可以使用flex布局,我主要是想借此说明一下不同的布局方式,让不清楚网格布局的同学能对它有一定的认识。在这个案例的后面还会使用到这种布局。
/* 使用flex布局也可以达到同样的效果 */
.display-body-left, .display-body-right {
width: 420px;
display: flex;
flex-direction: column;
gap: 20px;
.display-item {
flex: 1;
background: #C0C4CCCC;
}
}
flex布局是轴线布局,只能指定子元素针对轴线的位置,可以看作是一维布局,grid 布局则是将容器划分成“行"和“列”,产生单元格,然后指定子元素所在的单元格,可以看作是二维布局,grid布局比 flex布局更强大,相应的,涉及到的属性也很多。这里不再一一列举,只针对一些主要的属性进行介绍。
容器属性
1)grid-template-columns、grid-template-rows分别用于划分列和行。
grid-template-columns: 100px 100px 100px; /* 3列,每列宽100px */
grid-template-rows: repeat(3, 100px); /* 3行,每行高100px */
- 使用 auto-fill 设置自动填充。
grid-template-columns: repeat(auto-fill, 100px);
grid-template-rows: repeat(auto-fill, 100px);
- fr 表示比例关系。
grid-template-columns: repeat(3, 1fr); /* 平均分成3等份 */
grid-template-rows: 2fr 1fr; /* 行高分为3份,第一行占2份,第二行占1份 */
- 使用 auto 由浏览器自动进行分配。
grid-template-columns: 100px auto 100px; /* 第1列和第3列 宽100px,第2列自动分配 */
grid-template-rows: 50px auto 50px; /* 第1行和第3列行 高50px,第2行自动分配 */
2)grid-column-gap、grid-row-gap、grid-gap 设置子元素之间的间距。
grid-gap是grid-column-gap和grid-row-gap的简写。另外,在最新标准中,这3个属性的前缀grid-已经删除,分别为:column-gap、row-gap、gap。
这个属性并不是只能用在grid布局中,在flex布局中也可以使用,在 .display-body 中就用了这个属性。
grid-template-columns: repeat(3, 1fr);;
grid-template-rows: repeat(3, 1fr);;
gap: 20px 10px;
3)align-items、justify-items、place-items
align-items和justify-items分别用于设置单元格内容的垂直和水平的对齐方式。
有4个取值:start、end、center、stretch。
place-items是这两个属性的简写。
justify-items: center;
align-items: start;
/* place-items: start center; */
子元素属性
1)grid-column-start、grid-column-end、grid-row-start、grid-row-end
用来指定子元素从哪根网格线开始,到哪根网格线结束。
grid-column是grid-column-start、grid-column-end的简写;grid-row是grid-row-start、grid-row-end的简写。
还可以进一步简写为:
grid-area: <grid-row-start> / <grid-column-start> / <grid-row-end> / <grid-column-end>
grid-column-start: 1;
grid-column-end: 3;
grid-row-start: 1;
grid-row-end: 3;
/* grid-column: 1 / 3; */
/* grid-row: 1 / 3; */
/* grid-area: 1 / 1 / 3 / 3 */
2)justify-self、align-self、place-self用于设置子元素自身的对齐方式。
与容器属性align-items、justify-items、place-items的用法一致。
align-self: center;
justify-self: center;
/* place-self: center; */
三、其他CSS技巧
1.定位方式
接着来看一下顶部标题。
<div class="display-header">
<div class="left">
<span class="datetime">{{ curDate }} 星期{{ curDay }}</span>
</div>
<div class="title">
<h1>大屏展示Demo</h1>
</div>
<div class="right">
<span class="datetime">{{ curTime }}</span>
</div>
</div>
.display-header {
height: 74px;
background: url("~@/assets/header.png") no-repeat center;
background-size: auto 100%;
position: relative;
.title {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #FFFFFFDD;
font-size: 20px;
}
.left, .right {
position: absolute;
bottom: 10px;
}
.left {
right: calc(50% + 260px);
}
.right {
left: calc(50% + 260px);
}
.datetime {
color: #08D1EA;
font-size: 16px;
}
}
标题、左右两边的日期、时间以它们的父级 .display-header 作为参照进行绝对定位。(父元素设置 position: relative; ,子元素设置 position: absolute; )
标题元素在父容器的水平方向、垂直方向上都居中,元素是以左上角作为基准进行定位的。
top: 50%; left: 50%; 会将标题元素的左上角定位到父容器的中心点,
再对其进行平移变换 transform: translate(-50%, -50%); ,使其相对于自身分别向x轴负方向(向左)偏移自身宽度的一半,向y轴负方向(向上)偏移自身高度的一半,从而在父容器中居中。
左右两边的日期时间,则以父容器中心为基准分别向左、向右偏移。
这里再介绍一下css中的四种定位方式:
- relative:相对定位,相对于元素自身位置进行定位。相对定位不会脱离文档流,在文档流中不会影响其他元素。
- absolute:绝对定位,相对于其最近的已定位(设置了position属性的)祖先元素进行定位,如果没有已定位的祖先元素,则相对于body元素进行定位。绝对定位的元素会脱离文档流。
- fixed:固定定位,相对于浏览器窗口进行定位,元素的位置在屏幕内容滚动时不会改变位置。固定定位的元素会脱离文档流。
- sticky:粘性定位,相对于其最近的滚动(设置了overflow相关属性的)祖先元素进行定位,固定在该祖先元素上,不会随之滚动。sticky定位非常适合用来做吸顶效果。
.box-father {
position: relative;
height: 400px;
overflow-y: auto;
.box-sticky {
width: 100%;
height: 30px;
position: sticky;
top: 10px;
}
}
上个图看一下它们之间的区别:
2.图片边框
最后看一下用来放置图表的 .display-item 容器,它有个不规则的边框,一般的做法是使用background来设置,但是容器未必是固定宽高,可能长宽比跟图片不一致,这样就会导致拉伸变形。如下图所示,左边是容器长宽比跟图片一致的情况,右边是不一致的情况,可以看到图片拉伸变形了:
这里介绍一下border-image属性,它用于给边框设置图片,并且可以将其分成9个部分,使其缩放时不会变形。
.display-item {
width: 100%;
border: 30px solid;
border-image: url("~@/assets/border.png") 30;
}
border-image是5个属性值的简写形式,它包括:
- border-image-source:设置要用于绘制边框的图像的路径。
border-image-source: url("~@/assets/border.png");
- border-image-slice:用来对source指定的图像进行切分。
- 可以指定上、右、下、左四个方位来分割图像,将其分成 9 个部份。可以用数值或者百分比来指定。可以加上 fill 关键字来填充中间区域,不加的话中间区域就是透明的。
除了 fill 关键字,border-image-slice 属性可以接收 1~4 个参数值:
- 如果提供全部四个参数值,那么将按上、右、下、左的顺序对图像进行分割
- 如果提供三个参数,那么第一个参数用于上方,第二个参数用于左、右两侧,第三个参数用于下方
- 如果提供两个参数,那么第一个参数用于上方和下方,第二个参数用于左、右两个
- 如果只提供一个参数,那么上、右、下、左都将使用该值进行分割
border-image-slice: 30;
- border-image-width:用于指定边框的宽度。默认为1(即 1 * border-width)。同样可以接收1~4 个参数值。
border: 30px solid;
border-image-width: 2; /* 60px */
- border-image-outset:用于指定边框向外偏移的距离。
border-image-outset: 10px;
border-image-repeat:用于设置填充方式。有以下取值:
- stretch(默认):将被分割的图像使用拉伸的方式来填充满边框区域;
- repeat:将被分割的图像使用重复平铺的方式来填充满边框区域,当图像碰到边界时,超出的部分会被截断;
- round:与 repeat 关键字类似,当背景图像不能以整数次平铺时,会根据情况缩放图像;
- space:与 repeat 关键字类似,当背景图像不能以整数次平铺时,会用空白间隙填充在图像周围。
border-image-repeat属性可以接收 1~2 个参数值:
- 如果提供两个参数,那么第一个参数将用于水平方向,第二个将用于垂直方向;
- 如果只提供一个参数,那么将在水平和垂直方向都应用该值。
四、总结
本篇通过一个实例,介绍了适配、布局相关css、定位相关css以及使用border-image实现图片拉伸不变形的效果。熟练掌握并且灵活运用css可以使页面结构更加清晰简洁(没有多余的容器或元素),也可以使页面呈现更好的效果。