跳转到正文
brucewar
返回上一页

使用requestAnimationFrame实现通知栏滚动

requestAnimationFrame是什么

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

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

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

如何使用

let frameId = window.requestAnimationFrame(callback)

// 停止动画
window.cancelAnimationFrame()

优缺点

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

实现滚动通知栏

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

import React, { Component } from 'react'

export default class NoticeBar extends Component {
  constructor (props) {
    super(props)
    this.state = {
      closed: false
    }
    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({ closed: true })
  }
  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>
    )
  }
}
.notice-bar{
  width: 100%;
  height: 30px;
  border-radius: 6px;
  background: #ffe37e;
  font-size: 12px;
  color: #222;
  display: flex;
  align-items: center;
  padding: 0 0 0 9px;
  & > .notice-bar-icon{
    background: url("../img/notice-bar-icon.png") center no-repeat;
    background-size: 100%;
    width: 9px;
    height: 9px;
    margin-right: 6px;
    flex-shrink: 0;
    flex-grow: 0;
  }
  & > .notice-bar-content{
    margin-right: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
    & .notice-bar-marquee-wrap{
      overflow: hidden;
      & .notice-bar-marquee{
        white-space: nowrap;
        display: inline-block;
        position: relative;
      }
    }
  }
  & > .notice-bar-operation{
    height: 100%;
    display: flex;
    & .icon-close{
      background: url("../img/notice-bar-close.png") center no-repeat;
      background-size: 100%;
      width: 12px;
      height: 12px;
      display: block;
      align-self: flex-start;
      flex-shrink: 0;
      flex-grow: 0;
    }
    & .icon-link{
      background: url("../img/notice-bar-link.png") center no-repeat;
      background-size: 100%;
      width: 12px;
      height: 12px;
      display: block;
      align-self: center;
      flex-shrink: 0;
      flex-grow: 0;
      margin-right: 12px;
    }
  }
}


下一篇
什么是thunk?