dollarshaveclub / stickybits

Stickybits is a lightweight alternative to `position: sticky` polyfills 🍬
https://github.com/yowainwright/stickybits
MIT License
2.19k stars 181 forks source link

Does not work in IE #419

Closed jacobshutty closed 5 years ago

jacobshutty commented 5 years ago

I know there have been quite a few IE issues opened, but I've been trying to get it to work on even a basic level in IE for hours.

I would post a codepen, but codepen doesn't work in IE. Which means I wasn't able to try out any of your examples either.

Here is my code. You can paste it as an html file and open it. I'm using IE 11, and the latest version of stickybit (3.5.7).

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div class="sticky-thing"></div>
  </body>
  <script>
    /**
   stickybits - Stickybits is a lightweight alternative to `position: sticky` polyfills
   @version v3.5.7
   @link https://github.com/dollarshaveclub/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.stickybits = factory());
    })(this, function() {
      "use strict";

      /*
        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'
        --------
        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
        - .removeClass = older browser support class remover
        - .removeInstance = removes an instance
        - .cleanup = removes all Stickybits instances and cleans up dom from stickybits
      */
      var Stickybits =
        /*#__PURE__*/
        (function() {
          function Stickybits(target, obj) {
            var o = typeof obj !== "undefined" ? obj : {};
            this.version = "3.5.7";
            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",
              /*
                define positionVal
                ----
                -  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,
              useStickyClasses = _this$props.useStickyClasses;
            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 el = this.els[i]; // set vertical position

              el.style[verticalPosition] = verticalPositionStyle;
              el.style.position = positionStyle;

              if (positionVal === "fixed" || useStickyClasses) {
                var instance = this.addInstance(el, this.props); // 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 _this = this;

            var item = {
              el: el,
              parent: el.parentNode,
              props: props,
            };
            this.isWin = this.props.scrollEl === window;
            var se = this.isWin
              ? window
              : this.getClosestParent(item.el, item.props.scrollEl);
            this.computeScrollOffsets(item);
            item.parent.className += " " + props.parentClass;
            item.state = "default";

            item.stateContainer = function() {
              return _this.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 = scrollElOffset + p.stickyBitStickyOffset;
            it.stickyStart = isTop ? stickyStart - it.offset : 0;
            it.stickyChange = it.stickyStart + stickyChangeOffset;
            it.stickyStop = isTop
              ? parentBottom - (el.offsetHeight + it.offset)
              : parentBottom - window.innerHeight;
            return it;
          };
          /*
            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) {
            // cache object
            var it = item;
            var e = it.el;
            var p = it.props;
            var state = it.state;
            var start = it.stickyStart;
            var change = it.stickyChange;
            var stop = it.stickyStop;
            var stl = e.style; // cache props

            var ns = p.noStyles;
            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";
            /*
              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 tC = this.toggleClasses;
            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";
            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";
              rAF(function() {
                tC(e, stuck, sticky);
                stl.position = pv;
                if (ns) return;
                stl.bottom = "";
                stl[vp] = p.stickyBitStickyOffset + "px";
              });
            } else if (isSticky) {
              it.state = "default";
              rAF(function() {
                tC(e, sticky);
                if (pv === "fixed") stl.position = "";
              });
            } else if (isStuck) {
              it.state = "stuck";
              rAF(function() {
                tC(e, sticky, stuck);
                if (pv !== "fixed" || ns) return;
                stl.top = "";
                stl.bottom = "0";
                stl.position = "absolute";
              });
            }

            var isStickyChange = scroll >= change && scroll <= stop;
            var isNotStickyChange = scroll < change || scroll > stop;
            var stub = "stub"; // a stub css class to remove

            if (isNotStickyChange) {
              rAF(function() {
                tC(e, stickyChange);
              });
            } else if (isStickyChange) {
              rAF(function() {
                tC(e, stub, stickyChange);
              });
            }

            return it;
          };

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

            return this;
          };
          /*
            removes an instance πŸ‘‹
            --------
            - cleanup instance
          */

          _proto.removeInstance = function removeInstance(instance) {
            var e = instance.el;
            var p = instance.props;
            var tC = this.toggleClasses;
            e.style.position = "";
            e.style[p.verticalPosition] = "";
            tC(e, p.stickyClass);
            tC(e, p.stuckClass);
            tC(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];
              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;
    });

    stickybits(".sticky-thing");
  </script>
  <script></script>
  <style>
    .sticky-thing {
      width: 100%;
      height: 300px;
      background-color: aquamarine;
    }
    body {
      height: 10000px;
    }
  </style>
</html>
yowainwright commented 5 years ago

@jacobshutty sorry about the issue you're having

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
     <style>
      .sticky-thing {
         width: 100%;
         height: 20px;
         background-color: aquamarine;
      }
    body {
      height: 10000px;
    }
  </style>
  </head>
  <body>
    <div class="sticky-thing"></div>
     <script src="https://unpkg.com/stickybits@3.5.7/dist/stickybits.min.js"></script>
     <script>
        stickybits(".sticky-thing");
     </script>
  </body>
</html>

Does that code snippet help you?

Thanks, Jeff

TheSench commented 5 years ago

Although codepen's editor does not support IE, you can still go to the view-only URLs in IE. Create the pen in a different browser, then click on "Change View" in the upper-right and select "Full Page". Copy that URL into IE to test there. If I import stickybits 3.5.7 and paste in your div and CSS, it works in IE for me.

Editor View: https://codepen.io/Jon-Senchyna/pen/yQvVJV Full Page View (loads in IE): https://codepen.io/Jon-Senchyna/full/yQvVJV