使用requestAnimationFrame实现通知栏滚动

requestAnimationFrame是什么

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。如果想让浏览器在下一次重绘前继续更新下一帧动画,需要在回调函数中再次调用window.requestAnimationFrame()

回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的<iframe> 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

回调函数执行时,会被传入一个时间戳参数,表示当前执行回调函数的时刻。

如何使用

1
2
3
4
let frameId = window.requestAnimationFrame(callback)
// 停止动画
window.cancelAnimationFrame()

优缺点

setTimeout(callback, 16)类似,但又不同,requestAnimationFrame是通过浏览器刷新频率决定执行的最佳时机,动画不会出现卡顿现象。

  • 优点
    • 动画保持60fps(每帧16ms),浏览器内部决定渲染的最佳时机
    • API简洁标准,维护成本低
  • 缺点
    • 动画的开始/取消需要开发者自己控制
    • 浏览器标签未激活时,一切都不会执行
    • 老版本浏览器不支持IE9
    • Node.js不支持,无法用在服务器的文件系统事件

实现滚动通知栏

以下基于React实现了一个文字可滚动的通知栏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import React, { Component } from 'react'
export default class NoticeBar extends Component {
  constructor (props) {
    super(props)
    this.state = {
      closedfalse
    }
    this.marquee = null
    this.aFrameId = null
  }
  componentDidMount () {
    if (window.requestAnimationFrame && this.props.marqueeText) {
      let scrollWidth = this.marquee.parentElement.offsetWidth
      let textWidth = this.marquee.offsetWidth
      let right = 0
      let marquee = () => {
        right++
        if (right >= textWidth - scrollWidth + 30) {
          right = 0
        }
        this.marquee.style.right = right + 'px'
        this.aFrameId = window.requestAnimationFrame(marquee)
      }
      if(textWidth > scrollWidth) this.aFrameId = window.requestAnimationFrame(marquee)
    }
}
  componentWillUnmount () {
// 为了提高性能,组件卸载前,记得停止动画
    window.cancelAnimationFrame(this.aFrameId)
    this.aFrameId = null
  }
  onClose = () => {
    if (this.aFrameId) {
      window.cancelAnimationFrame(this.aFrameId)
      this.aFrameId = null
    }
    this.setState({ closedtrue })
  }
  render () {
    const { mode, marqueeText, onClick, children } = this.props
    const { onClose } = this
    const { closed } = this.state
    if (closed) return null
    return (
      <div className="notice-bar">
        <div className="notice-bar-icon"/>
        <div className="notice-bar-content">
          {!!children && children}
          {
            !!marqueeText && !children && (
              <div className="notice-bar-marquee-wrap">
                <div className="notice-bar-marquee" ref={ele => this.marquee = ele}>
                  {marqueeText}
                </div>
              </div>
            )
          }
        </div>
        {
          mode === 'link' && (
            <div className="notice-bar-operation" onClick={onClick}>
              <i className="icon-link"/>
            </div>
          )
        }
        {
          mode === 'closable' && (
            <div className="notice-bar-operation" onClick={onClose}>
              <i className="icon-close"/>
            </div>
          )
        }
      </div>
    )
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
.notice-bar{
  width100%;
  height30px;
  border-radius6px;
  background#ffe37e;
  font-size12px;
  color#222;
  display: flex;
  align-items: center;
  padding0 0 0 9px;
  & > .notice-bar-icon{
    background: url("../img/notice-bar-icon.png") center no-repeat;
    background-size100%;
    width9px;
    height9px;
    margin-right6px;
    flex-shrink0;
    flex-grow0;
  }
  & > .notice-bar-content{
    margin-right12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex1;
    & .notice-bar-marquee-wrap{
      overflow: hidden;
      & .notice-bar-marquee{
        white-space: nowrap;
        display: inline-block;
        position: relative;
      }
    }
  }
  & > .notice-bar-operation{
    height100%;
    display: flex;
    & .icon-close{
      background: url("../img/notice-bar-close.png") center no-repeat;
      background-size100%;
      width12px;
      height12px;
      display: block;
      align-self: flex-start;
      flex-shrink0;
      flex-grow0;
    }
    & .icon-link{
      background: url("../img/notice-bar-link.png") center no-repeat;
      background-size100%;
      width12px;
      height12px;
      display: block;
      align-self: center;
      flex-shrink0;
      flex-grow0;
      margin-right12px;
    }
  }
}
Enjoy it? Donate me!您的支持将鼓励我继续创作!

热评文章