Sticky library for React.

Overview

react-sticky-el

Sticky library for React.

Demos

Installation

npm install react-sticky-el

Overview & Basic Example

The elements you actually want to "stick" should be wrapped in the tag. The full list of props are available below, but typical usage will look something like so:

app.jsx

import React, {Component} from 'react';
import Sticky from 'react-sticky-el';
...

class App extends Component ({
  render() {
    return (
      ...
        <Sticky>
          <header>
            ...
          </header>
        </Sticky>
      ...
    );
  },
});

This will be rendered as:

    <div> 
        <div> 
            <header>
                ...
            header>
        div>
    div>

'Sticky' will be - wrapper element, holder element is used to reserve space in DOM when holder element has 'fixed' position.

When the "stickiness" becomes activated, the following inline style rules are applied to the Sticky element:

  position: fixed;
  top: 0;
  left: 0;
  width: that was before 'stickiness'>

Note that the calculation of the Sticky element's height does not currently take margins into account. If you have margins on this element it may result in unexpected behavior.

Props

mode (default: 'top')

'top' or 'bottom' - to which side element should stick

disabled (default: false)

Allows you to disable sticking by setting this prop to true

onFixedToggle (default: null)

This handler will be called right before changing fixed state.

boundaryElement (default: null)

Selector to define a boundaryElement.
It should be one of the parents of the current element.

Look at these examples

scrollElement (default: window)

Selector to define a scrollElement. All position checks will be performed according to this element, also it will be listened for 'scroll' event.

It should be one of the parents of the current element. Possible selectors: 'body', 'window', '#{id}', anything suitable for Element.matches.

Look at the Demos for an example

app.jsx

import React, {Component} from 'react';
import Sticky from 'react-sticky-el';

class App extends Component {
  render() {
    return (
      <div>
        <p>....</p>  
        <div className="scrollarea" style={{height: '200px', overflow: 'scroll'}}>
          <Sticky scrollElement=".scrollarea">
            <h1>Scroll pane</h1>
          </Sticky>
        </div>
        <p>....</p>
      </div>
    );
  }
}

positionRecheckInterval (default: 0)

If your DOM structure is mutating (you are adding/removing elements), it will be usefull to provide positionRecheckInterval greater than zero, in this case position check will be also performed using setInterval in addition to scroll events.

stickyStyle (default: {})

In the event that you wish to override the style rules applied, simply pass in the style object as a prop:

app.jsx

  <Sticky stickyStyle={customStyleObject}>
    <header />
  </Sticky>

Note: You likely want to avoid messing with the following attributes in your stickyStyle: left, top, and width.

stickyClassName (default: 'sticky')

You can also specify a class name to be applied when the element becomes sticky:

app.jsx

  <Sticky stickyClassName={customClassName}>
    <header />
  </Sticky>

topOffset (default: 0)

Sticky state will be triggered when the top of the element is topOffset pixels from the top of the scrollElement. Positive numbers give the impression of a lazy sticky state, whereas negative numbers are more eager in their attachment

app.jsx

  <Sticky topOffset={80}>
    <SomeChild />
  </Sticky>

Look at the Basic Demo for an example

bottomOffset (default: 0)

Sticky state will be triggered when the bottom of the element is bottomOffset pixels from the bottom of the scrollElement.

app.jsx

  <Sticky bottomOffset={80}>
    <SomeChild />
  </Sticky>

Look at the Basic Demo for an example

hideOnBoundaryHit (default: true)

If false then the sticky element won't disappear on reaching it's boundaries. A configuration like this is implemented below.

  import Sticky from 'react-sticky-el';
  
  <div className="block">

    <Sticky boundaryElement=".block" hideOnBoundaryHit={false}>
      <SomeChild />
    </Sticky>
  </div>

Look at the Basic Demo for an example.

Note: If the scrollareaElement is not at the top of the viewport, when the sticky element overflows it, it will stay visible. This can not be fixed with overflow: hidden because the sticky element has position: fixed and it's relative to the viewport. A solution is to use clip-path: inset(0 0 0 0); on the scrollareaElement. This may have unforeseen consequences with other styles in your app - use caution. You can see this happening in this example

dontUpdateHolderHeightWhenSticky (default: false)

