如何使用CSS、anime.js和segment.js构建具有微交互的下载按钮

来自菜鸟教程
跳转至:导航、​搜索

介绍

在用户体验设计中,微交互是帮助用户导航界面的小反馈时刻。 通常,微交互是通过网站设计中的微妙动画进行的。

在本教程中,您将构建一个带有微交互的功能下载按钮。 为了让它工作,我们将使用 CSS 过渡和动画,以及用于 SVG path 动画的轻量级动画库 anime.jssegment.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%);
  }
}

我们的按钮可以处于三种不同的状态:downloadingprogressingcompleted。 因此,我们使用以下结构定义了每个状态所需的样式:

// 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.jssegment.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上获取完整代码。 另请注意,此组件尚未完全准备好投入生产,因为它需要真实的进度数据以及后端如何影响微交互的一些考虑。