Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <header class="header">
  <a href="" class="header__link">
    <h1 class="header__title">Stickybits</h1>
    <p>Check out Stickbits on Github</p>
  </a>
</header>
<main class="main">
  <div class="parent">
    <div class="child">
      <p>Child 1</p>
    </div>
  </div>
  <div class="parent">
    <div class="child">
      <p>Child 1</p>
    </div>
  </div>
  <div class="parent">
    <div class="child">
      <p>Child 1</p>
    </div>
  </div>
</main>
              
            
!

CSS

              
                body,
html {
  color: white;
  font-family: sans-serif;
  margin: 0;
  padding: 0;
}
header {
  text-align: center;
  h1 {
    margin-bottom: 0;
  }
}
a {
  color: black;
  text-decoration: none;
  &:hover {
   text-decoration: underline;
  }
}
main {
  counter-reset: div;
  display: flex;
  height: 100%;
  justify-content: space-between;
  max-width: 100vw;
  min-height: 2000px;
  position: absolute;
  top: 150px;
  width: 100%;
}
.child {
  padding: 1rem 0;
  text-indent: 1rem;
  width: 100%;
}
.parent {
  height: 300px;
  position: relative;
  width: 100%;
}
.parent:nth-child(odd) {
  background: tan;
}
.parent:nth-child(odd) .child {
  background-color: red;
}
.parent:nth-child(even) {
  background: aqua;
}
.parent:nth-child(even) .child {
  background-color: green;
}
.parent:before {
  counter-increment: div;
  content: 'Parent 'counter(div);
  left: 1rem;
  position: absolute;
  top: 1rem;
  z-index: 2;
}
.child.js-is-sticky {
  top: 0;
}
.child.js-is-stuck {
  bottom: 0;
}
              
            
!

