SVG + CSS 实现 Material Design Loading

material loading

相信这个 loading 的标志大家都很熟悉,是不是很和谐?

对着发呆...

额...有毒,看得停不下来。既然,那么神奇,我就好奇地研(goo)究(gle)了一下。

原来它是 Material Design Progress(谷歌网站,你懂得)的一种 —— Circular。

在研究的过程中,发现有大神用 CSS + SVG 在 codePen 上实现了它。接着,就一步步来看这个魔性的 loading 是如何实现的。

SVG

既然,它是一个页面元素,那么,就先看看它的 dom 结构。

<div class="loader">
    <svg class="circular" viewBox="0 0 50 50">
        <circle cx="25" cy="25" r="20" fill="none"/>
    </svg>
</div>

可以看到,结构很简单,是一个 div 标签包裹一个 svg 标签(circlesvg 中的一个预定义形状,后面再讲)。div 大家都很熟悉,那么,svg 是什么哪?

可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。SVG由W3C制定,是一个开放标准。 ——wikipedia

同其他图像格式相比,svg 的主要优势在于:它是可伸缩的,即缩小、放大都不会影响显示的质量。

知道了,svg 标签是什么,那其中的 viewBox 属性又是用来干什么的?

viewBox

The viewBox attribute allows you to specify that a given set of graphics stretch to fit a particular container element. ——MDN

我的理解就是,选中 svg 中的一部分作为内容的显示区域来进行放大或缩小来适应整个 svg 的大小。(如果还是不太明白的,可以查看张鑫旭大神的文章,形象生动)

viewBox 的值是 4 个数字并用逗号分割,分别对应原 svg 图的 x 坐标,y 坐标,宽度,高度。通过这 4 个值就能在原 svg 图中划出一个矩形,然后将它缩放至现有 svg 的大小。

circle

明白了 svg 是用于描述图形,那该如何将图形画于其中哪?

svg 提供了一些预定义形状,除了之前用到的 circle,还有:

这里只用到了 circle,对其他有兴趣的可以直接点链接了解。

circle 的属性很简单,cx, cyr,对应圆心的 x 坐标,y 坐标和半径。

那例子中的就是画一个以 (25, 25)为圆心,半径为 20 的圆。

<circle cx="25" cy="25" r="20" fill="none"/>

fill 属性用来填充,这里 fill="none" 就是没有填充色。

ok。这样圆就完成了,但如果你也在一边尝试的话会发现,界面上依旧是一片空白。

别着急,刚刚只是前戏,正戏现在才开始。

stroke

从最初的图中可以看到,并不是要画一个圆,而是画一段线,这段线围绕一个圆来运动。

画好了圆,给它加上外边线不就有了一个围绕圆的线了么,这就要用到 stroke

The stroke attribute defines the color of the outline on a given graphical element. The default value for the stroke attribute is none. ——MDN

也就是给图形的外线框添加颜色。

<circle cx="25" cy="25" r="20" fill="none" stroke="#106CFA"/>

这时,你就能看到一个蓝色的细环了。但是,太细了,可以通过 stroke-width 调整。

the stroke-width attribute specifies the width of the outline on the current object. Its default value is 1. If a <percentage> is used, the value represents a percentage of the current viewport. If a value of 0 is used the outline will never be drawn. ——MDN

刚才,觉得太细就是因为 stroke-width 的默认值是 1。这里将 stroke-width 设定成 5%,使用百分比的好处是:当它做成组件后,只需控制 svg viewport 的大小,线宽会自动调整粗细。

于是,代码又变成了这样

<circle cx="25" cy="25" r="20" fill="none"
        stroke="#106CFA" stroke-width="5%" />

loading 中的线段并不是一直保持环装,而是长短会变化,这该如何控制哪?

答案是:stroke-dasharray

the stroke-dasharray attribute controls the pattern of dashes and gaps used to stroke paths. ——MDN

也就是说,它是用来一组值来表示设置环绕在形状外部的虚线间隔。当这组值是偶数时,那么,它就分别表示线段长,间距长...,并以此类推。当它为奇数时,系统会默认追加相同的设置到末尾,使它成为偶数,然后再按偶数时的处理方式,如 5,3,2 就是 5,3,2,5,3,2。

你会想,虚线和 loading 有啥关系,loading 是一条线啊?没错,一开始我也是这样想的。

但,大神就想到了,如果将虚线的第一部分设定的足够长,那么它在可视范围内就是一条实线。于是,通过控制第一段实线的长度,也就控制了整条线段的长度。

因为,线段变长变短是一个过程,这就要用到动画。

.loader{
  // 省略...
  circle{
    animation: circle-dash 2s ease-in-out infinite;
  }
}

@keyframes circle-dash{
  0% {
    stroke-dasharray: 1, 125;
  }
  100% {
    stroke-dasharray: 125, 125;
  }
}

这时,你就能看到线段周而复始地从一根细线变为一个圆圈。但这有突然闪屏的感觉,和所要的结果不同,再修改一下动画,让线段成为圆圈后再退回成一根细线。

@keyframes circle-dash{
  0% {
    stroke-dasharray: 1, 125;
  }
  50% {
    stroke-dasharray: 125, 125;
  }
  100% {
    stroke-dasharray: 1, 125;
  }
}

是不是和结果越来越像了,但还是不对,loading 中的线段没有给人有倒退的感觉,那该如何做?

那就要使用 stroke-dashoffset,通过设定该属性线段的开始位置,来作出线段在不断前行的假象。

the stroke-dashoffset attribute specifies the distance into the dash pattern to start the dash. ——MDN

再修改一下,刚刚的动画。

@keyframes circle-dash{
  0% {
    stroke-dasharray: 1, 125;
    stroke-dashoffset: 0;
  }
  50% {
    stroke-dasharray: 100, 125;
    stroke-dashoffset: -25px;
  }
  100% {
    stroke-dasharray: 100, 125;
    stroke-dashoffset: -125px;
  }
}

这次感觉是不是很相像了,只是现在它的开口一直处于一个位置,就没什么魔性了。可以通过让整个圆形旋转起来,这样圆的开口的位置也就会不断变化了。

.circular{
  animation: rotate 2s linear infinite;
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

Finish!来看看最后的结果。(简书不支持 CodePen,可以到我的博客或直接到 CodePen 上查看)

stroke 还有几个其他相关的属性,比如,stroke-linecap 可以用来改变线头的形状,其他还有 stroke-linejoin, stroke-miterlimit, stroke-opacity

最后

模拟 Material Design 的 loading 就这样完成了,并应用到了我的博客中,比如,首页的文章列表的懒加载。

最近,因工作需要搭了一个 React 全家桶 + Ant.Design 的脚手架,有兴趣的可以看看

最后不得不吐槽一句,React + Redux 相对于 vue 2 + vuex 用起来真心累...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容