时钟——CSS 动画

译者注:本文内容为 Clocks - CSS Animation 的译文

译者在编写文章过程中写了 一份最终效果(iOS7 风格)源代码 供大家参考

时钟

这是关于时间的。在本文中,我们将接受创建和设置时钟动画的挑战,使用简单的CSS动画和JavaScript来触发它们。

这是我们使用HTML,CSS,SVG背景和一些JavaScript创建的时钟。我们将使用CSS动画或过渡进行任何移动,并依靠JavaScript来设置初始时间并添加基本的CSS变换。

HTML

要开始使用,我们需要一些HTML。

<article class="clock">
  <div class="hours-container">
    <div class="hours"></div>
  </div>
  <div class="minutes-container">
    <div class="minutes"></div>
  </div>
  <div class="seconds-container">
    <div class="seconds"></div>
  </div>
</article>

我最初的方法是为每只指针使用三个元素。然后我回去把每个包装在一个容器元素中。虽然简单的HTML可以用于基本的CSS动画,但是当我们想要定位指针并为它们制作动画时,我们还需要包含元素。

钟面

我们将从一个基本的时钟设计开始,圆脸,简单的小时,分钟和秒针。

.clock {
  border-radius: 50%;
  background: #fff url(/images/posts/clocks/ios_clock.svg) no-repeat center;
  background-size: 88%;
  height: 20em;
  padding-bottom: 31%;
  position: relative;
  width: 20em;
}

.clock.simple:after {
  background: #000;
  border-radius: 50%;
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 5%;
  height: 5%;
  z-index: 10;
}

你可以在这里获得SVG背景。我还添加了一个伪元素来向中心添加一个纯黑色圆圈。然后可以根据需要将时钟的指针放在该伪元素后面。

我们现在应该有这样的结果。

在添加指针之前,我们需要放置容器。

