Pixiviz的主页经过了N多次的迭代,到现在终于是迭代到了一个让我非常满意的版本。
今天恰逢有时间,来分享一下这个主页的CSS设计。
设计概念
和一般网站使用的单栏布局或侧边栏 + 主内容式的布局不同,我希望在PC端上给用户呈现一个设计风格完全不同的、极简化的Landing页面。
我只想突出站点的主要页面入口,只给用户最直接地呈现用户所需要的元素。
基于Pixiviz的业务,站点Landing其实只需要展示三块内容就行了,分别是Banner、Search和Rank,这是站点内页的主要入口。
为了方便后续的功能集成,我把Banner设计成了可点击展开,这样可以放置一些简单的设置项。未来有必要在Landing堆叠更多功能,也可以充分利用“切换”的思想和设计模式来堆叠更多的要素。
这样很多内容就可以隐藏在页面下,深度用户能够找到他们想要的功能,而普通用户也可以很直观的进入到各种内页。
那么在这种设计下,Landing页引出了两个难点,第一是Banner的点击展开动画,第二是背景图。
这里还需要提到的一个背景是,Pixiviz本身是需要对各种浏览器做兼容的。
动画
动画这个方案只做了一版,一版效果就不错,后面没有改动过。
总体的动画设计是很简单的,从技术层面来说,是对CSS中transition属性的灵活应用。
展开很显然是只需要改变max-height,配合transition就能完成一个动画,但是整个动画流中,我需要让下面两个模块能够在展开的同时淡出,所以下两个模块涉及到Vue + Vue的transition组件,配合延迟改变v-if来实现淡出。
内部内容的展开实际上是一个标题的translateY动画 + 淡出,这个实现难度也不大,使用setTimeout对整个动画流程做好控制即可。
因为主体框架是Vue,使用JS配合transition做这一套动画是最好的方案,没有必要用纯CSS去实现。transition本身是一个很强大的属性,配合JS是可以完成很多简单动画效果的。
剩下的工作就是结合media-query对不同的视口宽高做调整,max-height这个东西我采用的是写死的固定值,其实可以用计算值,但是写死数值对于保持Landing这一页总体布局的稳定十分重要。
背景图
第一阶段 - 垃圾
背景图经历了三个阶段,第一个阶段完全是一个垃圾实现,因为非常不优化。
实现方式是很直观且很简单粗暴的,即切四张图,三个框对应三个框,然后再加一个Banner展开的背景图。这是一个很直接切人人都能想到的方案。
这个方案之所以成为垃圾,是因为有两个很致命的地方:
1、四张图,而且为了保证1080p屏有较好显示效果,图的尺寸还不能压得太厉害,以至于站点的首次打开速度很糟糕。即使加上了Lazyload和Webp优化白屏优化总体的Loading时间,但是实际上结果还是不理想的。
2、不能很好地兼容各种分辨率,因为图你只能按照某一个分辨率下的三个块的间隔来切,但是当用户换成手机等其他设备浏览的时候,体验就变得很糟糕了。
所以这个方案很快就被改掉了,首先要保证加载,一张图才是最好的实现方案。
第二阶段 - Hack
一张图不去切,很粗暴的做法就是利用媒体查询,对各种分辨率做特殊化的适配,通过大量的媒体查询规则,配合CSS的background-size、background-position去完成类似于这个切割的效果。
由于四个div加载的是同一张背景图,这个背景图只需要被加载一次,优化掉了3张图片的请求。这解决了上面提到的第一个问题,即Loading时间过长的问题,结合其他优化,整个页面的加载速度有了质的提升。
但是副作用也很明显,因为切图本身是根据三个块的大小来切的,设置成cover之后在其他分辨率下无非也就是会有一些拉伸,但是三块背景总体的位置关系是不会有明显问题的。
但是background-position + background-size要明显更不理想,它不能很好地兼容各种分辨率,在各种分辨率下保持三块背景的位置关系。
很快这个方案也被弃用了,我开始探索新的方案,即用上层的div做蒙版,让下面的一张完整的背景只在上层指定的div中透出。
第三阶段 - mix-blend-mode
说到蒙版,CSS提供了mask,但是在这个场景下不适用。我们需要的是让div作为蒙版来抠出背景,这个div本身是动态的,而mask需要传入静态的内容。
在探索的时候我注意到了mix-blend-mode这个神奇的属性,我们需要把整个Landing页在Z轴上进行拆解、组合,结合mix-blend-mode的特性达成我们要的效果。
首先在层次结构上,我们要做三个层。第一层是顶部的内容,包括文字、控件。第二层是蒙版层,用于抠下面的背景。第三层是背景。
其中第二个层需要依赖mix-blend-mode生成,然后同样也利用mix-blend-mode的特性去抠背景。
mix-blend-mode在浅色模式下可以利用screen去抠出背景,screen的计算模式是如果图A的像素比图B同位置像素灰度值高,那么取灰度值更高的那个像素显示出来。
黑色灰度值是0,白色灰度值是255,所以我们可以做一个很简单的设计,让背景中需要显示出来的地方对应的蒙版位置是纯黑色,而需要遮盖掉的地方改成纯白色。
这样一来最终的结果就是蒙版白色的地方是白色,黑色的地方本质上等同于透明,显示出背景图。
我们利用mix-blend-mode的normal模式,直接叠加纯白背景和上层三个黑色的div,形成蒙版层,蒙版层用screen模式和背景混合,得到最终效果。
对于深色模式,深色模式是反过来的,我们用darken模式去混合蒙版和背景,蒙版中白色的地方不会被加深颜色,而黑色的地方在darken处理之后会变成纯黑色。
之后我们需要做的就是对其背景色,我们的背景色用的是简单纯色,所以我们可以通过对整个Landing这一块上一个filter去对齐颜色。
这么做看似做完了,但是mix-blend-mode有一个问题,它会把子层叠上下文的内容一并计入混合运算,这意味着这你没有办法去做text-shadow、box-shadow,这很影响页面美观。
所以我们需要做一个叠加层,在这个叠加层里渲染所有的真实上层内容。整个Landing实际上拆分了上下两个层叠上下文,一个是利用mix-blend-mode混合渲染出来的背景,另一个是上层真实内容。
构建新层叠上下文渲染上层真实内容很简单,整个Landing本身是一个限定好了宽高的box,我们只需要叠一个设置了position: absolute;的box上去即可,然后正常在里面渲染真实内容。
因为盒模型本身就是这么设计的,这么做不会影响到真实内容和背景之间的位置关系,一切都是一样的。背景部分我们只需要做一个宽高和真实内容相同的占位符,背景色设置成浅色/深色模式下对应的蒙版色就可以了。
之后再利用Vue的组件通信同步真实内容和蒙版占位符这两个层的动画,整个Landing的新方案就完成实现了。
不足
mix-blend-mode是目前CSS现有特性能够达到的理论上的最好效果,而且未来整个Landing的背景其实可以换成任意图片,
但是它也有不足,mix-blend-mode和transition的结合在某些浏览器下不完美,比如一直都比较落后的Safari,它并不一定能够在动画过程中完美渲染整个mix-blend-mode的效果,可能会出现闪屏。
对于圆角,圆角边缘产生因为硬件特性和计算特性产生的毛刺,或者说边缘略微溢出的问题需要用巧法解决,对于蒙版来说这个问题还是有点致命的。
总结
多看大站一些花哨的页面怎么用的CSS,多去挖掘CSS的冷门属性,可以发掘出不少很实在的东西,得到更好的解决方案。