走马灯的几种实现方式

2,909 阅读5分钟

一、名称来源

走马灯古称蟠螭灯(秦汉)、仙音烛和转鹭灯(唐)、马骑灯(宋),汉族特色工艺品 [1] ,亦是传统节日玩具之一,属于灯笼的一种。常见于元夕、元宵、中秋等节日。

灯内点上蜡烛,烛产生的热力造成气流,令轮轴转动。轮轴上有剪纸,烛光将剪纸的影投射在屏上,图象便不断走动。因多在灯各个面上绘制古代武将骑马的图画,而灯转动时看起来好像几个人你追我赶一样,故名走马灯。

百度百科

marquee 大帐篷

a large tent used for eating and drinking in at events held mainly outside that involve a lot of people

剑桥英语词典

二、应用

电脑中的跑马灯,跑马灯在编程中,通常指有时需要用一矩形条显示少量用户特别关心的信息,这条信息串首尾相连,向一个方向循环滚动。

另外,在日常生活中也随处可见,如 新闻播报 股票行情 电子广告牌 等。

三、实现方式

1、HTML (不推荐)

<marquee> 元素已经 过时,请不要再使用。尽管一些浏览器仍然支持它,但它不是必须的。此外,使用这个元素基本上是你可以对你的用户做最糟糕的事情之一,所以请不要这样做。

根据 MDN 的描述,<marquee> 标签已经不被推荐使用,虽然为了向后兼容在部分浏览器上该标签依然有效,但未来可能会被移除掉。至于可能的原因,有两个:

  • HTML的本质是标记语言,应当负责文档结构的部分,而不是表现形式;
  • 已经有了更好的CSS动画实现方案

相关的讨论:

why-is-marquee-deprecated-and-what-is-the-best-alternative

why-marquee-is-deprecated

因此不推荐使用此种方式实现。

2、CSS

2.1 基础实现:

  • HTML部分:
<div class="marquee">
  <span>I believe that the times ahead will be radically different from the times we have experienced so far in our lifetimes, though similar to many other times in history.</span>
</div>

外部容器 <div> 控制可视宽度,内部元素 <span> 展示具体内容

  • CSS部分
.marquee {
  width: 800px;
  margin: 0 auto;
  background: lightblue;
  overflow: hidden;
  white-space: nowrap;
  span {
    display: inline-block;
    padding-left: 100%;
    animation: marquee 15s linear infinite;
    &:hover {
      animation-play-state: paused;
    }
  }
}

@keyframes marquee {
  0% { transform: translate(0,0); }
  100% { transform: translate(-100%, 0); }
}

这里选择使用 transform 而不是 left ,是因为在动画中使用 left 会导致浏览器重绘,相比 transform 更耗性能,有兴趣可以阅读 Should I do the animation with Left or TranslateX

另外,使用 padding-left: 100% 百分比,而非固定宽度,使得改变文字内容时,无需重新设置宽度,而只需调整动画时长即可。

此种实现方式基本可以满足大部分需求。只是,在一次滚动结束后,下次滚动开始前,中间会有大量的留白。

2.2 无缝连接

为了解决两次滚动中间的留白,我们可以将内部元素复制一份,通过将他们首位相接的方式,来实现无缝连接的效果。

<div class="marquee">
    <span>&nbsp;&nbsp;&nbsp;&nbsp;I believe that the times ahead will be radically different from the times we have experienced so far in our lifetimes, though similar to many other times in history.</span>
  
    <span>&nbsp;&nbsp;&nbsp;&nbsp;I believe that the times ahead will be radically different from the times we have experienced so far in our lifetimes, though similar to many other times in history.</span>
</div>
.marquee {
  width: 800px;
  margin: 0 auto;
  background: lightblue;
  overflow: hidden;
  white-space: nowrap;
  span {
    display: inline-block;
    padding-left: 100%;
    animation: marquee 15s linear infinite;
  }
  &:hover span{
    animation-play-state: paused;
  }
}

@keyframes marquee {
  0% { transform: translate(0,0); }
  100% { transform: translate(-100%, 0); }
}

无缝连接的实现原理如下:

  1. 两个内部元素从各自初始位置(相隔100%宽度)同时滚动;
  2. 当内部元素1离开展示区时,内部元素2刚好进入展示区;
  3. 当内部元素2离开展示区时,内部元素1此时刚好播完一次动画,开始播放下一次动画,也就接上了内部元素2的尾巴,实现了无缝连接。

codepen

3、JavaScript

<div id="marquee">
  <span>I believe that the times ahead will be radically different from the times we have experienced so far in our lifetimes, though similar to many other times in history.</span>
</div>
#marquee {
  width: 800px;
  margin: 0 auto;
  background: lightblue;
  padding: 8px 16px;
  overflow: hidden;
  > span {
    display: inline-block;
    white-space: nowrap;
    position: relative;
  }
}
window.onload = () => {
  let box = document.querySelector("#marquee");
  let text = document.querySelector("#marquee > span");
  let boxWidth = box.offsetWidth; // offsetWidth = width + padding + margin
  let textWidth = text.offsetWidth;

  if (textWidth > boxWidth) {
    startAnimation();
  }

  function startAnimation() {
    return step();
  }

  function step() {
    let left = getLeft(text);
    setLeft(text, --left);
    if (left < -textWidth) {
      setLeft(text, boxWidth);
    }
    setTimeout(step, 30);
  }
  
  function getLeft(box) {
    let leftStr = box.style.left;
    if (leftStr === '') return 0;
    return leftStr.substr(0, leftStr.length-2) - 0;
  }
  
  function setLeft(box, left = 0) {
    box.style.left = `${left}px`;
  }
};

实现原理如下:

  1. 首先判断,若文字元素 offsetWidth 大于容器元素 offsetWidth,才继续下面的流程
  2. 添加轮询定时器,每隔一段时间向左移动文字元素,直到文字元素完全离开容器(即left 属性小于容器的宽度),则重置 left 属性为容器的宽度(即将文字元素左侧对齐容器右侧)

codepen

四、小结

本文主要介绍:

  • 跑马灯的来源
  • 几种实现方式

在日常开发中,CSS 的实现方式基本可以满足大部分场景,而且代码方便维护,建议优先考虑。

当然,除了使用 lefttransform 外,还有操纵其他属性来实现:

  • margin-left (✨)
  • left (✨✨)
  • transform( ✨✨✨)
  • scrollLeft(有取值限制)
  • padding-left(不允许负值)
  • 改变文字数量(鬼才)

感谢您的阅读,欢迎留言交流。

完。