CSS vs. SVG:任意图形UI组件

发布于 大漠

在这个系列教程的前两篇文章中,我们比较了CSS和SVG创建图形文本复选框和单选按钮的技术与效果。在这篇文章中将介绍CSS和SVG对比技术中的另一个技术——创建图形UI组件的技术。

具体地说,我们将要讨论的是如何创建圆形菜单效果,因为这是非矩形UI组件的典型案例。

有关于圆形菜单的相关教程、插件和工具:

让我们一起深入探讨这方面的技术。

首先要介绍的是CSS能不能创建非矩形的几何形状......

CSS Shapes

所有的HTML和CSS是一个矩形盒子。每个HTML元素就是一个矩形的盒模型。甚至图形和文本都是矩形。

几年前,CSS出了一个CSS Shapes规范,这样可以很轻易的实现非矩形的形状(比如,圆、椭圆和多边形等形状),并且可以根着非矩形的形状,让文本环绕在非矩形形状的周围,从而打破了文本只能实现矩形流的限制,使网页设计更有可能接近印刷设计的布局。

CSS Shapes(规范)并不能使元素的渲染成任意不规则形状外观,仅能影响文本内容流动形状。就算是元素内容围绕非矩形流动,元素本身仍是呈矩形状,除非你采取进一步技术手段来改变这种状况。在接下来我们将要讨论的就是如何做到这一点。

有关于CSS Shapes相的教程,可以点击这里阅读

几何形状

其他类型的CSS形状——不要和上面提到的CSS Shapes混淆。这里所讲的几何形状指的是使用不同的CSS属性创建的几何形状,这样的创意其实并不适合我们需要的。

在HTML元素或伪元素上使用box-shadowborder-radius和其他的边框属性,可以在页面上实现不同的几何形状。但这些形状不是真正的形状,只是让你在视觉上感觉是几何形状。可以说这些形状是似的形状,跟元素的矩形或圆形是完全不同的。并且这些形状都基本上依赖于定位拼合出来的几何形状。

例如,使用border-*属性,你可以使用一个元素创建任何一个方向的三角形。只需要把元素的widthheight设置为0,如果你将元素的边框颜色不设置为透明的颜色,你可以看到下面的动画展示了如何使用一个元素制作三角形:

三角形

因此,在CSS中通过这两个简单的步骤,只需要使用几行CSS代码就可以创建三角形形状。选择你想要哪个三角形,并且将其他边框的颜色设置为透明颜色。所以,如果你想要一个向上的三角形,你只要让底部的颜色值留着,而其他边框的颜色设置为透明颜色。

可以看看这个示例,当你鼠标悬浮在元素上时,你可以看到三角形形成的整个过程。

使用border-radius也可以制作出不同的形状,例如,使用一个空的div和其两个伪元素,可以创建一个心形形状出来。伪元素制作两个圆,然后使用定位,让他们重叠在一起,而形成一个心形的形状。下面的示例中演示出CSS制作心形的效果。如果你改变它的位置,你可以把它们分开,看看会发现什么。

使用CSS属性将多个元素组合起来,可以制作出很多复杂的形状。CSS-Tricks提供了很多个使用CSS创建图形形状的案例。如果你感兴趣,可以把代码拿出来玩玩。这里拿制作五角星形状的特殊案例来做阐述。

#star-five {
   margin: 50px 0;
   position: relative;
   display: block;
   color: red;
   width: 0px;
   height: 0px;
   border-right: 100px solid transparent;
   border-bottom: 70px  solid red;
   border-left: 100px solid transparent;
   transform: rotate(35deg);
}
#star-five:before {
   border-bottom: 80px solid red;
   border-left: 30px solid transparent;
   border-right: 30px solid transparent;
   position: absolute;
   height: 0;
   width: 0;
   top: -45px;
   left: -65px;
   display: block;
   content: '';
   transform: rotate(-35deg);
}
#star-five:after {
   position: absolute;
   display: block;
   color: red;
   top: 3px;
   left: -105px;
   width: 0px;
   height: 0px;
   border-right: 100px solid transparent;
   border-bottom: 70px solid red;
   border-left: 100px solid transparent;
   transform: rotate(-70deg);
   content: '';
}