.minutes-container, .hours-container, .seconds-container {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

这会将每个容器堆叠在时钟之上。接下来我们创造指针。

时针

每只指针都被赋予absolute属性值并放置在十二点钟位置。我们将从时针开始。

.hours {
  background: #000;
  height: 20%;
  left: 48.75%;
  position: absolute;
  top: 30%;
  transform-origin: 50% 100%;
  width: 2.5%;
}

我正在使用百分比来简化时钟缩放。这是一项复杂的工作,但能让它们更容易适应视图或缩小手机。我们还将transform-origin属性设置为指针的底部,以便以后可以旋转。

分针

分针跟时针类似,但更长更细。

.minutes {
  background: #000;
  height: 40%;
  left: 49%;
  position: absolute;
  top: 10%;
  transform-origin: 50% 100%;
  width: 2%;
}

秒针

秒针再次变细,但也进一步向下设置,使其比中心延伸得更远。为此,transform-origin为80%。这使得20%的手伸出中心。

.seconds {
  background: #000;
  height: 45%;
  left: 49.5%;
  position: absolute;
  top: 14%;
  transform-origin: 50% 80%;
  width: 1%;
  z-index: 8;
}

动画

停止的时钟每天只能正确两次。让我们添加一些动画来让时钟表现得更像真实的东西。

有些时钟会随着每秒跳跃,通常会产生滴答声。有些时钟会随着手的平稳移动而发出呜呜声。我们两个都试试。首先,我们会让双手平稳移动。

我们可以使用一个keyframe来指示手在360度左右移动(暗示0%的起始位置)。

@keyframes rotate {
  100% {
    transform: rotateZ(360deg);
  }
}

如果使用animation属性应用于元素,则此关键帧会告诉元素在360度左右设置动画。我们将在指针上使用linear计时功能,使指针平稳移动。

.hours-container {
  animation: rotate 43200s infinite linear;
}
.minutes-container {
  animation: rotate 3600s infinite linear;
}
.seconds-container {
  animation: rotate 60s infinite linear;
}

时针设置为每12小时(43,400秒)旋转一次。分针每小时旋转一次,秒针每分钟旋转一次。

把它放在一起,我们现在有运动!

如果你的眼睛非常敏锐,你甚至可以让分针移动。它需要一个小时才能完成一个旋转,十二个小时的时间来完成它的循环。

秒针需要60秒,因此更容易注意到。

添加停顿

通过让秒针在60个单独的动作中全天候移动,我们可以让指针更像普通时钟。实现此目的的一种简单方法是使用steps计时功能。然后每只指针的animation属性变为:

.minutes-container {
  animation: rotate 3600s infinite steps(60);
}
.seconds-container {
  animation: rotate 60s infinite steps(60);
}

分针和秒针现在分六步走动。浏览器会自动计算这60个步骤中的每个步骤移动的距离。

正确的时间

一切都很好,时间看起来不错,但准确度如何呢?使用一点JavaScript,我们可以让时间对我们的访问者来说是正确的。这是代码:

/*
 * Starts any clocks using the user's local time
 * From: cssanimation.rocks/clocks
 */
function initLocalClocks() {
  // Get the local time using JS
  var date = new Date;
  var seconds = date.getSeconds();
  var minutes = date.getMinutes();
  var hours = date.getHours();

  // Create an object with each hand and it's angle in degrees
  var hands = [
    {
      hand: 'hours',
      angle: (hours * 30) + (minutes / 2)
    },
    {
      hand: 'minutes',
      angle: (minutes * 6)
    },
    {
      hand: 'seconds',
      angle: (seconds * 6)
    }
  ];
  // Loop through each of these hands to set their angle
  for (var j = 0; j < hands.length; j++) {
    var elements = document.querySelectorAll('.' + hands[j].hand);
    for (var k = 0; k < elements.length; k++) {
        elements[k].style.webkitTransform = 'rotateZ('+ hands[j].angle +'deg)';
        elements[k].style.transform = 'rotateZ('+ hands[j].angle +'deg)';
        // If this is a minute hand, note the seconds position (to calculate minute position later)
        if (hands[j].hand === 'minutes') {
          elements[k].parentNode.setAttribute('data-second-angle', hands[j + 1].angle);
        }
    }
  }
}

此功能将时间(小时,分钟和秒)转换为每只指针的相应角度。然后循环遍历每只手并使用rotateZstyle.transform属性应用该角度。

这适用于除Safari或其他需要前缀的浏览器之外的大多数浏览器。为此,我们还使用了style.webkitTransform属性。

然后,将时钟设置为当前系统时间。

对齐秒针和分针

当首次在屏幕上绘制时钟时,在分针需要移动之前不到一分钟。为了实现这一点,我们可以计算出第一分钟结束的时间并手动轻推分针。由于我们使用JavaScript进行第一次移动,我们可以使用setInterval每分钟继续旋转6度。

在我们移动分针之前,我们需要告知我们当前的分钟数。您可能已经注意到这些线条。

if (hands[j].hand === 'minutes') {
  elements[k].parentNode.setAttribute('data-second-angle', hands[j + 1].angle);
}

这些额外的行检查指针是否是“分钟”指针,如果是,则使用秒针的当前角度设置数据属性。

使用此数据属性集,我们可以使用此数据计算何时移动分针。

/*
 * Set a timeout for the first minute hand movement (less than 1 minute), then rotate it every minute after that
 */
function setUpMinuteHands() {
  // Find out how far into the minute we are
  var containers = document.querySelectorAll('.minutes-container');
  var secondAngle = containers[0].getAttribute("data-second-angle");
  if (secondAngle > 0) {
    // Set a timeout until the end of the current minute, to move the hand
    var delay = (((360 - secondAngle) / 6) + 0.1) * 1000;
    setTimeout(function() {
      moveMinuteHands(containers);
    }, delay);
  }
}

/*
 * Do the first minute's rotation
 */
function moveMinuteHands(containers) {
  for (var i = 0; i < containers.length; i++) {
    containers[i].style.webkitTransform = 'rotateZ(6deg)';
    containers[i].style.transform = 'rotateZ(6deg)';
  }
  // Then continue with a 60 second interval
  setInterval(function() {
    for (var i = 0; i < containers.length; i++) {
      if (containers[i].angle === undefined) {
        containers[i].angle = 12;
      } else {
        containers[i].angle += 6;
      }
      containers[i].style.webkitTransform = 'rotateZ('+ containers[i].angle +'deg)';
      containers[i].style.transform = 'rotateZ('+ containers[i].angle +'deg)';
    }
  }, 60000);
}

添加弹跳

由于我们现在使用JavaScript来移动分针,我们应该删除动画属性。我们可以用过渡替换它,而不是简单地删除它。这是一个为动画添加更多角色的机会。

当JavaScript为手设置新角度时,元素上的CSS转换将告诉浏览器为此新位置设置动画。这意味着JavaScript只处理简单的角度变化,浏览器可以处理它的动画。

在我们这样做之前,我们应该更新代码以使用JavaScript来移动秒针。让我们使用此代码为秒针容器设置动画,每分钟动画60次。

/*
 * Move the second containers
 */
function moveSecondHands() {
  var containers = document.querySelectorAll('.seconds-container');
  setInterval(function() {
    for (var i = 0; i < containers.length; i++) {
      if (containers[i].angle === undefined) {
        containers[i].angle = 6;
      } else {
        containers[i].angle += 6;
      }
      containers[i].style.webkitTransform = 'rotateZ('+ containers[i].angle +'deg)';
      containers[i].style.transform = 'rotateZ('+ containers[i].angle +'deg)';
    }
  }, 1000);
}

通过JavaScript处理秒针和分针,更新CSS以使用transitions替换animation属性。

.minutes-container {
  transition: transform 0.3s cubic-bezier(.4,2.08,.55,.44);
}
.seconds-container {
  transition: transform 0.2s cubic-bezier(.4,2.08,.55,.44);
}

这些转换适用于transform属性并使用cubic-bezier定时函数。这个计时功能让秒针有些反弹。

iOS7 风格

我非常喜欢Apple iOS 7中使用的时钟的简单性。他们已经伸长了指针,但我更喜欢较短的版本。

通过设计针部样式并添加带有数字的背景图像,我们可以轻松创建此外观。

这种设计本身就是Hans Hilfiker对瑞士铁路时钟的演变。通过改变一些样式并添加新的背景图像,我们可以使我们的时钟适应这种风格。

如果您想出其他设计,请告诉我。

使用 Moment.js

我计划这篇文章的第一个想法之一是重建酒店钟表场景,三个时钟显示不同的时区。

调整不同时区的最简单方法是使用令人惊叹的Moment.js 时区库

您可以在此Codepen上查看代码。

浏览器兼容性

现代浏览器可以处理所涉及的CSS过渡和动画。 IE10 +,最近的Chrome和Firefox支持它们没有前缀,Safari需要-webkit前缀。

不要忘记在JavaScript中使用前缀属性。

(完)

本文作者 Thinker

本文如有错误之处,请留言,我会及时更正

觉得对您有帮助的话就点个赞收藏吧!

欢迎转载或分享,转载时请注明出处

阅读原文