index.mjs 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import { Feature } from '../Feature.mjs';
  2. import { observeIntersection } from './observers.mjs';
  3. const thresholdNames = {
  4. some: 0,
  5. all: 1,
  6. };
  7. class InViewFeature extends Feature {
  8. constructor() {
  9. super(...arguments);
  10. this.hasEnteredView = false;
  11. this.isInView = false;
  12. }
  13. startObserver() {
  14. this.unmount();
  15. const { viewport = {} } = this.node.getProps();
  16. const { root, margin: rootMargin, amount = "some", once } = viewport;
  17. const options = {
  18. root: root ? root.current : undefined,
  19. rootMargin,
  20. threshold: typeof amount === "number" ? amount : thresholdNames[amount],
  21. };
  22. const onIntersectionUpdate = (entry) => {
  23. const { isIntersecting } = entry;
  24. /**
  25. * If there's been no change in the viewport state, early return.
  26. */
  27. if (this.isInView === isIntersecting)
  28. return;
  29. this.isInView = isIntersecting;
  30. /**
  31. * Handle hasEnteredView. If this is only meant to run once, and
  32. * element isn't visible, early return. Otherwise set hasEnteredView to true.
  33. */
  34. if (once && !isIntersecting && this.hasEnteredView) {
  35. return;
  36. }
  37. else if (isIntersecting) {
  38. this.hasEnteredView = true;
  39. }
  40. if (this.node.animationState) {
  41. this.node.animationState.setActive("whileInView", isIntersecting);
  42. }
  43. /**
  44. * Use the latest committed props rather than the ones in scope
  45. * when this observer is created
  46. */
  47. const { onViewportEnter, onViewportLeave } = this.node.getProps();
  48. const callback = isIntersecting ? onViewportEnter : onViewportLeave;
  49. callback && callback(entry);
  50. };
  51. return observeIntersection(this.node.current, options, onIntersectionUpdate);
  52. }
  53. mount() {
  54. this.startObserver();
  55. }
  56. update() {
  57. if (typeof IntersectionObserver === "undefined")
  58. return;
  59. const { props, prevProps } = this.node;
  60. const hasOptionsChanged = ["amount", "margin", "root"].some(hasViewportOptionChanged(props, prevProps));
  61. if (hasOptionsChanged) {
  62. this.startObserver();
  63. }
  64. }
  65. unmount() { }
  66. }
  67. function hasViewportOptionChanged({ viewport = {} }, { viewport: prevViewport = {} } = {}) {
  68. return (name) => viewport[name] !== prevViewport[name];
  69. }
  70. export { InViewFeature };