如何使用CSS、anime.js和segment.js构建具有微交互的下载按钮
介绍
在用户体验设计中,微交互是帮助用户导航界面的小反馈时刻。 通常,微交互是通过网站设计中的微妙动画进行的。
在本教程中,您将构建一个带有微交互的功能下载按钮。 为了让它工作,我们将使用 CSS 过渡和动画,以及用于 SVG path 动画的轻量级动画库 anime.js 和 segment.js。
在教程结束时,我们将获得一个下载按钮,如下所示:
下载按钮的原始设计属于佩德罗·阿基诺,可以在这张Dribbble shot上找到。 完整代码可以在这个Github存储库上找到,这里是演示页面。
第 1 步 - 制作 HTML 结构
让我们看看我们将使用的 HTML 代码:
<!-- Button container -->
<div class="download-button-container">
<!-- The real button -->
<button class="download-button">
<span class="button-text-real hidden">download</span>
<!-- Extra elements to perform the animations -->
<span class="button-icon">
<span class="button-linear-progress">
<span class="button-linear-progress-bar"></span>
</span>
<svg class="button-icon-svg" viewBox="0 0 60 60">
<path class="button-icon-path button-icon-path-square" d="M 20 40 l 0 -20 l 20 0 l 0 20 Z"></path>
<path class="button-icon-path button-icon-path-line" d="M 40 20 l -20 20"></path>
</svg>
</span>
</button>
<!-- Extra elements to perform the animations -->
<svg class="border-svg" width="240px" height="100px" viewBox="0 0 240 100">
<path class="border-path hidden" d="M 40 3.5 a 36.5 36.5 0 0 0 -36.5 36.5 a 36.5 36.5 0 0 0 36.5 36.5 C 70 76.5 90 76.5 120 76.5 S 170 76.5 200 76.5 a 36.5 36.5 0 0 0 36.5 -36.5 a 36.5 36.5 0 0 0 -36.5 -36.5 Z"></path>
</svg>
<span class="button-text button-text-download">download</span>
<span class="button-text button-text-done">done!</span>
<div class="button-wave"></div>
<div class="button-progress-container">
<svg class="button-svg">
<path class="button-circular-progress" d="M 50 50 m 0 -32.5 a 32.5 32.5 0 0 1 0 65 a 32.5 32.5 0 0 1 0 -65"></path>
</svg>
<span class="button-ball"></span>
</div>
</div>
需要注意的是,SVG path 元素是手工绘制的,以获得我们想要的结果。 例如,在某些时候,我们希望按钮边框执行弹性动画,因此我们需要一个 SVG path 为带有anime.js 的变形动画做好准备(paths 中的结构相同) :
第 2 步 — 添加样式
准备好标记后,让我们为按钮设置样式。 请注意,我们在这里不包括整个样式表,而是最重要的部分; 您可以在 Github 存储库 上找到完整的代码。 为了更好地理解,代码已被充分注释。
让我们看看我们定义的 SCSS 变量,以及隐藏元素的辅助类:
// Some variables to use later
$button-width: 300px;
$button-height: 70px;
$button-border: 3px;
$icon-padding: 5px;
$icon-width: $button-height - ($icon-padding * 2);
$ball-width: 18px;
// Helper class to hide elements
.hidden {
visibility: hidden !important;
opacity: 0 !important;
}
真正的 button 元素的样式:
// Real button styles
.download-button {
position: relative;
display: inline-block;
width: $button-width;
height: $button-height;
background-color: #2C2E2F;
border: none;
box-shadow: 0 0 0 $button-border #02D1FF; // This will be our 'border'
border-radius: 100px;
cursor: pointer;
transition: 1s width, 0.3s box-shadow;
// Remove the custom behavior in some browsers
&, &:focus {
padding: 0;
outline: none;
}
&::-moz-focus-inner {
border: 0;
}
// Styles for the different states of the button
&:hover, &:active, &:focus {
box-shadow: 0 0 0 $button-border #02D1FF, 0 0 20px $button-border darken(#02D1FF, 20%);
}
}
我们的按钮可以处于三种不同的状态:downloading、progressing 和 completed。 因此,我们使用以下结构定义了每个状态所需的样式:
// Button container
.download-button-container {
// ...CODE...
// Following are the different states for the button: downloading, progressing and completed
// We have defined the states in the container to have access to all descendants in CSS
// Downloading: The download button has been pressed
&.downloading {
// ...CODE...
}
// Progressing: The progress starts
&.progressing {
// ...CODE...
}
// Completed: The progress ends
&.completed {
// ...CODE...
}
}
另一个有趣的代码用于在下载完成后实现球动画:
.button-ball {
left: 50%;
transition: none;
// CSS animations for the ball. All of them start at the same time, so we need to take care of delays
animation:
ball-throw-up 0.5s ease-out forwards, // Throw up the ball for 0.5s
ball-throw-down 0.5s 0.5s ease-in forwards, // Wait 0.5 seconds (throw up), and throw down the ball for 0.5s
ball-rubber 1s forwards; // Move the ball like a rubber deformation during 1s (throw up + throw down)
}
// Throw up animation
@keyframes ball-throw-up {
from {
transform: translate(-50%, 17.5px);
}
to {
transform: translate(-50%, -60px);
background-color: #00FF8D;
}
}
// Throw down animation
@keyframes ball-throw-down {
from {
transform: translate(-50%, -60px);
}
to {
transform: translate(-50%, 80px);
}
}
// Rubber animation
@keyframes ball-rubber {
from {
width: $ball-width;
}
25% {
width: $ball-width * 0.75;
}
50% {
width: $ball-width;
}
to {
width: $ball-width / 2;
}
}
所有其他使用的样式都可以在 Github 存储库 上找到。
第 3 步 — 使用 Javascript 制作动画
我们将使用 anime.js 和 segment.js 这两个轻量级库来帮助制作动画。
请注意,为清楚起见,我们不会在以下代码片段中包含一些变量声明。 如果您有任何疑问,请查看 Github 存储库 。
这是我们用来捕获 button 上的点击事件并执行我们想要的行为的基本代码:
// Capture click events
button.addEventListener('click', function () {
if (!completed) { // Don't do anything if downloading has been completed
if (downloading) { // If it's downloading, stop the download
stopDownload();
} else { // Start the download
startDownload();
}
}
});
// Start the download
function startDownload() {
// Update variables and CSS classes
downloading = true;
buttonContainer.classList.add('downloading');
animateIcon();
// Update progress after 1s
progressTimer = setTimeout(function () {
buttonContainer.classList.add('progressing');
animateProgress();
}, 1000);
}
// Stop the download
function stopDownload() {
// Update variables and CSS classes
downloading = false;
clearTimeout(progressTimer);
buttonContainer.classList.remove('downloading');
buttonContainer.classList.remove('progressing');
// Stop progress and draw icons back to initial state
stopProgress();
iconLine.draw(0, '100%', 1, {easing: anime.easings['easeOutCubic']});
iconSquare.draw('30%', '70%', 1, {easing: anime.easings['easeOutQuad']});
}
演示中的动画进度已被伪造; 对于真实的用例,它将被真实的进度数据取代。 这是处理进度的函数:
// Progress animation
function animateProgress() {
// Fake progress animation from 0 to 100%
// This should be replaced with real progress data (real progress percent instead '100%'), and maybe called multiple times
circularProgressBar.draw(0, '100%', 2.5, {easing: anime.easings['easeInQuart'], update: updateProgress, callback: completedAnimation});
}
最后,这里是用于在下载完成后执行动画的一段代码,其中触发了球动画,我们变形了 path 元素。
// Animation performed when download has been completed
function completedAnimation() {
// Update variables and CSS classes
completed = true;
buttonContainer.classList.add('completed');
// Wait 1s for the ball animation
setTimeout(function () {
button.classList.add('button-hidden');
ball.classList.add('hidden');
borderPath.classList.remove('hidden');
// Morphing the path to the second shape
var morph = anime({
targets: borderPath,
d: 'M 40 3.5 a 36.5 36.5 0 0 0 -36.5 36.5 a 36.5 36.5 0 0 0 10.5 26.5 C 35 86.5 90 91.5 120 91.5 S 205 86.5 226 66.5 a 36.5 36.5 0 0 0 10.5 -26.5 a 36.5 36.5 0 0 0 -36.5 -36.5 Z',
duration: 100,
easing: 'linear',
complete: function () {
// Morphing the path back to the original shape with elasticity
morph = anime({
targets: borderPath,
d: 'M 40 3.5 a 36.5 36.5 0 0 0 -36.5 36.5 a 36.5 36.5 0 0 0 36.5 36.5 C 70 76.5 90 76.5 120 76.5 S 170 76.5 200 76.5 a 36.5 36.5 0 0 0 36.5 -36.5 a 36.5 36.5 0 0 0 -36.5 -36.5 Z',
duration: 1000,
elasticity: 600,
complete: function () {
// Update variables and CSS classes, and return the button to the original state
completed = false;
setTimeout(function () {
buttonContainer.classList.remove('completed');
button.classList.remove('button-hidden');
ball.classList.remove('hidden');
borderPath.classList.add('hidden');
stopDownload();
}, 500);
}
});
}
});
}, 1000);
}
结论
本文展示了用于构建此下载按钮的主要代码片段:
你可以玩现场DEMO,或者在Github上获取完整代码。 另请注意,此组件尚未完全准备好投入生产,因为它需要真实的进度数据以及后端如何影响微交互的一些考虑。