制作一个简单的图形形状,使用了相当多的代码,不是吗?除了border属性、伪元素和绝对定位之外,你还需要使用CSS的transform。像这些图形,如果使用上面介绍的方法来制作,并不适合,既不实际,而且不靠谱,不灵活。他们甚至不是真正的形状,缺乏语义,屏幕阅读器也无法识别。例如上面创建的心形,屏幕阅读器是无法识别出其是一个心形。如果你只想要一个小三角形,做为气泡提示方向,那么使用CSS来制作三角形是有意义的。然而,如果你仅想使用CSS的borderbox-shadow等属性来创建一些复杂的图形形状,这绝对不是最好的方案。在空元素上使用CSS制作的图形都是欺骗用户得到的,如果在实际使用情况之下,使用CSS属性创建任何形状,仅仅是为了给用户提供一个假象。因此,我想通过更多的实例来告诉大家,可以使用CSS来制作更多不确定的图形形状。特别是今天要介绍的内容,如何使用两种不同的CSS方法创建圆形菜单。在这里我们将加入SVG的方法。通过实例看到这两种技术的利弊。

扩展阅读

使用CSS的clip-path创建圆形菜单

在CSS中,可能使用CSS的clip-path属性剪切出不同的形状。剪切是对一个图形的操作,允许您隐藏整个元素或元素的部分。剪切的元素可以是任何容器或图形元素。元素的显示或隐藏部分由一个剪切路径决定。

clip-path

clip-path是如何工作的?(相关教程这里查阅。)

clip-path属性是一个新属性,可以说是clip属性的替代品,它改进了clip的属性特性。clip属性使我们可以剪切元素,让其只显示一部分内容。而且只有clip属性支持rect()函数,意味着只能给元素做一个矩形剪切。尽管这个属性已弃用了,但它仍然能在所有的浏览器上工作,甚至包括IE6或者更早些版本的浏览器。如果你有兴趣,可以通过这篇文章clip属性做深入的了解。clip-path属性允许我们使用形状函数对元素做非矩形的剪切。其包括四个不同的图形函数:circle()ellipse()inset()polygon()。每个函数定义的形状都是应用一些点来组成。以下是使用clip-path属性的一些示例:

clip-path: polygon(15px 99px, 30px 87px, 65px 99px, 85px 55px, 122px 57px, 184px 73px, 198px 105px, 199px 150px, 145px 159px, 155px 139px, 126px 120px, 112px 138px, 80px 128px, 39px 126px, 24px 104px);
clip-path: circle(70% at 0% 50%) padding-box;
clip-path: inset(10px 20px 30px 40px round 10px) margin-box;
clip-path: ellipse(farthest-side closest-side at 25% 25%);

这些图形函数可以将一个HTML元素裁剪出你想要的任何图形形状。这些函数当中属polygon()函数是最有意思的,因为它可以让我们有更多的选择项。有很多多边形形状,仅使用circle()ellipse()inset()函数是无法实现的。clip-path属性还接受SVG元素引用定义的图形形状,这样可以让我们具有做出更多图形形状的能力,克服polygon()函数的限制。例如,使用CSS的基本图形函数是无法实现一个圆弧的图形形状,但在SVG的path中使用clip-path就显示非常简单。

