preface

In the previous project, the head navigation needed to achieve the top sucking effect. At first, I implemented it myself, but the effect was always a little bit poor. At that time, I was anxious to achieve the function and found the library of React-sticky, but now I want to thoroughly figure out the top sucking problem.

1. Sticky positioning

The effect of top suction will naturally think of position:sticky, which is also a lot of related information on the Internet, you can check it by yourself. Just to mention one thing that was different from what I originally expected:

Example 1. As I expected, normal top suction

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

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

Example 2. It doesn’t meet my expectations

// 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; }Copy the code

I thought that as long as I added position:sticky, I set the value of top and it would suck the top, regardless of other elements, which is exactly what I need, as in example 1.

But in fact, for position:sticky, its range of activity can only be within the parent element, if the scroll beyond the parent element, it also cannot be top. In example 2, if the height of. Sticky -container is the same as that of. Set a 1000px height for. Sticky -container so that it will hit the top in 1000px scrolling.

Of course, Sticky is designed this way to achieve a more complex effect.

CSS Position Sticky – How It Really Works!

2. react-sticky

2.1 the use of

// React use <StickyContainer style={{height: 2000}}> <Sticky> {({style}) => {return<div style={style}>123 </div> // Sticky> Other content </StickyContainer> // Corresponding generated Dom <div style='height: 2000px; '>                        // sticky-container
    <div>                                            //  parent
        <div style='padding-bottom: 0px; '></div> // placeholder <div>123Copy the code

2.2 confused

React code: Sticky generates a nested div that wraps around the elements we really need to top:

<div>                                            //  parent
    <div style='padding-bottom: 0px; 'Placeholder / > < / div > < div > 123 < / div > / / element suction a top < / div >Copy the code

I was a little confused at first, why should this library be implemented this way, can’t it generate the following structure? Minus the div1, div2?

<div style='height: 2000px; '> <div>123 </div> Others </div>Copy the code

So I ignore other people’s code, write local demo, thinking about how to achieve the top effect, slowly understand the react-sticky design.

2.3 to reassure

Poptop, that is, the poptop element is poptop when the scrolling distance of the page exceeds the height of the poptop element from the top of the document (not the browser window), otherwise the poptop element becomes normal document flow positioning.

So can, of course, before the first scroll, through the top element absorption (later, you can substitute sticky sticky. GetBoundingClientRect (). The top the elements from the top of the HTML document distance hypothesis for htmlTop, stressed on the first scroll before because, Only before the first scroll represents the distance from the top of the HTML document, and after the scroll only represents the distance from the top of the browser window.

Through the document. The documentElement. ScrollTop the page scrolling distance hypothesis as the scrollTop, for each scroll event triggered when computing scrollTop – htmlTop, greater than 0 will be sticky elements of the set of fixed position, Otherwise, restore the original location mode.

This can be normal, but there will be a problem, because the sticky becomes fixed from the document flow, resulting in the lack of a piece of document content. Imagine:

Div1 div2 div3 1,2,3 divs, if suddenly 2 is fixed, then it will become: div1 div3 div2Copy the code

That is, after the top, the content of DIV3 will be blocked by DIV2.

So if you look at the DOM you just generated with the react-sticky, the top element will have a sibling, placeholder. With placeholder, even if the top element is fixed out of the document flow, there is a placeholder that holds its place:

div1
placeholder div2
div3
Copy the code

And since the top element has a sibling element, the best way to do that is to wrap the two elements with a parent element so that they are not susceptible to other elements or (I guess) other elements.

2.4 the source code

Its implementation is also very simple, just two files, Sticky. Js and container. js. The code doesn’t paste a point open here look at container.js, Sticky.js.

  • First bind a batch of events:resize.scroll.touchstart.touchmove.touchend ,pageshow.load.
  • With the observer mode, when these events are triggered, willContainerThe location information is passed toStickyComponents.
  • StickyThe component then calculates the location information to determine whether it is neededfixedPositioning.

That’s what it is, and of course it supports relative and STACKED, so the code is a little bit more complicated. See what we can learn from this:

  • With the help ofrafLibrary control animation, it’s rightrequestAnimationFrameCompatibility processing is performed
  • To useContextAnd the observer model
  • I need to listen for so many events (I would just add scroll anyway)
  • withReact.cloneElementTo create the element for final useStickyComponents feel a little unusual to use.
  • disableHardwareAccelerationProperty to turn off hardware acceleration for animation, essentially deciding whether to set ittransform:"translateZ(0)";

I’m interested in the last one. I always say that using transform will enable hardware acceleration and make animation smoother. An Introduction to Hardware Acceleration with CSS Animations. A local test of Chrome’s performance found only a few green bars for Left, Top, FPS, GPU,frames and transform. It makes sense.

3. Take the easy version

After understanding the source code to write (copy) one, only to achieve the most simple top function:

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 () / / since the container only wrapped placeholder and top element absorption, And the container positioning properties will not change. / / so the container getBoundingClientRect (). The top is greater than 0 elements in the normal flow of documents / / suction a top less than zero element fixed location and suction a top, At the same time open the placeholder element space of the original const suction a top {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. The content (style)} < / div >)}} / / using the < EasyReactSticky content={style => {return <div style={style}>this is EasyReactSticky</div>
}} />
Copy the code

Obviously, most of the code borrowed from react-sticky, reducing the parameter configuration code and supporting stacked and relative for the two modes. It’s pretty simple, and it changed the form of component calls to render-props.

4. To summarize

This article originated from a small hole left by the previous work rush to complete the task, fortunately, now fill in. React-sticky has 1,926 stars on Github, but it’s not complicated enough to learn from reading a library that has stood the test of open source.

5. Discuss

  • If you give it to onetop,leftChange the element to animate, for exampleAn Introduction to Hardware Acceleration with CSS AnimationsThe first example in addtransform:translateZ(0)Will there be hardware acceleration? (The result of my test seems to be no, still a lot of painting)

Welcome to discuss ~

The resources

react-sticky

CSS Position Sticky – How It Really Works!

An Introduction to Hardware Acceleration with CSS Animations

render-props