一步步教你用HTML5 SVG实现动画效果

栏目: Html5 · 发布时间: 5年前

内容简介:HTML5 SVG使用CSS3和Vanilla JavaScript填充动画翻译:第一秩序原文:

HTML5 SVG使用CSS3和Vanilla JavaScript填充动画

翻译:第一秩序

原文: www.smashingmagazine.com/2019/01/htm…

摘要 在这篇文章中你将了解Awwwards网是怎样实现动画的。 本文介绍了HTML5 SVG中的circle 元素,它的stroke属性,以及如何使用CSS变量以及用 Vanilla JavaScript 为它们设置动画。

SVG是一种基于XML的,用于定义缩放矢量图形的标记语言。 它允许你通过在2D平面中确定的一组点来绘制路径、曲线和形状。 此外你还可以通过在这些路径上添加动态属性(例如笔触,颜色,粗细,填充等)来生成动画。

从2017年4月起,CSS Level 3填充和描边模块开始支持从外部样式表设置SVG颜色和填充图案,而不是在每个元素上设置属性。 在本教程中,我们将会使用简单的纯十六进制颜色,不过填充和描边属性也支持图案,渐变和图像作为值。

注意:访问Awwwards网站时,你需要把浏览器宽度设置为1024px或更高的才能更好的查看动画显示。

一步步教你用HTML5 SVG实现动画效果

文件结构

让我们从在终端中创建文件开始:

:rose:  mkdir note-display
:rose:  cd note-display
:rose:  touch index.html styles.css scripts.js
复制代码

HTML 这是连接 cssjs 文件的初始模板:

<html lang="en">
<head>
  <meta charset="UTF-8">

  <title>Note Display</title>

  <link rel="stylesheet" href="./styles.css">
</head>
<body>
  <script src="./scripts.js"></script>
</body>
</html>
复制代码

每个note元素都包含一个列表项: li 用于保存 circlenote 值及其 label

一步步教你用HTML5 SVG实现动画效果

图:列出项元素及其直接子元素: .circle , .percent.label

.circle_svg 是一个SVG元素,它包含两个 元素。 第一个是要填充的路径,第二个用来为动画作准备。

一步步教你用HTML5 SVG实现动画效果

图:SVG元素:SVG包装器和圆形标签

注释分为整数和小数,所以可以把它们设定为不同大小的字体。 label 是一个简单的 <span> 。 把所有得这些元素放在一起看起来像这样:

<li class="note-display">
  <div class="circle">
    <svg width="84" height="84" class="circle__svg">
      <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
      <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
    </svg>

    <div class="percent">
      <span class="percent__int">0.</span>
      <span class="percent__dec">00</span>
    </div>
  </div>

  <span class="label">Transparent</span>
</li>
复制代码

cxcy 属性定义圆的x轴和y轴中心点。 r 属性定义其半径。

你可能已经注意到类名中的下划线/破折号模式。 这是 BEM(block element modifier) ,分别代表 block , elementmodifier 。 它是使元素命名更加结构化、有条理和语义化的一种方法。

推荐阅读: 什么是BEM以及为什么需要它

为了完成模板结构,让我们将四个列表项包装在无序列表元素中:

一步步教你用HTML5 SVG实现动画效果

图:无序列表包装器拥有四个 li 子元素

<ul class="display-container">
  <li class="note-display">
    <div class="circle">
      <svg width="84" height="84" class="circle__svg">
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
      </svg>

      <div class="percent">
        <span class="percent__int">0.</span>
        <span class="percent__dec">00</span>
      </div>
    </div>

    <span class="label">Transparent</span>
  </li>

  <li class="note-display">
    <div class="circle">
      <svg width="84" height="84" class="circle__svg">
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
      </svg>

      <div class="percent">
        <span class="percent__int">0.</span>
        <span class="percent__dec">00</span>
      </div>
    </div>

    <span class="label">Reasonable</span>
  </li>

  <li class="note-display">
    <div class="circle">
      <svg width="84" height="84" class="circle__svg">
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
      </svg>

      <div class="percent">
        <span class="percent__int">0.</span>
        <span class="percent__dec">00</span>
      </div>
    </div>

    <span class="label">Usable</span>
  </li>

  <li class="note-display">
    <div class="circle">
      <svg width="84" height="84" class="circle__svg">
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
      </svg>

      <div class="percent">
        <span class="percent__int">0.</span>
        <span class="percent__dec">00</span>
      </div>
    </div>

    <span class="label">Exemplary</span>
  </li>
</ul>
复制代码