.element {
    /* … */
    clip-path: url(#svgClipPathID);
}

有关于SVG中path语法和使用方法超出了本文要介绍的范围,如果你对这方面知识感兴趣的话,你可以点击这里这里了解。下面这个示例演示了clip-path属性使用方法。其中一个是使用CSS的剪切路径,另一个是使用CSS的polygon()函数剪切多边形的图形形状效果。

有关于clipclip-path的更多介绍,可以点击这里这里了解更多的知识。

使用clip-path改变一个元素的形状,实际上就是在视窗的可视区域改变它的形状,用来定义元素哪些部分是可见的。元素自身仍然是一个矩形。

由此可见,clip-path属性可以用来创建非矩形的Web UI组件,其中圆形菜单就是一个很好的例子。

实际上,使用clip-path创建圆形菜单是非常的简单:

  • 所有的菜单列表项,其文档流位置依旧是一个矩形。
  • 根据需要将菜单列表项剪切成需要的图形形状。图形的角度根据你菜单列表项的数量来决定。例如你有一个圆形的菜单,它包括六个列表项,那么每个菜单列表项的角度就是60deg
  • 根据剪切的区域定位,旋转所需的列表项,形成一个完整的圆形。

下图显示了整个制作过程。注意元素放置在相同的初始位置,最重要的是彼此的层叠,然后根据不同的剪切做对应的角度旋转。

clip-path

由于polygon()函数并不能让我们在两个点之间创建一个弧形,此时,我们需要参考SVG形状,使用clip-path做为剪切路径。如上图所示的菜单列表做成扇形是这样实现的:

<svg height="0" width="0">
  <defs>
    <clipPath clipPathUnits="objectBoundingBox" id="sector">
      <path fill="none" stroke="#111" stroke-width="1" class="sector" d="M0.5,0.5 l0.5,0 A0.5,0.5 0 0,0 0.75,.066987298 z"></path>
    </clipPath>
  </defs>
</svg>

CSS中像这样引用:

.menu li { 
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  clip-path: url(#sector);
}

然后每个菜单列表根据自己的角度进行旋转:

.one {
  background-color: #A5E2F3;
  transform: rotate(0deg);
}
.two {
  background-color: #86d8ef;
  transform: rotate(-60deg);
}
.three {
  background-color: #66ceeb;
  transform: rotate(-120deg);
}
.four {
  background-color: #47c4e6;
  transform: rotate(-180deg);
}
.five {
  background-color: #27b9e2;
  transform: rotate(-240deg);
}
.six {
  background-color: #1ba4cb;
 transform: rotate(-300deg);
}

上面讨论的是使用CSS的clip-path创建一个圆形菜单。

话说,这种技术使用不广泛,主要是因为在浏览器中存在一些问题,就算是在Webkit和Blink内核浏览器中也不能避免,至使圆形导航至今也无法使用。有关于CSS的clip-path创建圆形导航,我建议您阅读CSS-Tricks上的这篇文章,这篇文章详细介绍了制作圆形菜单的细节。

使用CSS的transform创建圆形菜单

使用CSS创建圆形菜单还有一种方法,那就是使用CSS的transform

这种技术相对而言更为复杂,但是让我们看看它是如何工作的,这样让你有一个更为清晰的思路,也能让你做出更为明智的选择,使用哪种技术更为适合。

除了前面介绍了CSS的基本图形函数和路径剪切功能之外,要让一个矩形元素变形只能使用CSS的transform中的skew()

通过对元素做扭曲,你可以将一个矩形形状变成一个平行四边形。这是为每个菜单列表项创建想要的倾斜角度的第一步。

和前面介绍的一样,根据你菜单列表项的数量计算出需要的菜单列表项倾斜的角度值。首先元素要绝对定位,让它们层叠在一起,然后通过一些基本的数学公式计算出倾斜的角度值,然后使用transform:skew()应用到所有的菜单列表项上。

接下来,旋转对应的菜单列表项,让它们不再重叠,并且根据角度值将菜单列表项构成一个完整的圆形,类似于前面剪切所介绍的一样。

但是这样一来,每个菜单列表项元素依然是一个平行四边形。因此为了让平行四边形有一个弧形,需要将它们放在一个容器内,并且给容器设置border-radius值,将多余的部分溢出隐藏。

同样,实践能证明一切,下面这个动画演示了整个制作过程:

transform

使用CSS的transform:skew()overflow从外观上仿照了一个圆形的菜单效果。

值得一提的是,导航菜单列表项做了skew()处理,所以它们的内容(比如说使用<a>标签包裹的内容),也会做扭曲处理,这样就会变得不可读。为了解决这个问题,你需要对文本(比如<a>做一个反向同等角度的扭曲处理)。

上面的动画生动的演示了如何使用CSS的transform制作一个圆形菜单。其实这个动画演示记录了我在Codrops上的一个制作圆形菜单的整个过程,如果你感兴趣,可以看看这篇文章

不用多说,这种技术更为复杂,任何角度和位置的变化都将需要做大量的计算来调整相应的菜单列表项。如果你使用Sass,那么你可以通过一些变量和一个循环来简化数学计算,但对于创建一个自适应的圆形菜单,仅使用CSS还是相当麻烦的,难道你不觉得吗?

如果你仍然要使用CSS来创建一个圆形菜单,我强烈推荐上使用上面介绍到的clip-path或使用SVG。

接下来,我们将一起探讨如何使用SVG来创建一个圆形菜单。但首先,让我们哪了解使用SVG创建一些基本图形形状和CSS有哪些不同。

SVG中的图形形状

SVG是XML,SVG标记使用类似于HTML元素标签,但其用于创建图形形状更加灵活,更加强大。毕竟,SVG是专门用来创建形状和图像的,而且SVG创建的图形形状更具语义化,因为它可以使用一些有语义的元素,这些元素可以让你创建你想要的任何图形,同时仍具可读性。

几何形状

在SVG中主要有六个元素来创建不同的基本图形形状:<line><polyline><rect><circle><ellipse><polygon>。他们都是根据形状来命名,这样他们的名字都是不言而喻的。

这些形状是由一组点构成,比如poylinepolygon图形形状。比如矩形元素,可以通过设置他们的widthheigth和位置来创建,而圆形或椭圆形,可以通过圆心位置和半径值来创建。

还有另外一个元素<path>,这使我们可以画出任意形状的路径,为我们提供CSS不能提供的自适应的图形形状。

使用SVG元素创建图形形状非常简单,其中最简单的方法就是使用类似Adobe Illustrator等图形编辑器绘制出你需要的图形路径,然后将这些图形形状导出成SVG代码。

下面的示例就是SVG绘制的不同图形形状:

我想使用<polygon>元素创建一个五角星。还记得使用CSS创建这样的图形需要多少代码吗?SVG的代码是这样的:

<polygon fill="#fff" stroke="#FD4C70" stroke-width="10" points="350,75 379,161 469,161 397,215 423,301 350,250 277,301 303,215 231,161 321,161" />

现在,哪个更好看呢?我想大家都会喜欢使用SVG这个,对吗?SVG创建图形不仅比CSS创建简单,而且还具有CSS不具备的一些优势:

  • SVG图形是真实的内容,实际元素绘制在屏幕上,而不是像空的div没有任何内容,导致不具阅读性等
  • 屏幕阅读器可以读到SVG图形
  • SVG图形更具语义化(SVG的元素和属性)
  • SVG图形可以通过图形编辑器编辑,而CSS形状不能
  • SVG图形有很多强大的属性,能让你更好的控制图形形状(比如strokefillstroke-width等)

所以说,如果你想在Web页面中创建想要的几何图形,使用SVG是首先方案。在继续往下之前,让我们来看看SVG中<path>的示例效果:

上面的示例包含了两条路径,一个是灰色的,一个是粉色的。

灰色的路径看起来像一个使用<polyline>绘制的折线,事实上,任何简单的形状都可以使用SVG的<path>元素来绘制。

SVG中的路径有一组命令,可以让你绘制出任何你想要的形状。这些命令包括值线命令(绝对或相对的)和一个弧形命令(绝对或相对的),以及你可以移动你的光标在画布的任何地方绘制路径的开始位置和一个命令能让你边接关闭路径的最后一点,相当于二次曲线命令一样。

这些命令是用字母来表示SVG中路径的属性:数据定义路径。命令包括MILAz等。你可以通过SVG规范找到所有路径的属性与命令,以及他们所对应的解释。

这些形状可能制作一些UI组件。例如在<a>链接上制作任何形状,允许这些形状做为页面上的链接。

通过SVG的直线或弧线路径命令,和一些简单的数据计算,可以轻易的制作一个圆形的菜单效果。

使用SVG创建圆形菜单

使用SVG创建圆形菜单,只需要SVG的<path>为每个菜单列绘制一个扇形形状。

你可以在图形编辑器是绘制出来,然后导出SVG代码运用在菜单列表项上,或者你可以手工编写<path>值,将多余的部分溢出隐藏。

画一个形状,你将需要具备下面的一些条件:

  • 将光标移动到你想要开始绘制图形的地方
  • 从一个点到另一个点画一条线(这是扇形的一部分)
  • 根据菜单列表项数目决定弧形的角度
  • 绘制另一条直线关闭扇形

点击下面示例中的“播放按钮”来演示一个扇形是如何绘制出来的:

绘制扇形需要三个点,可以使用路径命令达到以上的效果。确定这些点的位置,我们需要知道其放置在圆形菜单中的哪个位置。所有项目在同一个位置,然后通过类似于CSS的clip-path方法对菜单列表项进行旋转。点击示例中的播放按钮,可以看到整个效果是怎么实现的。

通过上面的示例,可以了解菜单列表项是如何通过三个点位置绘制出扇形,并且是如何定位。

第一个点是菜单的中心,根据菜单的高度和宽度来确定这个中心点。这个点的XY坐标值等于宽度和高度值的一半。

第二个点是菜单的边缘,这意味着它是沿着Y(Y轴一半)和X轴(菜单宽度)定位。

第三个点用来画弧线,这个有点棘手,需要一些数学知识。通过菜单列表项的数量,计算出每个菜单列表项的角度值。这个角度值是用来计算第三个点的XY值。

第三个点的计算介绍超出来本文的介绍范围。有关于如何计算出第三个点的坐标值以及必要的命令来画出路径和如何旋转,我在以前的一篇博文中做过深入的探讨,如果你感兴趣,你可以阅读这篇文章,你将会了解到更多的知识。

在每个菜单项<a>上包裹一个<path>,并且使用SVG做一个循环。

不过有一些细节需要注意:

  • CSS的clip-path技术对于SVG同样的需要。唯一不同的是路径没有做别的,仅用来做菜单项。然而,你需要的是知道如何绘制路径,以便正确地将它使用到你的项目中,不管是在CSS或SVG中。
  • 使用SVG路径需要知道坐标点变化。而不是绝对值,你需要使用区间值[0,1],浏览器能够使用其坐标系统绘制需要的形状。
  • 不管使用CSS或SVG制作一个圆形形状,你都需要一些数学知识。而且两种方法都有其利弊。
  • SVG制作的圆形菜单在所有支持SVG的浏览器都能正常工作,同时clip-path支持度不是最好的。

自己创建一个圆形的SVG菜单可能需要一定的时间做计算,不过几个月前我创建了一个工具,允许你可见即可得创建圆形菜单,然后将下载下来的代码放到你的页面中。

总结

在CSS的clip-path属性出现之前,要创建非矩形UI组件几乎是不太可能,它的出现之后,让这一切变得更容易。

上面提到的使用CSS的transform创建圆形菜单目前不是最优秀的方案,所以我强烈建议不要使用。我希望能将它与clip-path技术做比较,让你更清晰的了解到为什么它不是最佳的方案。也让能你在CSS和SVG做出更好的选择。

这里主要介绍的是制作圆形菜单,但这里运用的概念与技术适用于创建任何不规则的图形UI组件。

SVG可以创建非矩形形状,而且这些形状可以更好的结合我们的设计,并且运用到页面中。

事实上,本系列的下一篇文章(这将是此系列的最后一篇文章),将向您展示如何灵活的使用SVG形状,使用不同的方式更好的改善一些常见的UI组件。

我希望你喜欢这篇文章,发现它有用。感谢您的阅读。

本文根据@Sara Soueidan的《CSS vs. SVG: Shapes and Arbitrarily-Shaped UI Components》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://blogs.adobe.com/dreamweaver/2015/09/css-vs-svg-shapes-and-arbitrarily-shaped-ui-components.html