这篇文章取自SmashingMagazine, 作者Bryan James前段时间做了个独立站,名叫“In Pieces”,在前端和设计圈里火了一阵子。这篇文章讲述了他是怎样利用CSS的Clip-Path的属性来构建这一切的。
原文链接:www.smashingmagazine.com/2015/06/02/the-making-of-in-pieces/
基于Web的展示形式经常被用来推广各种高端产品。CSS的Clip-Path
属性很少有人知道,在我(指作者,以下同)学习了这个属性之后,我用了五个多月的时间构建了自己的线上产品In Pieces,它旨在让人们去了解世界上30种濒危物种。这个网站不光在展示这些动物,用户还可以深入了解一些数据、下载壁纸、甚至获取一系列的海报。此外,网站支持移动设备。
In Pieces始自代码实验而非一个宏大的计划。2014年年中我接触了CSSClip-Path
中的Polygon
属性,觉得它具有非常大的前景。几个月过去了,我发现人们好像都没有怎么在Web上应用它,可能是大家都在使用SVG、canvas以及WebGL的原因。我觉得Clip-Path
是个好机会,可以创造点之前人们没有想过的新花样。
如果你没有接触过Clip-Path
,Dirk Schulze有一篇教程可以看看,非常不错。举个栗子,讲一个常见的四角div
转成一个三角形可以通过下面的办法:
.polygon-div { -webkit-clip-path: polygon(0% 0%, 50% 100%, 100% 0%); }
我想知道的第一个事就是polygon属性与CSS过渡属性能不能结合。幸运的是,他们可以在一起。
.polygon-div { background: #f00; width:100px; height:100px; -webkit-clip-path: polygon(0% 0%, 50% 100%, 100% 0%); -webkit-transition: 1.8s cubic-bezier(.7,.3,0,1); } .polygon-div:hover { -webkit-clip-path: polygon(20% 59.35%, 30% 58.95%, 40% 61.08%); }
了解了技术特性,我便开始构思项目。我的想法是要让展示的多边形具有美感,要有一种视觉上的亲和力。也许多边形的动物是个不错的尝试。首先,我搞清了一件事:多边形在艺术方面可不是一个新鲜事物,在Behance这样的艺术作品集网站上,随便搜一下“Polygon animals”就能找到很多相关作品。这一点对于“创意”这个词来说很重要。我看过关于那些作品的评论,有的人提到“折纸风格”,有的人提到“多边形风格”,但这只是视觉层面的,深入点说,这些构造图面的方式是一种碎片化的处理,它隐喻着事物本身就是“碎片化”的,他们的存在也是“碎片化”的。如果没有想到这个层面,那我的项目只能是个形而上的作品。
将技术融于设计和艺术中是一件非常浪漫的事情。
其他创意也接踵而至,比如我想要每个展示的动物用30个碎片来构成,同时展示的数据也要用碎片来表达。下面放点干货,讲讲我是怎么深入到技术细节的。
每个动物形象都是我亲手创作的。如果你仔细看我的代码就会发现,每个动物都是由30个div彼此堆叠构成,嵌套在一系列的父级div里。然而这30个div又同时隶属于30个动物。
最简单的方式就是在Ai里构图。创作出30个动物确实有点繁琐,但从另一个角度讲也是整个项目中简单直接的一个工作。主要是受到多边形数量上的限制。
接下来我把这些动物示意图放进浏览器中勾画他们的路径。考虑到有30个示意图需要转换成代码,绝望的我需要一个更加速度有效的方式。我设计了这样一个流程:把30个多边形放到指定位置,然后分别移动他们的点对准下面的PNG。有一些线上的工具,比如「CSS Plant Generator」、或者Bennett Feely的「Clippy」,都能帮助我们勾勒clip-path
需要的端点。但是我需要的是更为定制化的工具,因此我用Js做了下面这个功能:
解释一下:
· mouseX
和 mouseY
表示点击时的鼠标位置
· shapesoffset
表示div与浏览器窗口左上角的距离
· shapesmouseX
和 shapesmouseY
表示鼠标在polygon-wrap
中的相对位置
· mousepercentX
和 mousepercentY
计算鼠标在polygon-wrap
中的百分比
· finalmouse
和 normalised
将十进制的百分比转成CSS接受的值
· nodecount
表示我在屏幕上点击了多少次。因为所有的多边形都是三角形,所以这个数字记录从0到3,然后循环回0。
这个函数能够让你在屏幕上点击三次,然后勾出一个三角形的div,三角形的端点就是基于百分比的。你的每三次点击就会生成一个字符串,以浏览器Alert的形式输出位置信息,这样你就能把这些位置复制黏贴进你的CSS里。如下图:
我们需要做的还不仅仅是勾画多边形的路径,我们还需要知道每个多边形的颜色。从Ai里复制每个颜色再拷贝进Sublime里太麻烦了。其实有个应用可以帮助我们:「Sip」,它能够直接吸取颜色放在剪贴板中,随时准备输出给CSS。
这样,我就能够吧30个动物一个一个地用代码表示出来了。但紧接着又一个麻烦出现了。如果你要两个矢量图形彼此紧挨着,他们之间会出现一条虽然微弱但很显眼的线,就像Ai中那样。所以我需要小心谨慎地调整一些点的位置,好让这些碎片互相之间有些重叠。麻烦在于:我怎么知道我要调整的多边形是究竟是哪一个?(译者:这就有点像有个10胞胎的母亲,看着自己的一堆孩子里有一个犯错了,但是母亲怎么才能知道叫那个犯错的什么名字)这时候CSS多边形的另一个优势来了:它们能够像遮罩一样把图像放进去。这样,我做了30个背景图片,每个图片都满是重复的数字。我把每一个图片都顺序地放进相应的多边形里,这样,我就能够直观地看出我要调的多边形是第几个了。
In Pieces很大程度上根植于类名的增删改,其实每个动物都是一样的,本身没有区别。我们只需对父级div改个类名就可以实现CSS的变化。这些动物以数组的形式存储:
var animalList = [‘crow’, ‘vaquita’, ‘tamarin’, ‘frog’, ‘owl’, ‘turtle’, ‘oryx’, ‘iguana’, ‘seahorse’, ‘armadillo’, ‘sloth’, ‘kakapo’, ‘echidna’, ‘penguin’, ‘damselfly’, ‘bear’, ‘parrotfish’, ‘camel’, ‘butterfly’, ‘ostrich’, ‘panda’, ‘tapir’, ‘sifaka’, ‘lynx’, ‘rhino’, ‘peccary’, ‘okapi’, ‘loris’, ‘hirola’, ‘drill’ ];
每当一个新的物种被引用,我都会让它产生这样的变化:
prevAnimal = ( animalList.indexOf($(‘#animalchanger’).attr(‘class’)));
这其中,#animalchanger
是控制物种们的父元素,被设定成animalList
中的某个字符串值。这行代码决定了目前这个动物在物种列表中的索引。这一切都是为了创造newAnimal
变量。比方说,界面中有一个按钮叫做“下一个动物”,它背后的代码是这样的:
newAnimal = prevAnimal + 1;
当然同时还会有其他很多代码伴随着发生,但是新的动物的索引一旦创建,我们就可以把它放在div的类中:
$('#animalchanger').attr('class',animalList[newAnimal]);
现在你知道多边形是怎么做的,同时也知道了通过类名的改变来操作每个物种共用的多边形。下面才是最好玩的:动画!
之前讲过,所有的都是基于CSS的,运动也是一样。为此,我设定了一系列的基础过渡(base transition),好让用户的每个操作都能显示合适的动作。
不过有个事我要提一下,在上面的代码例子中,我们要计算下一个动物是从左进入还是从右进入。
if (prevAnimal > newAnimal) { $('.wrap').addClass('right-to-left'); $('.wrap.left-to-right').removeClass('left-to-right'); } else { $('.wrap').addClass('left-to-right'); $('.wrap.right-to-left').removeClass('right-to-left'); }
这个需要通过SASS的for
循环来实现。
$fluidpolygons: .7,.3,0,1; .left-to-right { @for $i from 1 through 30 { $s: ($i*0.04+0.3s); $t: ($i*0.02s+0.2s); $ct: ($i*0.02s); :nth-child(#{$i}) { transition: -webkit-clip-path $s $t cubic-bezier($fluidpolygons), background-color $s $ct; } } } .right-to-left { @for $i from 1 through 30 { $s: ((31-$i)*0.04+0.3s); $t: ((31-$i)*0.025s+0.2s); $ct: ((31-$i)*0.02s); :nth-child(#{$i}) { transition: -webkit-clip-path $s $t cubic-bezier($fluidpolygons), background-color $s $ct; } } }
看到了吗,有多种多样的速度和延迟属性可供使用。Sass的for循环用来改变过渡的时间和延时效果,取决于多边形的索引。比如说,一个从左至右的运动,第十个多边形会有一个0.7秒的过渡效果外加一个0.4秒的延时。反过来的方向则仅仅是翻转一下顺序——将索引号减去31即可。
动物们不是通过CSS动画移动的,原因是我不喜欢中途突然发生“插进去”的那种突兀的效果。我设定了两个种状态:初级运动和次级运动。
function animalStates(e) { setInterval(function(){ e.removeClass('state-four'); setTimeout(function(){ e.addClass('state-two'); }, 1000); setTimeout(function(){ e.removeClass('state-two'); e.addClass('state-three'); }, 2000); setTimeout(function(){ e.removeClass('state-three'); e.addClass('state-four'); }, 3000); },4000); } function animalStatesSecondLevel(e) { setInterval(function(){ setTimeout(function(){ e.addClass('two-state-two'); }, 1000); setTimeout(function(){ e.removeClass('two-state-two'); }, 1100); setTimeout(function(){ e.addClass('two-state-two'); }, 1400); setTimeout(function(){ e.removeClass('two-state-two'); }, 1500); },3000); }
每个状态实际上都是替换了新的CSS,而多边形只是移动到新的位置、改变下颜色之类的。重要的是,一旦“动画阶段”开始(也就是一个动物完成了它从上一个动物的变形),多边形上一个过渡的时间属性以及延时属性就会被重写。
比如下面的Gif,你可以看到,animalStates
控制着金毒蛙的生囊运动,而animalStatesSecondLevel
控制着眼睛的眨动。
Clip-Path
属性在大多数主流浏览器中都能被支持,是的,除了IE。此外,火狐浏览器也有点小问题,因为他们对于这个属性是基于SVG路径的,意味着对坐标系的变换要在CSS之外完成。这时我的降维(译者:原文用词是Fallback,指的是网页设计中使用了前沿技术的同时还能考虑到老浏览器的用户的做法)方法是这样的:如果用户在他们的浏览器中无法看到那些动物们的过渡、动画、转场效果,那么我就仅仅是给他们一系列的图片,简单地做个幻灯片效果,视觉上其实差不多,这些用户一样能够理解我这个项目的意义所在。
使用Clip-Path
,你不可避免地会遇到一堆问题,尤其跟当转场过渡结合的时候。首先,他们在应用了透明属性的过渡时就不能产生叠加效果——有时候他们干脆就不发生任何变化,就像老电视机接不到信号了。
之前提到过,我是用了一大坨的父div来实现位移、旋转和缩放的。如果不做这些嵌套,光是这些多边形直接的变换就产生了大量的问题,问题多到差点直接让我放弃这个项目。
原则上来说,retina设备支持Clip-Path是没问题的,少量的多边形还没什么问题。但是当有30个多边形的时候,就出现了之前说的透明度堆叠的问题。比如说,你会注意到在非Retina的桌面屏幕上,我在小动物的周围加了一些装饰,让视觉上看起来完整一些,但是在Retina设备上,除非我把它们去掉,否则程序就不运行。
毫无疑问,为了获得良好的性能,我还需要做很多优化工作。之前提到了,Retina上面有一些瑕疵,需要移除一些东西。但是我也做了很多CSS的工作,让其更加整洁,帮助网站运行地更加平滑。
想一下吧,你让浏览器在同一时间处理30个物体的移动,这很可能会出问题。但如果你给一个0.199秒的动画配上一个0.200秒的延迟,其实就等于同一时间只移动一个物体。事实上,如果动画是一个链式结构一个个进行,性能几乎可以提升30倍。
制作主菜单这一环节是我最喜欢的,制作它的方法我还用在了很多别的地方上。
你们能看到的菜单像一个圆圈一样具有宽度和高度的尺寸,但其实整个div是建构在一个没有宽和高的父级div上的。这个位于界面中心的父div包含有30个独立的div表示各个物种,同时每个物种还带有伪元素用来创造交互动作。但是整体其实十分简单,我想给你们展示一下它们是如何放置的:下面是一个非常简单的代码
.hover-detector { div { position: absolute; left: 0; top: 0; margin-left: -35px; margin-top: -35px; width: 70px; height: 70px; @for $i from 1 through 30 { &:nth-child(#{$i}) { $r: ($i*12deg - 7deg); @include transform(rotate($r) translateY(-230px)); } } } }
这里,30个div元素被引用为div
。我使用了Sass的for
循环好让每个div以其中心锚点为参考转移至相应位置。总共30个元素,因此,每12度转个角,然后让元素离圆心偏离230个像素。
类似D3这种工具可以极大地扩展我们与可视化数据之间的交互,脱离方块、圆圈等几何限制,我们可以创造具有触觉的信息。
CSS多边形也可以用这种方式实现,非常简单。在In Pieces的可视化图表中,我用了跟制作主菜单相同的技术,只用了一个单一的div结合clip path的变换,我就可以在圆形表格中放置那些数据和数字。
下面这行代码展示了如何快速方便地制作一个上图那样的极坐标表格:
-webkit-clip-path: polygon(31% 4%, 66% 11%, 84% 36%, 81% 63%, 60% 74%, 43% 68%, 37% 55.5%, 42.5% 47%);
整个项目的标题带有一种刮痕的纹理。这用到了CSS的新技术:给文本加上图片遮罩。
仅针对Webkit浏览器,下面是用来实现这个效果的代码:
.textured-type, .animal-nav-content h2 { background: url('../img/textured-ui/repeat-white.png') center center repeat; background-size: 80px 60px; -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: scratchy .253s linear forwards infinite; } keyframes scratchy { 0% { background-position: 0 0; } 25% { background-position: 0 0; } 26% { background-position: 20px -20px; } 50% { background-position: 20px -20px; } 51% { background-position: 40px -40px; } 75% { background-position: 40px -40px; } 76% { background-position: 60px -60px; } 99% { background-position: 60px -60px; } 100% { background-position: 0 0; } }
它本质上就是融合了基本的CSS文本遮罩技术以及Webkit浏览器的CSS动画,简单地将文字的背景图片快速周期性地移动,创造一种动感的刮痕感。
人们对In Piece的反响令我震惊。我们可以针对“在客户项目中做Webkit-only的网站是否合适”这种问题讨论一整天。然而实际的点击和公众的反馈显得更有意义多了。我觉得未来,CSS的多边形可以有巨大的潜力。