vivo 悟空活动中台 - 栅格布局方案
本文首发于 vivo互联网技术 微信公众号
链接: https://mp.weixin.qq.com/s/6O0CH0U_WE1YkPK75m-jDQ
作者:悟空中台研发团队
一、背景
今天来给大家分享一下栅格布局方案。在后台展示页面中,常见卡片列表的展示需求。但是这类展示型需求其实不容易满足,有以下两个问题。
1、优化前的问题
(1)PC分辨率不统一
用户在使用管理后台时,打开浏览器页面宽度不一。主流的显示宽度1920像素,但也存在1366,1600等其他宽度的场景。而且用户在桌面端搭建页面时,同时在操作的内容不仅仅是管理后台,可能会适当缩小页面宽度来做其他的事情。这样我们在页面布局时需要考虑到兼容广泛的屏幕宽度。
(2)一份设计稿
无论是前端开发还是设计师,在页面布局时,都希望能兼顾到不同终端场景下的展示差异,但是设计师不可能去针对性做出多套设计稿,来针对内容进行自适应设计。尚待有一种更加通用,且又轻量的方案供参考使用。
2、优化前的方案
在卡片列表展示的页面,往往会实现一种统一尺寸的卡片依次排列,或者一行展示固定数量的卡片,宽度动态调整。这两种布局方式是我们实现展示类需求的常备方案。
但是这两种常备方案,只是简单的解决了设计稿所呈现的分辨率展示诉求,在页面宽度有调整时,展示效果就不尽如人意了。
(图1 - 优化前的方案)
(1)固定卡片宽度
在页面宽度进行调整时,页面左右侧会有较大幅度的空白,没有展示具体的内容。
(2)固定卡片个数
在页面宽度过大或者宽度过小时,卡片容易放大缩小至非常夸张的比例,展示的效果欠佳。
二、布局方案分析
鉴于目前简单的方案无法满足广泛页面宽度的诉求,我们可以对行业内多种自适应布局方案进行调研,来对我们最终的布局方案做出一定程度的参考。为了兼容页面不同宽度的情况,我们先将页面上布局元素进行一个简单的分类,布局的组成部分为卡片宽度,卡片外边距,容器内边距。自适应栅格布局,就是动态的调整这三个影响因子,来实现页面更理想的展示。
- 卡片宽度:卡片的宽度随着页面宽度自适应调整
- 卡片外边距:卡片互相之间的边距随着页面宽度自适应调整
- 容器内边距:容器的内边距随着页面宽度自适应调整
(图2 - 影响因子)
1、行业内的方案
我们这里收集了三种常见的行业内解决方案,来进行一一列举。
(1)谷歌 Material Design
谷歌的 Material Design ,是一套大一统的自适应布局解决方案,将页面宽度划分为13种场景,在达到这13种场景的临界点时,调整自适应布局的三个影响因子,来实现统一的自适应效果。该方案的优点是以谷歌的设计规范为蓝本,能够在一类产品中实现统一的布局风格。如果你的产品希望以谷歌的设计规范为依托,并且后期不会做个性化调整,谷歌的 Material Design 是很好的选择。
(图3 - 谷歌 Material Design)
(2)定制栅格的方案
定制栅格方案常用于企业官网或者视频网站。国内常见的视频网站,在首页展示视频内容时,就用到了这种定制栅格的方案,本质上就是在不同的分辨率区间,使用不同的设计稿。这种方案适合于设计资源比较丰富的团队,能够基于自适应的要求,给出多套实现的设计稿,并给出一套内部的定制化栅格方案。
(图4 - 定制栅格的方案)
(3)自适应栅格方案
自适应栅格方案,就是有一套计算公式,可以套用给页面宽度和页面布局三个影响因子,通过固定其中两项因子的值,计算出第三项因子的值,来实现页面布局的效果。这个方案适合于团队对设计资源要求不高,并且对展示效果有较高的要求的场景。
2、自适应方案分析
下面简要阐述三种自适应栅格方案各自的使用场景,和优缺点分析:
(表1 - 自适应栅格方案对比)
(1)自适应卡片方案
通过固定页面边距和卡片边距的尺寸来计算出卡片宽度,该方案的优点是整体框架稳定,卡片自适应伸缩。缺点是调整浏览器宽度时,卡片尺寸不可避免时大时小,卡片内部的元素要按照百分比布局,一定要做好自适应。
(图5 - 自适应卡片方案)
(2)自适应内边距方案
通过固定卡片边距和卡片宽度的尺寸来计算出页面边距,该方案的优点是,卡片展示内容整体居中,卡片宽度也不会有变化,卡片内部可以严格还原设计稿;但缺点是,卡片内容在大屏内会显得太小,但是在小屏上会显得很大。
(图6 - 自适应内边距方案)
(3)自适应边距方案
通过固定页面边距和卡片宽度的尺寸来计算出卡片边距,该方案的优点是,页面左右两侧不会有太大的空白区域,卡片宽度也不会有变化,卡片内部可以严格还原设计稿;但缺点是卡片边距会动态调整,这种场景中卡片直接的距离往往比较大,这样视觉效果更好。
(图7 - 自适应边距方案)
3、方案总结
行业内这三个方案各有不同的适用场景。
- Material Design 方案适合于团队设计资源投入少,设计风格以谷歌 Material Design UI 为准,且后期不会做个性化调整的团队。
- 定制化栅格方案适合于团队设计资源丰富且定制化强的团队。
- 自适应栅格方案适合于团队设计资源投入少且栅格风格更加个性化的团队。
悟空团队基于团队业务需求和设计效果,决定使用自适应栅格方案,下面是我们的栅格方案实现。
(图8 - 悟空栅格实现)
三、自适应栅格方案 - 栅格组件实现
我们根据自适应栅格方案实现了自适应栅格组件,三种自适应栅格方案可以使用同一个组件来实现,我们首先看看栅格组件如何进行使用。
1、组件使用方式
自适应栅格组件包含外层的容器组件 Grid 和内置的卡片组件 GridItem,容器组件有四个基础配置项和三个定制化配置项。
以下是容器组件 Grid 的基础配置项,Grid根据基础配置项粗算出一行展示的栅格个数。
(1)组件配置项
(表2 - 自适应栅格基础配置项)
以下是容器组件 Grid 的定制配置项,根据栅格个数和限制条件判断是否需要调整栅格个数,进行自适应调整。
(表3 - 自适应栅格定制配置项)
(2)组件使用范例
这里我们使用项目展示列表来举例。在项目展示的场景,我们将卡片容器使用 Grid 组件,并且在卡片素材使用 GridItem 组件进行包裹,不修改默认的 Grid 参数。
<grid ref="grid" :gridType="'autoMargin'" :baseW="250">
<grid-item v-for="project in projectList"
:key="project.projectId"
class="project-item"
>
<div class="name">{{ project.name }}</div>
</grid-item>
</grid>
2、组件核心逻辑
下面简要描述一下栅格布局组件 Grid 的核心逻辑:
- 组件 mounted 后,监听 window 的 resize 事件。
- 页面宽度发生变化时,清除当前计算数据,并重新计算出自适应效果。
- 在组件销毁前,移除对 window 的 resize 事件监听。
mounted () {
window.addEventListener('resize', this.calcGridSize, false)
this.reset()
},
beforeDestroy () {
window.removeEventListener('resize', this.calcGridSize, false)
},
methods: {
reset () {
// 清除计算数据
this.calcGridSize()
},
calcGridSize () {
// 重新进行计算
}
}
3、自适应效果计算
不同的自适应方案对应有不同的计算规则,但是其核心方案都是一致的,需要根据三个数值配置项作为基准,求解在页面宽度变化时,其中某一个配置项如何自适应变化。以下整理三种自适应方案的计算方式:
(表4 - 自适应栅格实现方式)
以下整理三种方案的计算公式:
(表5 - 自适应栅格计算公式)
以下根据自适应卡片宽度为例分析具体的流程:
- calcGridSize 根据当前配置项选择计算方式;
- getGridNum 计算出一行同时存在的卡片个数;
- 根据 limitW 计算是否需要调整卡片个数,计算出最终的卡片宽度。
calcGridSize () {
// step 1,选择计算方式
if (this.gridType === 'autoMargin') {
this.calcAutoMg()
} else if (this.gridType === 'autoPadding') {
this.calcAutoPd()
} else {
this.calcAutoWd()
}
},
// 自适应卡片宽度
calcAutoWd () {
const containW = this.$el.clientWidth
const { baseMg, baseW, limitW } = this
// step 2,计算 grid 个数
const caclW = containW - baseMg
let gridNum = Math.floor(caclW / baseW)
let gridW = caclW % baseW / gridNum + baseW - baseMg
// step 3,修正 grid 个数,计算出最终卡片宽度
if (caclW % baseW > limitW) {
gridW = caclW / (1 + gridNum) - baseMg
}
this.gridW = gridW
},
// 自适应外边距
calcAutoMg() {
const containW = this.$el.clientWidth
const { baseMg, baseW, limitMg } = this
// step 2,计算 grid 个数
const caclW = containW - baseMg
let gridNum = Math.floor(caclW / baseW)
let gridMg = (containW - baseW * gridNum) / (gridNum + 1)
// step 3,修正 grid 个数,计算出最终外边距
if (gridMg < limitMg) {
gridNum--
gridMg = (containW - baseW * gridNum) / (gridNum + 1)
}
this.gridMg = gridMg;
},
// 容器自适应内边距
calcAutoPd() {
const containW = this.$el.clientWidth
const { baseMg, baseW, limitPd } = this
// step 2,计算内边距
let gridPd = ((containW + baseMg) % (baseW + baseMg)) / 2
// step 3,修正 grid 个数,计算最终内边距
if (gridPd < limitPd) {
gridPd =
(((containW + baseMg) % (baseW + baseMg)) + baseW + baseMg) / 2
}
this.gridPd = gridPd;
}
4、自适应样式传值 - CSS变量
上一步骤,我们通过以自适应卡片宽度为例子,计算出来自适应参数后,需要有一种方式来实现CSS样式的父子传值。
在基于vue的管理后台项目上,我们有两种实现方式来解决CSS样式的父子传值。
- 通过父子组件props来传值
- 通过CSS变量来传值
考虑到CSS变量实现方式更加轻量而且兼容性符合我们项目要求。我们通过使用CSS变量来完成自适应样式传值,下面简要介绍一下CSS变量在前端页面中的使用。
(1)声明与使用
声明CSS变量时,需要在变量名前面增加两根连词线(--)。变量名大小写敏感,并且变量的值可以是色值、数字和字符串。
使用CSS变量时,需要使用var()函数进行包裹。var()函数默认接受两个参数,第一个参数是前文定义的CSS变量,第二个参数是默认值(可不传)。
下面代码展示了如何声明并使用CSS变量
- 在grids中声明了变量--gridW,并且在grid-item中进行使用。
- 在grid-item中使用变量--gridH作为高度,如果获取不到,则使用默认值 50px
<style>
.grids {
--gridW: 200px;
}
.grids .grid-item {
width: var(--gridW);
height: var(--gridH, 50px);
}
</style>
(2)作用域与优先级
CSS变量可以在多个选择器内进行定义和使用,优先级与CSS层叠优先级一致,作用范围则是在变量定义的选择器范围内均生效。
(3)兼容性
我们在业务开发中已经开始使用CSS变量了,通过 CanIUse 网站可以看出目前CSS变量已经兼容了众多主流浏览器,并且在前文中提到的谷歌 Material Design 方案也是使用CSS变量来实现谷歌的栅格布局方案。所以兼容性上,大家可以放心使用。
(图9 - CSS变量兼容性)
如果要考虑兼容性处理,建议在使用CSS变量时考虑下述方法进行兼容。
<style>
.grids .grid-item {
width: 200px;
width: var(--gridW);
}
</style>
(4)样式传值实现
栅格布局组件在父组件上定义卡片宽度,卡片边距。通过CSS变量传值给子组件进行使用。
<template>
<div class="grids" :style="{
'--gridW': gridW + 'px',
'--gridMg': gridMg + 'px',
'--gridPd': gridPd + 'px'
}">
<slot></slot>
</div>
</template> <script>
// ...
</script>
<style lang="less" scoped>
.grids {
overflow-y: auto;
display: flex;
flex-wrap: wrap;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
align-content: flex-start;
padding: 0 var(--gridPd) 0 calc(var(--gridPd) - var(--gridMg));
>>> .grid-item {
width: var(--gridW);
margin-left: var(--gridMg);
}
}
</style>
四、小结
我们从提升卡片类页面展示效果的诉求出发,历经:
- 当前布局问题分析
- 行业内布局方案筛选
- 栅格布局方案实现
- CSS变量优化
等一系列手段,探索出适合管理后台的栅布局方案,更好的服务了中台页面的展示场景。悟空中台开发团队将永不止步,持续研究和思考,为大家带来更多的实战技巧,感谢您的阅读。
悟空活动中台】系列往期精彩文章:
- 《揭秘 vivo 如何打造千万级 DAU 活动中台 - 启航篇》 主要为大家讲述 vivo 活动中台的能力与创新。
- 《悟空活动中台 - 微组件状态管理(上)》介绍了活动页内 RSC 组件之间的状态管理和背后的设计思路。
- 《悟空活动中台 - 微组件状态管理(下)》探索平台和跨沙箱环境下的微组件状态管理。
- 《vivo 悟空活动中台-基于行为预设的动态布局方案》本文以“满屏”场景下的页面布局思考为切入点,以微组件为元素单元,提供了一种新的布局方案设计思路——基于行为预设的动态布局方案,并详细的分享了设计目的及具体实现方案。
- 《vivo悟空活动中台 - 微组件多端探索》是基于自助多端扩展,也就意味着多端 微 组件选择越丰富,内容越通用,玩法越多样,产品价值也会越高。
- 《vivo 悟空活动中台 - H5 活动加载优化》从提高资源请求速度,资源压缩、缓存、渲染等多种角度出发,寻找悟空活动专题加载优化方案。
- 《vivo悟空活动中台 - 基于 WebP 的图片高性能加载方案》 从技术选型、架构设计到方案落地,全方位的呈现悟空活动中台基于 WebP 的图片高性能加载方案。
更多内容敬请关注 vivo 互联网技术 微信公众号
注:转载文章请先与微信号:Labs2020 联系
vivo 悟空活动中台 - 栅格布局方案的更多相关文章
- 基于淘宝弹性布局方案lib-flexible的问题研究
上篇文章<淘宝弹性布局方案lib-flexible实践>结合一个简单的实例,说明了lib-flexible的基本用法,但是lib-flexible的这种适配方式在适配的时候会修改viewp ...
- grid栅格布局
前面的话 Grid布局方式借鉴了平面装帧设计中的格线系统,将格线运用在屏幕上,而不再是单一的静态页面,可以称之为真正的栅格.本文将详细介绍grid布局 引入 对于Web开发者来说,网页布局一直是个比较 ...
- 你不需要基于 CSS Grid 的栅格布局系统
在过去的几个星期里,我开始看到基于 CSS Grid 的布局框架和栅格系统的出现.我们惊讶它为什么出现的这么晚.但除了使用 CSS Grid 栅格化布局,我至今还没有看到任何框架能提供其他有价值的东西 ...
- 浅谈css的栅格布局
栅格布局想必大家都很了解,我们做页面开发的时候,往往对页面板式的要求很高,如何对各个区域的内容排版,并使之对齐是我们的一大难题.而栅格系统就是我们排版的利器,他支持自动对齐.自动计算边距.流式布局等优 ...
- bootstrap(关于栅格布局)
栅格系统是通过行(.row)与列(column)的组合一起来创建页面布局的,所以只有列(column)可以作为行(row)的直接子元素,我们所要写的内容可以放在列里(column),不过在行的外层还需 ...
- 移动适配请使用比rem等更好的布局方案
移动端大行其道,rem/em.百分比.响应式方案更是层出不穷,看见周围的伙伴们都在对使用rem和百分比情有独钟,可我却偏不爱,之所以出现如此多的方法,其目的只有一个屏幕适配. 屏幕适配顾名思义 ...
- Bootstrap栅格布局系统的特点
栅格布局系统的特点: (1)所有的行必须放在容器中: .container或.container-fluid (2)分为多行(row),一行中平均分为12列(col) (3)网页内容只能放在列(col ...
- 淘宝弹性布局方案lib-flexible实践
2个月前,写过一篇文章<从网易与淘宝的font-size思考前端设计稿与工作流>总结过一些移动web中有关手机适配的一些思路,当时也是因为工作的关系分析了下网易跟淘宝的移动页面,最后才有那 ...
- CSS:谈谈栅格布局
检验前端的一个基本功就是考查他的布局.很久之前圣杯布局风靡一时,这里就由圣杯布局开始,到最流行的bootstrap栅格布局. 圣杯布局 圣杯布局是一种三列布局,两边定宽,中间自适应: * { box- ...
- bootstrap笔记-栅格布局
1. .clearfix 这个类可以在栅格布局中起到一个不占空间的clear的作用,如下:可以尝试带.clearfix和不带它的区别 <div class="container-f ...
随机推荐
- Android学习day01【搭建Android Studio】
是Google开发的操作系统 Android开发是移动应用开发的表现形式之一 还有很多的开发形式,就不一一列举了 完整项目精简的开发流程 开发工具 Android studio(强烈建议) Andro ...
- unsafe类和varhandle类讲解
Java的Unsafe类是一个非常特殊的类,它提供了一组原始.底层的操作,可以跳过Java的限制,直接操作内存和对象.这些操作可能会破坏Java的安全机制,所以Unsafe类被标记为不安全的. Uns ...
- Scrapy集成selenium-案例-淘宝首页推荐商品获取
scrapy特性就是效率高,异步,如果非要集成selenium实际上意义不是特别大....因为selenium慢.... 案例:淘宝首页推荐商品的标题获取 爬虫类 toabao.py import s ...
- scrapy 请求meta参数使用案例-豆瓣电影爬取
num = 0 import scrapy from scrapy.http import HtmlResponse from scrapy_demo.items import DoubanItem ...
- lca 学习笔记
定义 最近公共祖先简称 \(LCA\) 两个节点的最近公共祖先,就是这两个点的公共祖先里,离根最远的的那个 为了方便,我们记某点集 \(S={v1,v2,...,vn}\) 的最近公共祖先为 \(LC ...
- 文心一言 VS 讯飞星火 VS chatgpt (166)-- 算法导论13.1 6题
六.用go语言,在一棵黑高为 k 的红黑树中,内部结点最多可能有多少个?最少可能有多少个? 文心一言: 红黑树是一种自平衡的二叉查找树,其中每个节点都有一个颜色属性,可以是红色或黑色.红黑树满足以下性 ...
- Windows系统激活工具HK
下载:https://wwsi.lanzoum.com/iyUNn10e7foh 密码:g05d GitHub开源 .无毒 原文链接:https://github.com/zbezj/HEU_KMS_ ...
- Ef Core花里胡哨系列(10) 动态起来的 DbContext
Ef Core花里胡哨系列(10) 动态起来的 DbContext 我们知道,DbContext有两种托管方式,一种是AddDbContext和AddDbContextFactory,但是呢他们各有优 ...
- 为什么Java中“1000==1000”为false,而”100==100“为true?
在日常编程中,我们经常遇到一些看似简单却隐藏着复杂逻辑的问题. 比如,你是否想过为什么在 Java 中表达式1000==1000会返回 false,而 100==100 却返回 true 呢? Int ...
- 在ASP.NET Core微服务架构下使用RabbitMQ如何实现CQRS模式
前言 在现代软件开发中,微服务架构和CQRS模式都是备受关注的技术趋势.微服务架构通过将应用程序拆分为一系列小型.自治的服务,提供了更好的可伸缩性和灵活性.而CQRS模式则通过将读操作和写操作分离,优 ...