在一些特别重的网站上,用户需要暂时看到一个视觉提示,以表明资源和资产仍在加载,然后才能看到一个完整的网站。 有不同的方法来解决这种 UX 问题,从转轮到 骨架屏幕。
如果我们使用一个开箱即用的解决方案,它可以为我们提供当前的进度,就像 Jam3 的 preloader 包 所做的那样,构建一个加载指示器就会变得更容易。
为此,我们将创建一个环/圆圈,对它进行样式设置,根据进度进行动画,然后将其包装在一个组件中以供开发使用。
步骤 1:让我们创建一个 SVG 环
在众多使用纯 HTML 和 CSS 绘制圆圈的方法中,我选择 SVG,因为它可以通过属性进行配置和样式设置,同时在所有屏幕上保持其分辨率。
<svg
class="progress-ring"
height="120"
width="120"
>
<circle
class="progress-ring__circle"
stroke-width="1"
fill="transparent"
r="58"
cx="60"
cy="60"
/>
</svg>
在 <svg>
元素中,我们放置一个 <circle>
标签,在那里我们使用 r
属性声明环的半径,使用 cx
和 cy
声明其在 SVG viewBox 中的中心位置,以及圆圈描边的宽度。
您可能已经注意到半径是 58 而不是 60,这看起来应该是正确的。 我们需要减去描边,否则圆圈会溢出 SVG 容器。
radius = (width / 2) - (strokeWidth * 2)
这意味着,如果我们将描边增加到 4,那么半径应该为 52。
52 = (120 / 2) - (4 * 2)
要完成环,我们需要将 fill
设置为 transparent
,并为圆圈选择一个 stroke
颜色。
查看 CodePen 上 Jeremias Menichelli (@jeremenichelli) 的 SVG 环。
步骤 2:添加描边
下一步是为我们环的外线长度添加动画,以模拟视觉进度。
我们将使用两个您可能以前没有听说过的 CSS 属性,因为它们是 SVG 元素独有的,即 stroke-dasharray
和 stroke-dashoffset
。
stroke-dasharray
此属性类似于 border-style: dashed
,但它允许您定义虚线的宽度和它们之间的间隙。
.progress-ring__circle {
stroke-dasharray: 10 20;
}
使用这些值,我们的环将有 10px 的虚线,它们之间相隔 20px。
查看 CodePen 上 Jeremias Menichelli (@jeremenichelli) 的 虚线 SVG 环。
stroke-dashoffset
第二个属性允许您沿着 SVG 元素的路径移动此虚线-间隙序列的起点。
现在,想象一下,如果我们将圆圈的周长传递给 stroke-dasharray
的两个值。 我们的形状将有一个长虚线占据整个长度,以及一个相同长度的间隙,这个间隙将不可见。
这最初不会造成任何变化,但如果我们也为 stroke-dashoffset
设置相同长度,那么长虚线将完全移动并显示出间隙。
减小 stroke-dasharray
将开始显示我们的形状。
几年前,Jake Archibald 在 这篇文章 中解释了这种技术,这篇文章中还有一个实时示例,可以帮助您更好地理解它。 您应该去阅读他的教程。
周长
我们现在需要的是这个长度,它可以通过半径和这个简单的三角函数公式计算得出。
circumference = radius * 2 * PI
由于我们知道 52 是我们环的半径
326.7256 ~= 52 * 2 * PI
如果需要,我们也可以通过 JavaScript 获取这个值
const circle = document.querySelector('.progress-ring__circle');
const radius = circle.r.baseVal.value;
const circumference = radius * 2 * Math.PI;
这样,我们就可以稍后为我们的圆圈元素分配样式。
circle.style.strokeDasharray = `${circumference} ${circumference}`;
circle.style.strokeDashoffset = circumference;
步骤 3:进度到偏移量
有了这个小技巧,我们知道,将周长值分配给 stroke-dashoffset
将反映 0 进度状态,而 0
值将表示进度已完成。
因此,随着进度的增长,我们需要像这样减少偏移量
function setProgress(percent) {
const offset = circumference - percent / 100 * circumference;
circle.style.strokeDashoffset = offset;
}
通过对属性进行过渡,我们将获得动画效果
.progress-ring__circle {
transition: stroke-dashoffset 0.35s;
}
关于 stroke-dashoffset
的一件事是,它的起点是垂直居中,水平向右倾斜。 需要将圆圈负向旋转才能获得所需的视觉效果。
.progress-ring__circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%,
}
将所有这些内容放在一起将得到类似这样的东西。
查看 CodePen 上 Jeremias Menichelli (@jeremenichelli) 的 vegymB。
在本例中添加了一个数字输入,以帮助您测试动画。
为了使其能够轻松地与您的应用程序结合使用,最好将解决方案封装在一个组件中。
作为 Web 组件
现在我们已经有了逻辑、样式和 HTML,我们可以轻松地将其移植到任何技术或框架。
首先,让我们使用 Web 组件。
class ProgressRing extends HTMLElement {...}
window.customElements.define('progress-ring', ProgressRing);
这是自定义元素的标准声明,它扩展了本机 HTMLElement
类,该类可以通过属性进行配置。
<progress-ring stroke="4" radius="60" progress="0"></progress-ring>
在元素的构造函数中,我们将创建一个影子根,以封装样式及其模板。
constructor() {
super();
// get config from attributes
const stroke = this.getAttribute('stroke');
const radius = this.getAttribute('radius');
const normalizedRadius = radius - stroke * 2;
this._circumference = normalizedRadius * 2 * Math.PI;
// create shadow dom root
this._root = this.attachShadow({mode: 'open'});
this._root.innerHTML = `
<svg
height="${radius * 2}"
width="${radius * 2}"
>
<circle
stroke="white"
stroke-dasharray="${this._circumference} ${this._circumference}"
style="stroke-dashoffset:${this._circumference}"
stroke-width="${stroke}"
fill="transparent"
r="${normalizedRadius}"
cx="${radius}"
cy="${radius}"
/>
</svg>
<style>
circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
</style>
`;
}
您可能已经注意到,我们没有将值硬编码到 SVG 中,而是从传递给元素的属性中获取它们。
此外,我们还计算了环的周长,并提前设置了 stroke-dasharray
和 stroke-dashoffset
。
下一步是观察 progress
属性并修改圆圈样式。
setProgress(percent) {
const offset = this._circumference - (percent / 100 * this._circumference);
const circle = this._root.querySelector('circle');
circle.style.strokeDashoffset = offset;
}
static get observedAttributes() {
return [ 'progress' ];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'progress') {
this.setProgress(newValue);
}
}
在这里,setProgress
成为一个类方法,当 progress
属性发生变化时,该方法将被调用。
observedAttributes
由一个静态 getter 定义,当(在本例中)progress
被修改时,该 getter 将触发 attributeChangeCallback
。
查看 CodePen 上 Jeremias Menichelli (@jeremenichelli) 的 ProgressRing Web 组件。
此 CodePen 在撰写本文时只在 Chrome 中有效。 添加了一个间隔以模拟进度变化。
作为 Vue 组件
Web 组件很棒。 也就是说,一些可用的库和框架,比如 Vue.js,可以完成很多繁重的工作。
首先,我们需要定义视图组件。
const ProgressRing = Vue.component('progress-ring', {});
编写单个文件组件也是可能的,而且可能更简洁,但我们采用工厂语法来匹配最终的代码演示。
我们将属性定义为 props,并将计算定义为 data。
const ProgressRing = Vue.component('progress-ring', {
props: {
radius: Number,
progress: Number,
stroke: Number
},
data() {
const normalizedRadius = this.radius - this.stroke * 2;
const circumference = normalizedRadius * 2 * Math.PI;
return {
normalizedRadius,
circumference
};
}
});
由于 Vue 原生支持计算属性,我们可以使用它来计算 stroke-dashoffset
的值。
computed: {
strokeDashoffset() {
return this._circumference - percent / 100 * this._circumference;
}
}
接下来,我们将 SVG 添加为模板。 请注意,这里的简单部分是 Vue 为我们提供了绑定,将 JavaScript 表达式带入属性和样式中。
template: `
<svg
:height="radius * 2"
:width="radius * 2"
>
<circle
stroke="white"
fill="transparent"
:stroke-dasharray="circumference + ' ' + circumference"
:style="{ strokeDashoffset }"
:stroke-width="stroke"
:r="normalizedRadius"
:cx="radius"
:cy="radius"
/>
</svg>
`
当我们在应用程序中更新元素的 progress
prop 时,Vue 会负责计算更改并更新元素样式。
查看 CodePen 上 Jeremias Menichelli (@jeremenichelli) 的 Vue ProgressRing 组件。
注意:添加了一个间隔以模拟进度变化。 我们在下一个示例中也会这样做。
作为 React 组件
与 Vue.js 类似,React 借助 props 和 JSX 语法帮助我们处理所有配置和计算值。
首先,我们从传递下来的 props 中获取一些数据。
class ProgressRing extends React.Component {
constructor(props) {
super(props);
const { radius, stroke } = this.props;
this.normalizedRadius = radius - stroke * 2;
this.circumference = this.normalizedRadius * 2 * Math.PI;
}
}
我们的模板是组件 `render` 函数的返回值,我们在其中使用 progress prop 来计算 `stroke-dashoffset` 值。
render() {
const { radius, stroke, progress } = this.props;
const strokeDashoffset = this.circumference - progress / 100 * this.circumference;
return (
<svg
height={radius * 2}
width={radius * 2}
>
<circle
stroke="white"
fill="transparent"
strokeWidth={ stroke }
strokeDasharray={ this.circumference + ' ' + this.circumference }
style={ { strokeDashoffset } }
stroke-width={ stroke }
r={ this.normalizedRadius }
cx={ radius }
cy={ radius }
/>
</svg>
);
}
`progress` prop 的改变会触发一个新的渲染周期,重新计算 `strokeDashoffset` 变量。
查看 CodePen 上 Jeremias Menichelli 的 React ProgressRing 组件 (@jeremenichelli)。
总结
此解决方案的实现方法基于 SVG 形状和样式、CSS 过渡以及少量 JavaScript 代码来计算特殊属性以模拟绘制圆周。
一旦我们分离出这部分代码,就可以将其移植到任何现代库或框架中,并将其包含在我们的应用程序中。在这篇文章中,我们探讨了 Web Components、Vue 和 React。
一旦实现 conic-gradient 就会更容易
conic gradients 尽快推出吧
这本身就很酷。但更酷的是:这简直是 模板字面量 的真实案例演示!
cu, w0lf.
YAS :D 我很喜欢,真的很喜欢模板字面量
在尝试创建这样的元素时,我发现将圆的半径设置为 16,然后简单地将整个 SVG 元素缩放至适当尺寸非常有用。这是因为半径为 16 的圆的周长足够接近 100(它是 100.53…),这使得你的 `stroke-dasharray` 计算变得容易得多。
你只需要将你的 `stroke-dasharray` 值设置为 `100 100`,然后将 `stroke-dashoffset` 设置为 100 减去你的目标值。我发现更改 `stroke-dashoffset` 比更改 `stroke-dasharray` 更容易,因为只需要修改一个值,尽管这需要你在百分比计算中进行一些额外的减法。如果你更喜欢更改 `stroke-dasharray` 值,只需将第一个数字设置为你想要的百分比,并完全省略 `stroke-dashoffset`。
以下是我为此类情况创建的简单示例 SVG
请注意,`viewBox` 设置为 `0 0 36 36`。这不是必需的,但它使精确缩放圆形更容易,如果这很重要。最后两个值只是直径加上描边宽度。这样,如果我想要,比如一个精确为 100px x 100px 的圆,我只需要将 SVG 的宽度和高度设置为 100。非常容易。
const normalizedRadius = radius - stroke * 2;
我不确定,但应该不是 `const normalizedRadius = radius - stroke / 2;` 吗?或者是我假设描边位于轮廓的中心,这个假设错了?
描边不包含在形状的半径 (**r**) 中,因此你有两种选择: (1) 将宽度和高度增加描边的两倍,或 (2) 减小半径以保持预期的宽度和高度。描边从圆的外边缘开始。
它乘以 2 是因为在描边的中心,无论是在垂直方向还是水平方向上,描边都会出现在上下边缘,或左右边缘。
希望解释清楚 :)
在 IE11 中不起作用。
对不起,大家,它仍然活着。