Controls whenever the min-height for holder element should be updated when element becomes sticky or not. You can set it to true if the height of your sticky element changes when it becomes sticky (e.g. only some part of the header is visible when it's sticky) and you want to avoid your content jumping up.

Look at the Demo for an example.

Other props

All other props (such as className, style, etc..) will be applied to the holder element.

Advanced usage

If you want to use some custom components and have more control, then you can use RenderPropSticky:

import { RenderPropSticky } from 'react-sticky-el';

function StickyHeader() {
  return (
    <RenderPropSticky
        mode={mode}
        onFixedToggle={onFixedToggle}
        hideOnBoundaryHit={hideOnBoundaryHit}
        offsetTransforms={offsetTransforms}
        disabled={disabled}
        boundaryElement={boundaryElement}
        scrollElement={scrollElement}
        bottomOffset={bottomOffset}
        topOffset={topOffset}
        positionRecheckInterval={positionRecheckInterval}
      >
        {({ isFixed, wrapperStyles, wrapperRef, holderStyles, holderRef }) => (
          <div {...rest} ref={holderRef} style={holderStyles}>
            <div
              {...rest}
              className={`${wrapperClassName} ${isFixed ? stickyClassName : ''}`}
              style={
                isFixed ? { ...wrapperStyles, ...stickyStyles } : wrapperStyles
              }
              ref={wrapperRef}
            >
              <h1>I'm sticky!</h1>
            </div>
          </div>
        )}
      </RenderPropSticky>  
  )
}

License

MIT

Issues
  • When sticky element has different styling the holder height causes layout shifts

    When sticky element has different styling the holder height causes layout shifts

    My initial element has an height of 100 px, this will be set as min-height when the element becomes fixed. This is correct.

    However my stuck element needs to become less tall so I shrink my font-size and make sure long titles don't wrap.

    After a sec the min-height of my holder element gets updated to the height of my stuck element which causes a layout shift.

    Is there a way to deal with this? In opinion the min-height should not be calculated by the stuck element.

    opened by martinbroos 11
  • Component hides when reach the boundary

    Component hides when reach the boundary

    Hey, I'm having a problem. When my component reaches the boundary of the parent element it hides, even though I've set the position of the boundaryElement to relative and hideOnBoundaryHit to false.

    I don't know what I'm doing wrong. Thanks.

    opened by rizwanahmed19 9
  • ScrollElement top distance not taken into account on hideOnBoundaryHit

    ScrollElement top distance not taken into account on hideOnBoundaryHit

    If there is some distance between the scrollElement and the top of the viewport, the sticky element will jump to the top of the page when hitting the boundary.

    This is happening here: https://github.com/gm0t/react-sticky-el/blob/e4c99e204017ce5b1aef92d9d1fab4321ccdca7a/src/render-props-version.js#L35-L37

    The value for top will end up being something like -Xpx and with position: fixed this refers to the viewport.

    This can be replicated by adding a margin to the scrollElement on the hideOnBoundaryHit > false example.

    It seems that taking the top value into account would solve the problem, but I'm not sure of the implications:

    { top: `${boundaryBottom - height - bottomOffset}px` }
    // simplifying { top: `${top + boundaryBottom - (top + height + bottomOffset)}px` }
    
    opened by danielfdsilva 8
  • "transform translateZ(0px)" breaks z-index

    Hi,

    With transform translateZ(0px):

    screen shot 2018-01-11 at 13 20 28

    Without:

    screen shot 2018-01-11 at 13 21 12

    The HTML code:

    screen shot 2018-01-11 at 13 23 33

    It's a react-dates element on the screenshot, but I noticed the same issue with react-select when the dropdown is opened.

    Thanks for the help.

    opened by ggregoire 6
  • Prop to disable sticking

    Prop to disable sticking

    Hi there - first, thanks for the library! I've found it much simpler to add to our project than react-sticky so far.

    Would it make sense to add an unstick prop to explicitly turn off the sticky styles? Currently I've achieved this by setting a conditional stickyClassName that adds position: relative !important (ugly!).

    Of course it is possible to just render <Sticky /> conditionally, but I am trying to avoid this to keep the DOM tree consistent for server rendering and have fewer conditions with nested JSX.

    enhancement 
    opened by chacestew 4
  • Issues with dropdown menu

    Issues with dropdown menu

    Dropdown menus are not clickable when navbar is in fixed position. I am using bootstrap 4

    opened by emasys 4
  • Make transform: translateZ(0px) optional

    Make transform: translateZ(0px) optional

    Hi,

    Example: LinkedIn. The grid has 3 columns. I am considering the third for this emphasis. The 3rd column sticks as the middle one is being scrolled down. However on a mobile you cannot view 2-3 columns and probably for this case the stickiness is entirely not required on mobile. My suggestion is to "activate" the transformation only on mobile or on demand. The issue I am facing (still keeping in mind a layout like LinkedIn) is that a Modal declared inside a sticky component doesn't expand to full screen, it takes the size of the sticky container. Without the transformation everything is fine. I am not able to disable the inline styling off the component by overwriting that transformation.

    I attached 3 images: first: shows the Photos sticky container before selecting a photo. The second show the gallery modal running inside the sticky container. The third is the modal when I disable the transformation from Chrome console 1 2 3

    Thank you

    opened by paulincai 4
  • Stop stick when reach element

    Stop stick when reach element

    Hello, i´m trying to stop the behaviour of stickiness of one element when reach a parent element, not to hide, just be fixed above that element, is possible?

    thanks in advance :)

    opened by nuno-correia-rodrigues 4
  • Move eslint-plugin-react and flow-bin to devDependencies?

    Move eslint-plugin-react and flow-bin to devDependencies?

    I have dozens of dependencies in my app that depend on these 2 modules, but react-sticky-el is the only one that has either of them in their production dependencies, so I wonder if that's a mistake. The eslint plugin seems obvious to me but I don't know anything about flow-bin.

    Including flow-bin as a production dependency adds 78 MB to the node_modules footprint of this otherwise very small module, so it would be really good to remove it if it's not needed for production.

    opened by jefftmills 4
  • Sticky wrapper blocks re-render of child component on update child state (only IOS devices)

    Sticky wrapper blocks re-render of child component on update child state (only IOS devices)

    I've recorded the video please check by the following URL. If you have any additional questions leave a comment. Thanks.

    opened by MikePrus 0
  • Sticky header + sticky sidebar + footer

    Sticky header + sticky sidebar + footer

    I have pretty complex structure on the website - Header (not sticky), large subheader (sticky), content col + sidebar (sticky), footer.

    I almost did it, except footer is overlap sticky sidebar with "top: 100px !important" attribute, not sure how to fix it

    I added sandbox for it https://codesandbox.io/s/jcf-bug-sandbox-vl1vv

    Would really appreciate, if somebody will give any suggestions regarding how can I fix this issue image

    opened by zhil 3
  • iOS zooming and panning makes footer move out of position

    iOS zooming and panning makes footer move out of position

    Hi!

    First of all, thank you for providing this project! It's really useful :)

    I have an issue with iOS (iPhone 8 and iOS 12.1). We tested it at our office and my other colleagues with different iPhones and versions of iOS experienced the same issue as well.

    When I zoom or pan the page the footer moves unexpectedly on the page and covers the other content. To reproduce simply go to the demo page on an iPhone and start zooming and/or panning.

    • Zooming-in will cause the footer to "move upwards" on the page, covering the content.
    • Panning to either side will cause the footer to "move sideways" on the page.

    I have made sure that we use no vertical margins in the footer. Any ideas of what is causing this and what could be a good workaround?

    opened by richardloof 0
  • Improve documentation for instances of fixed z-index elements

    Improve documentation for instances of fixed z-index elements

    Improve documentation for instances of fixed z-index elements in which the top or the bottom of the page. The page could have a navigation bar, etc.

    An example of mine that I'd like to write documentation on

    <div className={classnames('boundary-element', styles.boundaryElement_positioning)}></div>
            <Header pageName='schedule' />
            <div className={'container'}>
              <div className={''} style={{ overflow: 'scroll' }}>
                <Sticky
                  hideOnBoundaryHit={false}
                  boundaryElement=".boundary-element"
                  bottomOffset={0}
                  topOffset={-120}
                  stickyStyle={{ transform: 'translateY(120px)' }}
                >
                  <div className={'flex gridish-container--complete flex-justify-center '}>
                    <MediaQuery minWidth={481}>
                      <a className={styles.button_schedule} href="#friday">friday</a>
                      <a className={classnames(styles.button_schedule, styles.button_schedule__leftSpacing)} href="#saturday">saturday</a>
                      <a className={classnames(styles.button_schedule, styles.button_schedule__leftSpacing)} href="#sunday">sunday</a>
                    </MediaQuery>
                    <MediaQuery maxWidth={480}>
                      <a className={styles.button_schedule} href="#friday">fri</a>
                      <a className={classnames(styles.button_schedule, styles.button_schedule__leftSpacing)} href="#saturday">sat</a>
                      <a className={classnames(styles.button_schedule, styles.button_schedule__leftSpacing)} href="#sunday">sun</a>
                    </MediaQuery>
                  </div>
                </Sticky>
              </div>
    
    opened by JacobDFrank 5
  • Cannot read property

    Cannot read property "matches" of null

    I have built a mobile app using this package, and once the initial page loads, it stops loading React at all. It is working when being tested on browsers though and I am not sure why.

    Here's the error message:

    Uncaught TypeError: Cannot read property 'matches' of null at 8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:60347 at find.js (8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:60349) at g (8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:592) at d.n [as require] (8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:742) at e (8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:561) at sticky.js (8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:59986) at g (8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:592) at d.n [as require] (8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:742) at e (8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:561) at index.js (8bcfc8fa89c28c35a39b548a5b71ab733021a6e9.js?meteor_js_resource=true:formatted:59949)

    Here's line 60349 in bold

    if ("undefined" != typeof document) { var e = document.body; return "function" == typeof e.matches ? "matches" : "function" == typeof e.webkitMatchesSelector ? "webkitMatchesSelector" : "function" == typeof e.mozMatchesSelector ? "mozMatchesSelector" : "function" == typeof e.msMatchesSelector ? "msMatchesSelector" : "function" == typeof e.oMatchesSelector ? "oMatchesSelector" : null }

    opened by dsmalicsi 1
  • Issue with translateZ(0) on page load

    Issue with translateZ(0) on page load

    Hi, im not sure you are having this issue as well.

    But on page load in latest version of Google Chrome, the sticky plugin is wrapping the element with a translateZ(0) CSS style, which makes the element to be invisible.

    However, when I scroll down and the sticky occurs, the translateZ(0) CSS style is removed so the element is visible again.

    Any configuration that makes this working properly?

    Thank you

    opened by pedromagalhaes 2
  • In Microsoft Edge its not removing the sticky class after applying it

    In Microsoft Edge its not removing the sticky class after applying it

    Hi, thanks for this great plugin!

    Im using it to sticky the navigation in a website, and it works well in Chrome, but it doesnt work well in Edge. The sticky appends/wraps well when I pass the limit, but when i go back again to the initial state doesn't remove the sticky class anymore.

    Do you foresee any reason for this to happen?

    Thank you

    opened by pedromagalhaes 0
  • Cannot read property 'toLowerCase' of undefined

    Cannot read property 'toLowerCase' of undefined

    git clone -b react-sticky-el [email protected]:binary64/namjat.git react-sticky-el && cd react-sticky-el && yarn && yarn dev should get you http://localhost:3000/ which shows:

    Cannot read property 'toLowerCase' of undefined
    TypeError: Cannot read property 'toLowerCase' of undefined
        at ReactDOMServerRenderer.renderDOM (C:\p\react-sticky-el\node_modules\react-dom\cjs\react-dom-server.node.development.js:2304:28)
        at ReactDOMServerRenderer.render (C:\p\react-sticky-el\node_modules\react-dom\cjs\react-dom-server.node.development.js:2298:21)
        at ReactDOMServerRenderer.read (C:\p\react-sticky-el\node_modules\react-dom\cjs\react-dom-server.node.development.js:2234:19)
        at renderToString (C:\p\react-sticky-el\node_modules\react-dom\cjs\react-dom-server.node.development.js:2501:25)
        at renderPage (C:\p\react-sticky-el\node_modules\next\dist\server\render.js:176:26)
        at Function.getInitialProps (C:\p\react-sticky-el\node_modules\next\dist\server\document.js:93:25)
        at _callee$ (C:\p\react-sticky-el\node_modules\next\dist\lib\utils.js:36:30)
        at tryCatch (C:\p\react-sticky-el\node_modules\regenerator-runtime\runtime.js:62:40)
        at Generator.invoke [as _invoke] (C:\p\react-sticky-el\node_modules\regenerator-runtime\runtime.js:296:22)
        at Generator.prototype.(anonymous function) [as next] (C:\p\react-sticky-el\node_modules\regenerator-runtime\runtime.js:114:21)
        at step (C:\p\react-sticky-el\node_modules\babel-runtime\helpers\asyncToGenerator.js:17:30)
        at C:\p\react-sticky-el\node_modules\babel-runtime\helpers\asyncToGenerator.js:35:14
        at new Promise (<anonymous>)
        at new F (C:\p\react-sticky-el\node_modules\core-js\library\modules\_export.js:35:28)
        at C:\p\react-sticky-el\node_modules\babel-runtime\helpers\asyncToGenerator.js:14:12
        at loadGetInitialProps (C:\p\react-sticky-el\node_modules\next\dist\lib\utils.js:70:17)
    

    Any ideas? Many thanks in advance

    opened by binary64 0
  • "stickiness" not being activated

    I tried a simple example

    <Sticky>
        <div style={{background: 'red'}}>test</div>
    </Sticky>
    

    It seem to add the proper divs and styles

    <div style="min-height: 20px;">
        <div class="" style="transform: translateZ(0px);">
            <div style="background: red;">test</div>
        </div>
    </div>
    

    But when I scroll nothing happens, the fixed class is not being added so my test div remains hidden. Are there any cases when this component does not work ? (like particular css in parent)

    Thanks

    opened by ThunderDev1 6
Owner
Mikhail Bogdanov
Mikhail Bogdanov
A performant and comprehensive React sticky component.

react-stickynode A performant and comprehensive React sticky component. A sticky component wraps a sticky target and keeps the target in the viewport

Yahoo 945 Oct 14, 2021
Hide your header until you need it

react-headroom Hide your header until you need it. Demo http://kyleamathews.github.io/react-headroom Install npm install react-headroom Usage A simple

Kyle Mathews 1.6k Oct 10, 2021