你必须先问一下自己 TransparentReasonableUsableExemplary 标签都代表什么意思。 随着你对编程的不断熟悉,就会发现写代码不仅仅是为了能够使程序正常运行,还需要要确保它能够被长期维护和扩展。 这些只有在你的代码容易被修改时才能够实现。

“缩略词 TRUE 应该能够帮助你确定自己编写的代码是否能够适应未来的变化。”

那么,下次问问你自己:

透明:代码更改后果是否明确? 合理:成本效益值得吗? 可用:我是否能够在意外情况下重复使用它? 示例:它是否以高质量作为未来代码的示例?

Transparent(透明)
Reasonable(合理)
Usable(可用)
Exemplary(示例)

注:Sandi Metz在 《面向对象设计实践指南:Ruby语言描述》 一书解释了 TRUE 和其他原则,以及如何通过 设计模式 实现它们。 如果你还没有开始研究设计模式,请考虑将此书放到自己的案头。

CSS

让我们导入字体并使其对所有内容生效:

@import url('https://fonts.googleapis.com/css?family=Nixie+One|Raleway:200');

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

复制代码

box-sizing: border-box 属性中包括填充与边框值到元素的总宽度和高度,所以更容易计算图形的范围。

注意:有关 *box-sizing *的说明,请阅读 “使用CSS Box让你更轻松” _。

body {
  height: 100vh;
  color: #fff;
  display: flex;
  background: #3E423A;
  font-family: 'Nixie One', cursive;
}

.display-container {
  margin: auto;
  display: flex;
}
复制代码

通过组合规则显示: body 中的 flex.display-container 中的 margin-auto ,可以将子元素垂直水平居中。 .display-container 元素也将作为一个 flex-container ; 这样,它的子元素会沿主轴被放置在同一行。

.note-display 列表项也将是一个 flex-container 。 由于有很多子项被居中,所以我们可以通过 justify-contentalign-items 属性来完成。 所有 flex-items 都将垂直水平居中。 如果你不确定它们是什么,请查看 “CSS Flexbox 可视化指南” 中的对齐部分。

.note-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0 25px;
}
复制代码

