Разговор об эффекте потолка и реакции-липкости

React.js

предисловие

Головная навигация в предыдущем проекте нужна была для достижения эффекта потолка. Сначала я это реализовал сам, но обнаружил, что эффект всегда был немного хуже. В то время я торопился реализовать функцию.react-stickyЭта библиотека, когда у меня есть время, я думаю о том, чтобы тщательно обдумать эту проблему с потолком.

1. Липкое позиционирование

Эффект потолка, естественно, придет на умposition:sticky, В Интернете также есть много связанной информации об этом объекте, вы можете проверить это самостоятельно. Просто упомянем одну вещь, которая отличалась от того, что я изначально ожидал:

Пример 1. В соответствии с моими ожиданиями, нормальный потолок

// html
<body>
    <div class="sticky">123</div>
</body>

// css  
body {
    height: 2000px;
}
div.sticky {
  position: sticky;
  top:0px;
}

Пример 2. Не соответствует моим ожиданиям Не может потолок

// html
<body>
  <div class='sticky-container'>
      <div class="sticky">123</div>
  </div>
</body>

// css  
body {
    height: 2000px;
}
div.sticky-contaienr {
    height: 1000px;  // 除非加上这段代码才会有一定的吸顶效果
}
div.sticky {
  position: sticky;
  top:0px;
}

Я думал просто добавитьposition:sticky, уже настроенtopЗначение , может быть потолком, вне зависимости от других элементов, это как раз тот эффект, который мне нужен, как в примере 1.

Но на самом деле дляposition:stickyДругими словами, его область действия может быть только внутри родительского элемента, и если он прокручивается за пределы родительского элемента, он не может быть высосан наверх. В примере 2.sticky-containerвысота и.stickyВысота одинаковая, при прокрутке нет эффекта потолка. Дать.sticky-containerнастраивать1000pxвысота, что.stickyможет быть там1000pxРоллинг потолок.

КонечноstickyЭто сделано для достижения более сложных эффектов.

Прикрепить ссылкуCSS Position Sticky - How It Really Works!

2. react-sticky

2.1 Использование

// React使用
<StickyContainer style={{height: 2000}}>
    <Sticky>
    {({style}) => {
        return <div style={style}>123 </div>         // 需要吸顶的元素
    }}
  </Sticky>
  其它内容
</StickyContainer>


// 对应生成的Dom
<div style='height: 2000px;'>                        // sticky-container
    <div>                                            //  parent
        <div style='padding-bottom: 0px;'></div>     //  placeholder
        <div>123 </div>                              // 吸顶元素
    </div>
   其它内容
</div>

2.2 Сомнение

Глядя на приведенный выше код React и соответствующую сгенерированную структуру dom, мы обнаруживаем, чтоStickyГенерируется вложенная структура div, оборачивающая элементы, которые нам действительно нужны, к потолку:

<div>                                            //  parent
    <div style='padding-bottom: 0px;'></div>     //  placeholder
    <div>123 </div>                              // 吸顶元素
</div>

Сначала я был немного озадачен, почему эта библиотека реализована именно так, не может ли она сгенерировать следующую структуру? минусdiv1,div2?

<div style='height: 2000px;'>
    <div>123 </div>
   其它内容
</div>

Так что мне было наплевать на чужой код, я написал демку локально, и думал, как добиться эффекта потолка, а потом потихоньку разобралсяreact-stickyдизайн.

2.3 Путаница

потолок, то есть когда页面滚动的距离Превосходить吸顶元素距离文档(而非浏览器窗口)顶部的高度Когда это так, потолочный элемент будет потолком, в противном случае потолочный элемент станет обычным позиционированием потока документов.

Так что, конечно, вы можете第一次Перед прокруткой пропустите элемент потолка (позже будет заменен на липкий)sticky.getBoundingClientRect().topПолучите расстояние элемента от верхней части html-документа, которое должно бытьhtmlTop, причина, по которой он выделяется перед первой прокруткой, заключается в том, что только перед первой прокруткой отображается расстояние от верхней части HTML-документа, а после этого она может представлять только расстояние от верхней части окна браузера.

пройти черезdocument.documentElement.scrollTopПолучите предполагаемое расстояние прокрутки страницыscrollTop, вычисляется каждый раз, когда срабатывает событие прокруткиscrollTop - htmlTop, больше 0,sticky元素的positionустановить какfixed, в противном случае он вернется в исходный режим позиционирования.

Это нормальный потолочный отсос, но будет проблема, т.к.stickyсталиfixedВне потока документов, что приводит к отсутствующей части содержимого документа. Представлять себе:

div1
div2
div3

1,2,3三个div,假如突然2变为fixed了,那么会变成:  

div1
div3 div2   

То есть после обсоса потолка содержимое div3 будет заблокировано div2.

Так что проверьте прямо сейчасreact-stickyВ сгенерированном доме потолочный элемент будет иметь родственный элементplaceholder. С заполнителем, даже если потолочный элементfixedЧтобы выйти из потока документов, есть также заполнитель, который занимает свое место:

div1
placeholder div2
div3

В то же время, поскольку к потолочному элементу добавляется родственный элемент, лучший способ справиться с ним — добавить еще одинparentОберните два элемента так, чтобы они не подвергались легкому влиянию других элементов и не подвергались легкому влиянию других элементов (наверное).

