作者: 涂鸦-Cheetah
来涂鸦工作: job.tuya.com/
css flex 与 Vue 指令的完美结合
flex弹性盒子是最流行的布局属性,可以帮助我们快速完成响应式页面。
在开发vue时,我们会应用大量flex样式,这就需要我们去维护好我们的css代码。
痛点
你有遇到这样的问题吗?刚开始不需要flex布局,当新元素加入时,又突然需要使用flex进行对齐或者排版,
所以就不得不折返到css代码区添加flex样式。
这样会比较麻烦,尤其是代码很多时,需要在海量代码里全局搜索,而且会出现大量冗余代码。
解决方法
其实我们可以将flex有关的样式更加细分,并提取成通用css代码(不要在通用代码名称里出现与业务有关的东西),比如.flex, .flex-wrap, .flex-center等等,在需要的时候选择并组合自己所需的样式即可,这样可以避免复制粘贴冗余代码。
更好的解决方法
不过有更好,更vue的方法吗?可以让我们随时随地,想用就用而不必考虑css代码呢?
Vue指令
Vue为我们提供了自定义指令功能,我们已经用过一些内置指令比如v-model, v-show等,那么今天我们将使用指令去封装所有与flex有关的代码。
指令可以用来操作dom,一个指令其实就是在不同时机(钩子函数)去操作dom的方法
// 注册一个全局自定义指令 `v-autofocus`
Vue.directive('autofocus', {
// 当被绑定的元素插入到 DOM 中时执行
inserted: function (el, binding) {
// el为当前dom
// binding是一个对象里面包含指令名,modifier, srg以及表达式等等
}
//当然还有其他钩子函数
})
若你从未接触过指令,不要担心,它很简单,你可以请先阅读 官方教程,因为此文章是技巧经验分享,而不是Vue教程!
CSS flex回顾
那么在开始之前我们快速回顾下flex的一些属性以帮助我们更好的设计指令。
flex盒子由flex容器(以下简称容器)和内部的子flex item,(以下简称item)两部分组成。
flex容器用于指定其子item在容器内的排列方向,如何排列,对齐,是否换行等等;
item用于指定自身在容器内如何对齐,当然更重的就是自身如何伸展和收缩,排列顺序,这几个才是真正能体现弹性的属性;
我们总结一下上述的属性,因为在写指令时要用到,如果你已经会了,下面的内容对你来说有点啰嗦,请直接移步至指令设计部分.
首先我们先要区分出哪些属性用于容器,哪些用于item.
用于容器的属性:
display:
声明为flex容器
flex: 块级flex容器inline-flex: 行内flex布局
flex-direction
指定item的排列方向:
row: 横向排列itemcolumn: 竖直排列item
flex-wrap:
当在排列方向上没有足够空间时,换行排列item(想想下做广播体操时,一行人占不满就多排几行的场景)
justify-content:
控制item在容器中如何排列:
flex-start: item统一靠着容器头部排列,横排时为左侧,竖排时为顶部flex-end: item统一靠着容器尾部排列,横排时为右侧,竖排时为底部center:所有item都集中在容器中心space-between: item向着容器两边均匀排列分布,每个item之间相隔有均等的空隙(如果item没有占满容器,容器有剩余空间),容器两侧的item与容器无空隙space-around: 与space-between类似,只不过容器两边的item与容器有均等的空隙
align-item:
控制如何对齐元素,对齐方向与排列方向相反:
strech: item完全填满父容器flex-start: item靠着容器头部排列,横排时为顶部,竖排时为左侧flex-end: item靠着容器尾部排列,横排时为底部,竖排时为右侧center: 所有item都处在容器中心线对齐space-between: item向两边均匀分布,每个item之间相隔有均等的空隙(如果item没有占满容器,容器有剩余空间未占用),容器两侧的item与容器无空隙space-around: 与space-between类似,只不过容器两边的item与容器有空隙
其他关于item专用的flex属性,出于时间问题,这里不再赘述了,可参考下面的官方文档 developer.mozilla.org/zh-CN/docs/…
设计指令
指令其实是一个方法,vue合适的时机调用我们的方法并传入指令参数和节点,我们拿到参数后去处理节点等. 现在我们开始设计我们的指令
指令名与执行时机
-
选取执行指令的时机 根据你的项目需要来指定,在本章教程我们仅在
bind, update时执行,因为我们目的用于修改样式且不涉及添加listener等操作,且无需在销毁时做一些清理工作.bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
-
我们根据使用dom对象来将指令分成两个:
v-flex:用于容器v-flex-item: 用于item 让然如果你想简写,也可以写成v-flx, v-itm,但v-前缀是Vue要求的不可省略
-
传参并从指令取值
请先学习指令参数教程
从官方文档中得知我们可以接收多个参数,那么本文我们仅关心如下参数:arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar中,修饰符对象为{ foo: true, bar: true }
设计modifier
从上面我们得知modifier类型为boolean.
- 有一种使用场景就是:属性值是互斥的,只可选一个,这刚好对应了
true和false; - 另外一种场景就是用于指定某个属性的值,很可能这个属性有多个值可选,且该属性频繁使用
- 当然
modifier可以代表一种预设名,通过预设名来添加一套css代码,
因此我们可以这样设计:
-
取值互斥的
flex属性有:-
display: inline-flex | flex
inline(行内)和flex(块级)从来都是互斥,默认flex,所以默认代表false,inline代表true,我们可以将inline作为modifier,像下面这样:v-flex.inline等价于{ diaplay: inline-flex; }那么js代码也比较简单,直接添加
style即可Vue.directive('flex', { bind: function (el, binding) { let { inline } = binding.modifiers //判断是否有inline的modifier el.style.display = inline ? 'inline-flex', 'flex' } }) -
flex-direction,两者取值也是互斥,要么横排(row)要么竖排(column),横排是默认,因此设置column作为modifier,如下:v-flex.column等价于{ flex-direction: column; }注意:
direction还可以反向排列,这边我们将reverse作为modifier来标记反向:
v-flex.reverse等价于{ flex-direction: row-reverse; }
v-flex.column.reverse等价于{ flex-direction: column-reverse } -
flex-wrap的取值也互斥,类似地:
v-flex.wrap等价于:{ flex-wrap: wrap }
-
-
使用某个属性值作为
modifier
我们注意到justify-content和align-items是我们使用比较频繁的属性,我们可以将其设置modifier以便快速添加样式,就像下面这样:v-flex-justifyFlexStart等价于{ justify-content: flex-start; }注意到了吗?这样的写法首先字数太长,不便于记忆,更不方便后续开发人员接手与维护。其次,从指令里难以预测到最终的UI效果。因为justify和align并不能直观告诉我们排列方向
我们发现
justify-content和align-items虽然取值一样,但是他们刚好代表相反的方向: 水平或者竖直
因此我们可以用**语义化的modifier**代替原始css属性:- 我们将水平的排列(或者对齐)用
toLeft, toRight, toCenter标记 - 将竖直的排列(或者对齐)用
toTop, toBottom, toModdile标记 - 另外还有个特殊的
stretch为什么我们没有处理?原因是他是align-items的默认值,无需特别处理 - 同样的还有
space-between和space-around,不要把这两个漏了,他们仅适用于align-items
通过上述语义化
modifier,这样我们就可以直观的从指令中预测出UI的布局使用者不需要知道
modifier究竟对应justify-content和align-items哪一个,他们只需要关心朝哪个方向排列即可。因为我们已经在代码里handle下:- 当你的
direction为row时,toLeft...一组对应justify-content;toTop...一组对应align-items - 当
direction为column时,相反
哦,对了,我们还需要一个额外的
modifier,那就是center
在很多场景中,我们都会使用直接用flex布局,让一个子元素在容器里始终水平竖直都居中,比如对话框等等,这可比因此我们这样设计:v-flex.center等价于{ justify-content: center; align-items: center; } - 我们将水平的排列(或者对齐)用
-
预设
你可以根据你的需要指定预设,比如我有时候时候希望flex容器的宽度为包裹内容的宽度,比如一盒横向滑动的swiper,因此我增加fitContent作为一个modifier,以添加如下预设css代码:v-flex-fitContent等价于:{ width: fic-content; flex-basis: auto; }或者添加一个预设
noScroll以禁止超出容器:
v-flex-noScroll等价于:{ overflow: hidden; }
当然你还可以根据项目需要来添加更多预设。
我们还剩余arg和expression没有使用,由于modifier已经涵盖了大部分属性,在本文里看似已经不需要了,当然你也可以充分利用它,根据你们项目来使用。
- 比如你希望由
flag控制flex指令的启用与禁用,那么你可以这样写v-flex="isSupportFlex"并在代码里判断flag。 - 特别地,
expression还支持字面量对象,通过传入对象即可更大限度的拓展我们的指令功能,比如媒体查询等等
代码实现
实现方式为直接操作css,比较简单,具体代码可参考这个repo:
github.com/BingLee1994…
另外还有item指令,思想都一样,本文那就不再赘述了。
更多例子请参考:
github.com/BingLee1994…
总结
本文使用了指令来封装css flex
- 设计指令要选取好合适的时机(钩子方法)
- 本文大部分属性均通过
modifier来实现,因为modifier可用于那些互斥的,二选一的css属性,还可用于启用/禁用某些预设,亦或者直接是某个属性的值. expression用于向指令传递更丰富的数据,甚至可以支持传递字面量对象,方便我们拓展指令功能.arg可指定参数名,用来说明的expression的用途等等.
那么还有其他场景也可以使用指令,最常见的比如加loading动画,loadingSpinner等等
或者使用IntersectionObserver来创建懒加载的lazy-image等等,不过特别注意的是,对于需要添加event listener的指令,不要忘了随着组件销毁,也要及时清理listener.
来涂鸦工作: job.tuya.com/