让我们通过设置``stroke-width stroke-opacity stroke-linecap` 将笔划应用于圆,这些规则会使画面动起来。 接下来,我们为每个圆添加一种颜色:

.circle__progress {
  fill: none;
  stroke-width: 3;
  stroke-opacity: 0.3;
  stroke-linecap: round;
}

.note-display:nth-child(1) .circle__progress { stroke: #AAFF00; }
.note-display:nth-child(2) .circle__progress { stroke: #FF00AA; }
.note-display:nth-child(3) .circle__progress { stroke: #AA00FF; }
.note-display:nth-child(4) .circle__progress { stroke: #00AAFF; }
复制代码

为了绝对定位百分比元素,必须完全知道这些概念是什么。 .circle 元素应该是引用,所以让我们为其添加添加 position: relative

注意:对绝对定位更深入、直观的解释,请阅读 “一劳永逸的理解 CSS Position” 一文。

另一种使元素居中的方法是把 top: 50% , left: 50%transform: translate(-50%, -50%); 组合在一起, 将元素的中心定位在其父级中心。

.circle {
  position: relative;
}

.percent {
  width: 100%;
  top: 50%;
  left: 50%;
  position: absolute;
  font-weight: bold;
  text-align: center;
  line-height: 28px;
  transform: translate(-50%, -50%);
}

.percent__int { font-size: 28px; }
.percent__dec { font-size: 12px; }

.label {
  font-family: 'Raleway', serif;
  font-size: 14px;
  text-transform: uppercase;
  margin-top: 15px;
}
复制代码

到目前为止,模板应如该是下面这个样子:

一步步教你用HTML5 SVG实现动画效果

图:完成的模板元素和样式

填充过渡

可以在两个圆形SVG属性的帮助下创建圆形动画: stroke-dasharraystroke-dashoffset

stroke-dasharray 定义笔划中的虚线间隙模式。”

它最多可能需要四个值:

当它被设置为唯一的整数( stroke-dasharray:10 )时,破折号和间隙具有相同的大小; 对于两个值( stroke-dasharray:10 5 ),第一个应用于破折号,第二个应用于间隙; 第三种和第四种形式( stroke-dasharray:10 5 2stroke-dasharray:10 5 2 3 )将产生各种样式的虚线和间隙。

一步步教你用HTML5 SVG实现动画效果

图: stroke-dasharray 属性值

左边的图像显示属性 stroke-dasharray 设置为 0 到圆周长度 238px。

第二个图像表示 stroke-dashoffset 属性,它抵消了dash数组的开头。 它的取值范围也是从0到圆周长度。

一步步教你用HTML5 SVG实现动画效果

图: stroke-dasharraystroke-dashoffset 属性

为了产生填充效果,我们将 stroke-dasharray 设置为圆周长度,以便它所有长度都能充满其冲刺范围而不留间隙。 我们也会用相同的值抵消它,这样会使它能够被“隐藏”。 然后, stroke-dashoffset 将更新为对应的说明文字,根据过渡持续时间填充其行程。

属性更新将通过CSS Variables在脚本中完成。 下面让我们声明变量并设置属性:

.circle__progress--fill {
  --initialStroke: 0;
  --transitionDuration: 0;
  stroke-opacity: 1;
  stroke-dasharray: var(--initialStroke);
  stroke-dashoffset: var(--initialStroke);
  transition: stroke-dashoffset var(--transitionDuration) ease;
}
复制代码

为了设置初始值并更新变量,让我们从使用 document.querySelectorAll 选择所有 .note-display 元素开始。 同时把 transitionDuration 设置为900毫秒。

然后,我们遍历显示数组,选择它的 .circle__progress.circle__progress--fill 并提取HTML中的 r 属性集来计算周长。 有了它,我们可以设置初始的 --dasharray--dashoffset 值。

--dashoffset 变量被 setTimeout 更新时,将发生动画:

const displays = document.querySelectorAll('.note-display');
const transitionDuration = 900;

displays.forEach(display => {
  let progress = display.querySelector('.circle__progress--fill');
  let radius = progress.r.baseVal.value;
  let circumference = 2 * Math.PI * radius;

  progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);
  progress.style.setProperty('--initialStroke', circumference);

  setTimeout(() => progress.style.strokeDashoffset = 50, 100);
});
复制代码

要从顶部开始过度,必须旋转 .circle__svg 元素:

.circle__svg {
  transform: rotate(-90deg);
}

复制代码
一步步教你用HTML5 SVG实现动画效果

图:Stroke 属性转换

现在,让我们计算相对于 note 的 dashoffset 值。 note 值将通过 data-* 属性插入每个 li 项目。 * 可以替换为任何符合你需求的名称,然后可以通过元素的数据集在元数据集中检索: element.dataset.*

注意:你可以在 MDN Web Docs 上得到有关 data-* 属性的更多信息。

我们的属性将被命名为 “ data-note ”:

<ul class="display-container">
+ <li class="note-display" data-note="7.50">
    <div class="circle">
      <svg width="84" height="84" class="circle__svg">
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
      </svg>

      <div class="percent">
        <span class="percent__int">0.</span>
        <span class="percent__dec">00</span>
      </div>
    </div>

    <span class="label">Transparent</span>
  </li>

+ <li class="note-display" data-note="9.27">
    <div class="circle">
      <svg width="84" height="84" class="circle__svg">
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
      </svg>

      <div class="percent">
        <span class="percent__int">0.</span>
        <span class="percent__dec">00</span>
      </div>
    </div>

    <span class="label">Reasonable</span>
  </li>

+ <li class="note-display" data-note="6.93">
    <div class="circle">
      <svg width="84" height="84" class="circle__svg">
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
      </svg>

      <div class="percent">
        <span class="percent__int">0.</span>
        <span class="percent__dec">00</span>
      </div>
    </div>

    <span class="label">Usable</span>
  </li>

+ <li class="note-display" data-note="8.72">
    <div class="circle">
      <svg width="84" height="84" class="circle__svg">
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
        <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
      </svg>

      <div class="percent">
        <span class="percent__int">0.</span>
        <span class="percent__dec">00</span>
      </div>
    </div>

    <span class="label">Exemplary</span>
  </li>
</ul>
复制代码

parseFloat 方法将 display.dataset.note 返回的字符串转换为浮点数。 offset 表示达到最高值时缺失的百分比。 因此,对于 7.50 note,我们将得到 (10 - 7.50) / 10 = 0.25 ,这意味着 circumference 长度应该偏移其值的25%:

let note = parseFloat(display.dataset.note);
let offset = circumference * (10 - note) / 10;
复制代码

更新scripts.js:

const displays = document.querySelectorAll('.note-display');
const transitionDuration = 900;

displays.forEach(display => {
  let progress = display.querySelector('.circle__progress--fill');
  let radius = progress.r.baseVal.value;
  let circumference = 2 * Math.PI * radius;
+ let note = parseFloat(display.dataset.note);
+ let offset = circumference * (10 - note) / 10;

  progress.style.setProperty('--initialStroke', circumference);
  progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);

+ setTimeout(() => progress.style.strokeDashoffset = offset, 100);
});

复制代码
一步步教你用HTML5 SVG实现动画效果

sroke属性转换为note值

在继续之前,让我们将stoke转换提取到它自己的方法中:

const displays = document.querySelectorAll('.note-display');
const transitionDuration = 900;

displays.forEach(display => {
- let progress = display.querySelector('.circle__progress--fill');
- let radius = progress.r.baseVal.value;
- let circumference = 2 * Math.PI * radius;
  let note = parseFloat(display.dataset.note);
- let offset = circumference * (10 - note) / 10;

- progress.style.setProperty('--initialStroke', circumference);
- progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);

- setTimeout(() => progress.style.strokeDashoffset = offset, 100);

+ strokeTransition(display, note);
});

+ function strokeTransition(display, note) {
+   let progress = display.querySelector('.circle__progress--fill');
+   let radius = progress.r.baseVal.value;
+   let circumference = 2 * Math.PI * radius;
+   let offset = circumference * (10 - note) / 10;

+   progress.style.setProperty('--initialStroke', circumference);
+   progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);

+   setTimeout(() => progress.style.strokeDashoffset = offset, 100);
+ }

复制代码

注意增长值

还有一件事就是把 note 从 0.00 转换到要最终的 note 值。 首先要做的是分隔整数和小数值。 可以使用字符串方法 split() 。 之后它们将被转换为数字,并作为参数传递给 increaseNumber() 函数,通过整数和小数的标志正确显示在对应元素上。

const displays = document.querySelectorAll('.note-display');
const transitionDuration = 900;

displays.forEach(display => {
  let note = parseFloat(display.dataset.note);
+ let [int, dec] = display.dataset.note.split('.');
+ [int, dec] = [Number(int), Number(dec)];

  strokeTransition(display, note);

+ increaseNumber(display, int, 'int');
+ increaseNumber(display, dec, 'dec');
});
复制代码

increaseNumber() 函数中,我们究竟选择 .percent__int 还是 .percent__dec 元素,取决于 className ,以及输出是否应包含小数点。 接下来把 transitionDuration 设置为900毫秒。 现在,动画表示从0到7的数字,持续时间必须除以note 900 / 7 = 128.57ms 。 结果表示每次增加迭代将花费多长时间。 这意味着 setInterval 将每隔 128.57ms 触发一次。

设置好这些变量后,接着定义 setIntervalcounter 变量将作为文本附加到元素,并在每次迭代时增加:

function increaseNumber(display, number, className) {
  let element = display.querySelector(`.percent__${className}`),
      decPoint = className === 'int' ? '.' : '',
      interval = transitionDuration / number,
      counter = 0;

  let increaseInterval = setInterval(() => {
    element.textContent = counter + decPoint;
    counter++;
  }, interval);
}
复制代码
一步步教你用HTML5 SVG实现动画效果

图:计数增长

太酷了! 确实增加了计数值,但它在无限循环播放。 当note达到我们想要的值时,还需要清除 setInterval 。 可以通过 clearInterval 函数完成:

function increaseNumber(display, number, className) {
  let element = display.querySelector(`.percent__${className}`),
      decPoint = className === 'int' ? '.' : '',
      interval = transitionDuration / number,
      counter = 0;

  let increaseInterval = setInterval(() => {
+   if (counter === number) { window.clearInterval(increaseInterval); }

    element.textContent = counter + decPoint;
    counter++;
  }, interval);
}
复制代码
一步步教你用HTML5 SVG实现动画效果

图:最终完成

现在,数字更新到note值,并使用 clearInterval() 函数清除。

教程到此就结束了,希望你能喜欢它!

如果你想开发一些更具互动性的东西,请查看使用 Vanilla JavaScript 创建的Memory Game Tutorial 。 它涵盖了基本的HTML5,CSS3和JavaScript概念,如定位、透视、转换、Flexbox、事件处理、超时和三元组。


以上所述就是小编给大家介绍的《一步步教你用HTML5 SVG实现动画效果》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning PHP 5

Learning PHP 5

David Sklar / O'Reilly / July, 2004 / $29.95

Learning PHP 5 is the ideal tutorial for graphic designers, bloggers, and other web crafters who want a thorough but non-intimidating way to understand the code that makes web sites dynamic. The book ......一起来看看 《Learning PHP 5》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试