2.4 Исходный код

Его реализация также очень проста, достаточноSticky.jsа такжеContainer.jsДва файла, поговорим об этом немного. Код не вставлен, нажмите здесь, чтобы увидетьContainer.js, Sticky.js.

  • Сначала привяжите пакет событий:resize,scroll,touchstart,touchmove,touchend ,pageshow,load.
  • Через шаблон наблюдателя, когда запускаются вышеуказанные события,Containerинформация о местоположении передаетсяStickyна компоненте.
  • StickyЗатем компонент определяет, нужен ли он, вычисляя информацию о местоположении.fixedпозиция.

На самом деле, это так, конечно, это также поддерживаетrelative,stackedЕсть два режима, поэтому код усложняется. Посмотрите, чему мы можем научиться из этого:

  • использовалrafбиблиотека для управления анимацией, это правильноrequestAnimationFrameОбработка совместимости
  • использовать дляContext, и шаблон наблюдателя
  • Мне на самом деле нужно отслеживать очень много событий (в любом случае, я добавлю только прокрутку)
  • использоватьReact.cloneElementсоздавать элементы так, чтобы конечное использованиеStickyКомпоненты кажутся немного необычными в использовании.
  • disableHardwareAccelerationСвойство используется для отключения аппаратного ускорения анимации, по сути решая, следует ли устанавливатьtransform:"translateZ(0)";

Заинтересованы в последней точке знаний, всегда говорите использоватьtransformМожет запустить аппаратное ускорение, анимация плавнее, это правда? Поэтому я снова искал информацию.An Introduction to Hardware Acceleration with CSS Animations. проверить хром локальноperformanceдля открытияleft,top,fps,gpu,framesДождитесь появления зеленой столбчатой ​​линии, используйтеtransformЕсть только несколько зеленых столбчатых линий. Это имеет смысл.

3. Приходите к простому варианту

Разобравшись с исходным кодом, напишите (скопируйте) его самостоятельно и реализуйте только самую простую потолочную функцию:

import React, { Component } from 'react'

const events = [
  'resize',
  'scroll',
  'touchstart',
  'touchmove',
  'touchend',
  'pageshow',
  'load'
]

const hardwareAcceleration = {transform: 'translateZ(0)'}

class EasyReactSticky extends Component {
  constructor (props) {
    super(props)
    this.placeholder = React.createRef()
    this.container = React.createRef()
    this.state = {
      style: {},
      placeholderHeight: 0
    }
    this.rafHandle = null
    this.handleEvent = this.handleEvent.bind(this)
  }

  componentDidMount () {
    events.forEach(event =>
      window.addEventListener(event, this.handleEvent)
    )
  }

  componentWillUnmount () {
    if (this.rafHandle) {
      raf.cancel(this.rafHandle)
      this.rafHandle = null
    }
    events.forEach(event =>
      window.removeEventListener(event, this.handleEvent)
    )
  }

  handleEvent () {
    this.rafHandle = raf(() => {
      const {top, height} = this.container.current.getBoundingClientRect()
      // 由于container只包裹着placeholder和吸顶元素,且container的定位属性不会改变
      // 因此container.getBoundingClientRect().top大于0则吸顶元素处于正常文档流
      // 小于0则吸顶元素进行fixed定位,同时placeholder撑开吸顶元素原有的空间
      const {width} = this.placeholder.current.getBoundingClientRect()
      if (top > 0) {
        this.setState({
          style: {
            ...hardwareAcceleration
          },
          placeholderHeight: 0
        })
      } else {
        this.setState({
          style: {
            position: 'fixed',
            top: '0',
            width,
            ...hardwareAcceleration
          },
          placeholderHeight: height
        })
      }
    })
  }

  render () {
    const {style, placeholderHeight} = this.state
    return (
      <div ref={this.container}>
        <div style={{height: placeholderHeight}} ref={this.placeholder} />
        {this.props.content(style)}
      </div>
    )
  }
}

//使用
<EasyReactSticky content={style => {
    return <div style={style}>this is EasyReactSticky</div>
}} />

Очевидно, что большая часть кода заимствуетreact-sticky, уменьшая код конфигурации параметра и для двух режимовstackedа такжеrelativeслужба поддержки. Это действительно просто, и в то же время форма вызова компонента изменена, аrender-props.

4. Резюме

Эта статья возникает из-за небольшой дыры, оставленной предыдущей работой, стремящейся выполнить задачу, но, к счастью, теперь она заполнена.react-stickyна гитхабе1926Сама по себе звезда не сложна, и можно многому научиться, прочитав такую ​​маленькую библиотеку, которая выдержала испытание открытым исходным кодом.

5. Обсуждение

  • Если вы дадите использованиеtop,leftИзмените элемент на анимацию, например.An Introduction to Hardware Acceleration with CSS AnimationsВ первом примере добавьтеtransform:translateZ(0), Будет ли аппаратное ускорение? (Результат моего теста вроде никакой, впереди еще много покраски)

Добро пожаловать на обсуждение ~

использованная литература

react-sticky
CSS Position Sticky - How It Really Works!
An Introduction to Hardware Acceleration with CSS Animations
render-props