JS

              
                /**
  stickybits - Stickybits is a lightweight alternative to `position: sticky` polyfills
  @version v3.7.10
  @link https://github.com/yowainwright/stickybits#readme
  @author Jeff Wainwright <yowainwright@gmail.com> (https://jeffry.in)
  @license MIT
**/
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = global || self, global.stickybits = factory());
}(this, (function () { 'use strict';

  function _extends() {
    _extends = Object.assign ? Object.assign.bind() : function (target) {
      for (var i = 1; i < arguments.length; i++) {
        var source = arguments[i];

        for (var key in source) {
          if (Object.prototype.hasOwnProperty.call(source, key)) {
            target[key] = source[key];
          }
        }
      }

      return target;
    };
    return _extends.apply(this, arguments);
  }

  /*
    STICKYBITS πŸ’‰
    --------
    > a lightweight alternative to `position: sticky` polyfills 🍬
    --------
    - each method is documented above it our view the readme
    - Stickybits does not manage polymorphic functionality (position like properties)
    * polymorphic functionality: (in the context of describing Stickybits)
      means making things like `position: sticky` be loosely supported with position fixed.
      It also means that features like `useStickyClasses` takes on styles like `position: fixed`.
    --------
    defaults πŸ”Œ
    --------
    - version = `package.json` version
    - userAgent = viewer browser agent
    - target = DOM element selector
    - noStyles = boolean
    - offset = number
    - parentClass = 'string'
    - scrollEl = window || DOM element selector || DOM element
    - stickyClass = 'string'
    - stuckClass = 'string'
    - useStickyClasses = boolean
    - useFixed = boolean
    - useGetBoundingClientRect = boolean
    - verticalPosition = 'string'
    - applyStyle = function
    --------
    propsπŸ”Œ
    --------
    - p = props {object}
    --------
    instance note
    --------
    - stickybits parent methods return this
    - stickybits instance methods return an instance item
    --------
    nomenclature
    --------
    - target => el => e
    - props => o || p
    - instance => item => it
    --------
    methods
    --------
    - .definePosition = defines sticky or fixed
    - .addInstance = an array of objects for each Stickybits Target
    - .getClosestParent = gets the parent for non-window scroll
    - .getTopPosition = gets the element top pixel position from the viewport
    - .computeScrollOffsets = computes scroll position
    - .toggleClasses = older browser toggler
    - .manageState = manages sticky state
    - .removeInstance = removes an instance
    - .cleanup = removes all Stickybits instances and cleans up dom from stickybits
  */
  var Stickybits = /*#__PURE__*/function () {
    function Stickybits(target, obj) {
      var _this = this;

      var o = typeof obj !== 'undefined' ? obj : {};
      this.version = '3.7.10';
      this.userAgent = window.navigator.userAgent || 'no `userAgent` provided by the browser';
      this.props = {
        customStickyChangeNumber: o.customStickyChangeNumber || null,
        noStyles: o.noStyles || false,
        stickyBitStickyOffset: o.stickyBitStickyOffset || 0,
        parentClass: o.parentClass || 'js-stickybit-parent',
        scrollEl: typeof o.scrollEl === 'string' ? document.querySelector(o.scrollEl) : o.scrollEl || window,
        stickyClass: o.stickyClass || 'js-is-sticky',
        stuckClass: o.stuckClass || 'js-is-stuck',
        stickyChangeClass: o.stickyChangeClass || 'js-is-sticky--change',
        useStickyClasses: o.useStickyClasses || false,
        useFixed: o.useFixed || false,
        useGetBoundingClientRect: o.useGetBoundingClientRect || false,
        verticalPosition: o.verticalPosition || 'top',
        applyStyle: o.applyStyle || function (item, style) {
          return _this.applyStyle(item, style);
        }
      };
      /*
        define positionVal after the setting of props, because definePosition looks at the props.useFixed
        ----
        -  uses a computed (`.definePosition()`)
        -  defined the position
      */

      this.props.positionVal = this.definePosition() || 'fixed';
      this.instances = [];
      var _this$props = this.props,
          positionVal = _this$props.positionVal,
          verticalPosition = _this$props.verticalPosition,
          noStyles = _this$props.noStyles,
          stickyBitStickyOffset = _this$props.stickyBitStickyOffset;
      var verticalPositionStyle = verticalPosition === 'top' && !noStyles ? stickyBitStickyOffset + "px" : '';
      var positionStyle = positionVal !== 'fixed' ? positionVal : '';
      this.els = typeof target === 'string' ? document.querySelectorAll(target) : target;
      if (!('length' in this.els)) this.els = [this.els];

      for (var i = 0; i < this.els.length; i++) {
        var _styles;

        var el = this.els[i];
        var instance = this.addInstance(el, this.props); // set vertical position

        this.props.applyStyle({
          styles: (_styles = {}, _styles[verticalPosition] = verticalPositionStyle, _styles.position = positionStyle, _styles),
          classes: {}
        }, instance);
        this.manageState(instance); // instances are an array of objects

        this.instances.push(instance);
      }
    }
    /*
      setStickyPosition βœ”οΈ
      --------
      β€”  most basic thing stickybits does
      => checks to see if position sticky is supported
      => defined the position to be used
      => stickybits works accordingly
    */


    var _proto = Stickybits.prototype;

    _proto.definePosition = function definePosition() {
      var stickyProp;

      if (this.props.useFixed) {
        stickyProp = 'fixed';
      } else {
        var prefix = ['', '-o-', '-webkit-', '-moz-', '-ms-'];
        var test = document.head.style;

        for (var i = 0; i < prefix.length; i += 1) {
          test.position = prefix[i] + "sticky";
        }

        stickyProp = test.position ? test.position : 'fixed';
        test.position = '';
      }

      return stickyProp;
    }
    /*
      addInstance βœ”οΈ
      --------
      β€” manages instances of items
      - takes in an el and props
      - returns an item object
      ---
      - target = el
      - o = {object} = props
        - scrollEl = 'string' | object
        - verticalPosition = number
        - off = boolean
        - parentClass = 'string'
        - stickyClass = 'string'
        - stuckClass = 'string'
      ---
      - defined later
        - parent = dom element
        - state = 'string'
        - offset = number
        - stickyStart = number
        - stickyStop = number
      - returns an instance object
    */
    ;

    _proto.addInstance = function addInstance(el, props) {
      var _this2 = this;

      var item = {
        el: el,
        parent: el.parentNode,
        props: props
      };

      if (props.positionVal === 'fixed' || props.useStickyClasses) {
        this.isWin = this.props.scrollEl === window;
        var se = this.isWin ? window : this.getClosestParent(item.el, item.props.scrollEl);
        this.computeScrollOffsets(item);
        this.toggleClasses(item.parent, '', props.parentClass);
        item.state = 'default';
        item.stateChange = 'default';

        item.stateContainer = function () {
          return _this2.manageState(item);
        };

        se.addEventListener('scroll', item.stateContainer);
      }

      return item;
    }
    /*
      --------
      getParent πŸ‘¨β€
      --------
      - a helper function that gets the target element's parent selected el
      - only used for non `window` scroll elements
      - supports older browsers
    */
    ;

    _proto.getClosestParent = function getClosestParent(el, match) {
      // p = parent element
      var p = match;
      var e = el;
      if (e.parentElement === p) return p; // traverse up the dom tree until we get to the parent

      while (e.parentElement !== p) {
        e = e.parentElement;
      } // return parent element


      return p;
    }
    /*
      --------
      getTopPosition
      --------
      - a helper function that gets the topPosition of a Stickybit element
      - from the top level of the DOM
    */
    ;

    _proto.getTopPosition = function getTopPosition(el) {
      if (this.props.useGetBoundingClientRect) {
        return el.getBoundingClientRect().top + (this.props.scrollEl.pageYOffset || document.documentElement.scrollTop);
      }

      var topPosition = 0;

      do {
        topPosition = el.offsetTop + topPosition;
      } while (el = el.offsetParent);

      return topPosition;
    }
    /*
      computeScrollOffsets πŸ“Š
      ---
      computeScrollOffsets for Stickybits
      - defines
        - offset
        - start
        - stop
    */
    ;

    _proto.computeScrollOffsets = function computeScrollOffsets(item) {
      var it = item;
      var p = it.props;
      var el = it.el;
      var parent = it.parent;
      var isCustom = !this.isWin && p.positionVal === 'fixed';
      var isTop = p.verticalPosition !== 'bottom';
      var scrollElOffset = isCustom ? this.getTopPosition(p.scrollEl) : 0;
      var stickyStart = isCustom ? this.getTopPosition(parent) - scrollElOffset : this.getTopPosition(parent);
      var stickyChangeOffset = p.customStickyChangeNumber !== null ? p.customStickyChangeNumber : el.offsetHeight;
      var parentBottom = stickyStart + parent.offsetHeight;
      it.offset = !isCustom ? scrollElOffset + p.stickyBitStickyOffset : 0;
      it.stickyStart = isTop ? stickyStart - it.offset : 0;
      it.stickyChange = it.stickyStart + stickyChangeOffset;
      it.stickyStop = isTop ? parentBottom - (el.offsetHeight + it.offset) : parentBottom - window.innerHeight;
    }
    /*
      toggleClasses βš–οΈ
      ---
      toggles classes (for older browser support)
      r = removed class
      a = added class
    */
    ;

    _proto.toggleClasses = function toggleClasses(el, r, a) {
      var e = el;
      var cArray = e.className.split(' ');
      if (a && cArray.indexOf(a) === -1) cArray.push(a);
      var rItem = cArray.indexOf(r);
      if (rItem !== -1) cArray.splice(rItem, 1);
      e.className = cArray.join(' ');
    }
    /*
      manageState πŸ“
      ---
      - defines the state
        - normal
        - sticky
        - stuck
    */
    ;

    _proto.manageState = function manageState(item) {
      var _this3 = this;

      // cache object
      var it = item;
      var p = it.props;
      var state = it.state;
      var stateChange = it.stateChange;
      var start = it.stickyStart;
      var change = it.stickyChange;
      var stop = it.stickyStop; // cache props

      var pv = p.positionVal;
      var se = p.scrollEl;
      var sticky = p.stickyClass;
      var stickyChange = p.stickyChangeClass;
      var stuck = p.stuckClass;
      var vp = p.verticalPosition;
      var isTop = vp !== 'bottom';
      var aS = p.applyStyle;
      var ns = p.noStyles;
      /*
        requestAnimationFrame
        ---
        - use rAF
        - or stub rAF
      */

      var rAFStub = function rAFDummy(f) {
        f();
      };

      var rAF = !this.isWin ? rAFStub : window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || rAFStub;
      /*
        define scroll vars
        ---
        - scroll
        - notSticky
        - isSticky
        - isStuck
      */

      var scroll = this.isWin ? window.scrollY || window.pageYOffset : se.scrollTop;
      var notSticky = scroll > start && scroll < stop && (state === 'default' || state === 'stuck');
      var isSticky = isTop && scroll <= start && (state === 'sticky' || state === 'stuck');
      var isStuck = scroll >= stop && state === 'sticky';
      /*
        Unnamed arrow functions within this block
        ---
        - help wanted or discussion
        - view test.stickybits.js
          - `stickybits .manageState  `position: fixed` interface` for more awareness πŸ‘€
      */

      if (notSticky) {
        it.state = 'sticky';
      } else if (isSticky) {
        it.state = 'default';
      } else if (isStuck) {
        it.state = 'stuck';
      }

      var isStickyChange = scroll >= change && scroll <= stop;
      var isNotStickyChange = scroll < change / 2 || scroll > stop;

      if (isNotStickyChange) {
        it.stateChange = 'default';
      } else if (isStickyChange) {
        it.stateChange = 'sticky';
      } // Only apply new styles if the state has changed


      if (state === it.state && stateChange === it.stateChange) return;
      rAF(function () {
        var _styles2, _classes, _styles3, _extends2, _classes2, _style$classes;

        var stateStyles = {
          sticky: {
            styles: (_styles2 = {
              position: pv,
              top: '',
              bottom: ''
            }, _styles2[vp] = p.stickyBitStickyOffset + "px", _styles2),
            classes: (_classes = {}, _classes[sticky] = true, _classes)
          },
          default: {
            styles: (_styles3 = {}, _styles3[vp] = '', _styles3),
            classes: {}
          },
          stuck: {
            styles: _extends((_extends2 = {}, _extends2[vp] = '', _extends2), pv === 'fixed' && !ns || !_this3.isWin ? {
              position: 'absolute',
              top: '',
              bottom: '0'
            } : {}),
            classes: (_classes2 = {}, _classes2[stuck] = true, _classes2)
          }
        };

        if (pv === 'fixed') {
          stateStyles.default.styles.position = '';
        }

        var style = stateStyles[it.state];
        style.classes = (_style$classes = {}, _style$classes[stuck] = !!style.classes[stuck], _style$classes[sticky] = !!style.classes[sticky], _style$classes[stickyChange] = isStickyChange, _style$classes);
        aS(style, item);
      });
    }
    /*
      applyStyle
      ---
      - apply the given styles and classes to the element
    */
    ;

    _proto.applyStyle = function applyStyle(_ref, item) {
      var styles = _ref.styles,
          classes = _ref.classes;
      // cache object
      var it = item;
      var e = it.el;
      var p = it.props;
      var stl = e.style; // cache props

      var ns = p.noStyles;
      var cArray = e.className.split(' '); // Disable due to bug with old versions of eslint-scope and for ... in
      // https://github.com/eslint/eslint/issues/12117
      // eslint-disable-next-line no-unused-vars

      for (var cls in classes) {
        var addClass = classes[cls];

        if (addClass) {
          if (cArray.indexOf(cls) === -1) cArray.push(cls);
        } else {
          var idx = cArray.indexOf(cls);
          if (idx !== -1) cArray.splice(idx, 1);
        }
      }

      e.className = cArray.join(' ');

      if (styles['position']) {
        stl['position'] = styles['position'];
      }

      if (ns) return; // eslint-disable-next-line no-unused-vars

      for (var key in styles) {
        stl[key] = styles[key];
      }
    };

    _proto.update = function update(updatedProps) {
      var _this4 = this;

      if (updatedProps === void 0) {
        updatedProps = null;
      }

      this.instances.forEach(function (instance) {
        _this4.computeScrollOffsets(instance);

        if (updatedProps) {
          // eslint-disable-next-line no-unused-vars
          for (var updatedProp in updatedProps) {
            instance.props[updatedProp] = updatedProps[updatedProp];
          }
        }
      });
      return this;
    }
    /*
      removes an instance πŸ‘‹
      --------
      - cleanup instance
    */
    ;

    _proto.removeInstance = function removeInstance(instance) {
      var _styles4, _classes3;

      var e = instance.el;
      var p = instance.props;
      this.applyStyle({
        styles: (_styles4 = {
          position: ''
        }, _styles4[p.verticalPosition] = '', _styles4),
        classes: (_classes3 = {}, _classes3[p.stickyClass] = '', _classes3[p.stuckClass] = '', _classes3)
      }, instance);
      this.toggleClasses(e.parentNode, p.parentClass);
    }
    /*
      cleanup πŸ›
      --------
      - cleans up each instance
      - clears instance
    */
    ;

    _proto.cleanup = function cleanup() {
      for (var i = 0; i < this.instances.length; i += 1) {
        var instance = this.instances[i];

        if (instance.stateContainer) {
          instance.props.scrollEl.removeEventListener('scroll', instance.stateContainer);
        }

        this.removeInstance(instance);
      }

      this.manageState = false;
      this.instances = [];
    };

    return Stickybits;
  }();
  /*
    export
    --------
    exports StickBits to be used 🏁
  */


  function stickybits(target, o) {
    return new Stickybits(target, o);
  }

  return stickybits;

})));

// v3
stickybits('.child');
              
            
!
999px

Console