{"id":13626538,"url":"https://github.com/spring-media/sticky-observer","last_synced_at":"2025-12-30T02:08:44.256Z","repository":{"id":33410349,"uuid":"157847205","full_name":"spring-media/sticky-observer","owner":"spring-media","description":"A simple and basic sticky observer (or watcher) on HTMLElement's in a given container","archived":false,"fork":false,"pushed_at":"2024-01-02T16:05:22.000Z","size":1638,"stargazers_count":31,"open_issues_count":18,"forks_count":0,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-03-15T22:21:42.827Z","etag":null,"topics":["observer","open-source","rabbit","sticky","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spring-media.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-11-16T09:51:13.000Z","updated_at":"2024-03-06T19:07:10.000Z","dependencies_parsed_at":"2024-01-14T07:17:51.629Z","dependency_job_id":"65565470-5752-440e-8387-73f3e5e85233","html_url":"https://github.com/spring-media/sticky-observer","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spring-media%2Fsticky-observer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spring-media%2Fsticky-observer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spring-media%2Fsticky-observer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spring-media%2Fsticky-observer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spring-media","download_url":"https://codeload.github.com/spring-media/sticky-observer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223716594,"owners_count":17191074,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["observer","open-source","rabbit","sticky","typescript"],"created_at":"2024-08-01T21:02:22.576Z","updated_at":"2025-12-30T02:08:44.225Z","avatar_url":"https://github.com/spring-media.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# sticky-observer [![CircleCI](https://circleci.com/gh/spring-media/sticky-observer.svg?style=svg)](https://circleci.com/gh/spring-media/sticky-observer)\n\n[![npm](https://img.shields.io/npm/v/@weltn24/sticky-observer.svg)](https://www.npmjs.com/package/@weltn24/sticky-observer)\n[![codecov](https://codecov.io/gh/spring-media/sticky-observer/branch/master/graph/badge.svg)](https://codecov.io/gh/spring-media/sticky-observer)\n[![dep](https://david-dm.org/spring-media/sticky-observer/dev-status.svg)](https://david-dm.org/spring-media/sticky-observer?type=dev)\n[![GitHub license](https://img.shields.io/github/license/spring-media/sticky-observer.svg)](https://github.com/spring-media/sticky-observer/blob/master/LICENSE)\n\nA simple and easy to use sticky observer (or watcher) on `HTMLElement`'s in a designated container. When scrolling or resizing the window sticky-observer will tell you if an element is `STICKY`, `STICKY_END_OF_CONTAINER` or `NORMAL`. This library does **NOT** include any preconfigured styling or positioning options, what happens when and how is left up to the you to configure with the help of some included helper functions.\n\n\u003e Bring-Your-Own-Styling (BYOS)\n\nThis library was heavily inspired by [sticky-js](https://github.com/rgalus/sticky-js), it uses the same calculations and exhibits the same internal behaviour but due to it being completely unstyled it fits a different use case, all styling is left up to the you (BYOS).\n\n## Features\n\n- Full control over styling/positioning/placeholder. NO magic BUT more work for you.\n- Observing still works correctly with dynamic (out of control) appended container (ads)\n- Written in TypeScript\n- No dependencies\n- Fully tested\n- Small (cjs: 6.78 KB / 1.75 KB gzip) (esm: 5.88 KB / 1.59 KB gzip)\n\n## Demo\n\nThis library is in production on [welt.de](https://www.welt.de) for a few main features.\n\n1. Sticky Page-Header (desktop only): [Demo](https://www.welt.de)\n2. Sticky Video-Player (desktop only): [Demo](https://www.welt.de/sport/fussball/article183952542/Nations-League-So-verhindert-die-deutsche-Nationalmannschaft-den-Abstieg.html)\n3. Sticky Social-Bar (mobile and desktop): [Demo](https://www.welt.de/services/article7893735/Impressum.html)\n\n## Install\n\n```bash\n# via NPM\nnpm install @weltn24/sticky-observer\n# via yarn\nyarn add @weltn24/sticky-observer\n```\n\n## Usage example\n\n```html\n\u003cdiv class=\"container\"\u003e\u003cdiv class=\"sticky-element\" data-sticky-class=\"sticky-element--is-sticky\"\u003eexample\u003c/div\u003e\u003c/div\u003e\n```\n\n```js\nimport { StickyObserver } from '@weltn24/sticky-observer';\n\nconst stickyContainer = document.querySelector('.container');\nconst stickyElement = document.querySelector('.sticky-element');\n\nconst stickyObserver = new StickyObserver([stickyElement], stickyContainer);\nstickyObserver.init();\n\nstickyObserver.onStateChange(event =\u003e {\n  switch (event.nextState) {\n    case 'STICKY':\n      event.element.sticky.addStickyClass();\n      event.element.sticky.addPlaceholder();\n      break;\n    case 'NORMAL':\n      event.element.sticky.removeStickyClass();\n      event.element.sticky.removePlaceholder();\n      break;\n    default:\n      // ignore\n      break;\n  }\n});\n\nstickyObserver.observe();\n```\n\n## Options\n\n```js\nnew StickyObserver([stickyElement], stickyContainer, { offsetTop: 20, offsetBottom: 20 });\n```\n\nEach sticky-element can be configured with a few options via HTML `[data-*]` attributes. All configuration options are optional.\n\n```html\n\u003cdiv class=\"container\"\u003e\n  \u003c!--\n    [data-sticky-class]\n    Css class to add when calling `addStickyClass()`. See API section.\n\n    [data-sticky-placeholder-class]\n    Css class to add when calling `addPlaceholder()` and [data-sticky-placeholder-auto-height] is `false`. See API section.\n\n    [data-sticky-placeholder-auto-height]\n    When calling `addPlaceholder()` a `\u003cdiv/\u003e` is added to the DOM with the same height of the sticky element.\n    With this option you can disable the auto height and add your own css class for example.\n\n    [data-sticky-offset-top]\n    The top offset takes part in the 'NORMAL' to 'STICKY' calculation.\n    With the offset you have some more control over the 'sticky breakpoint'. You can 'move' it up and down.\n    The value must be a number without any units.\n\n    [data-sticky-offset-bottom]\n    The bottom offset takes part in the 'STICKY' to 'STICKY_END_OF_CONTAINER' calculation.\n    With the offset you have some more control over the 'sticky breakpoint'. You can 'move' it up and down.\n    The value must be a number without any units.\n  --\u003e\n  \u003cdiv\n    class=\"sticky-element\"\n    data-sticky-class=\"sticky-element--is-sticky\"\n    data-sticky-placeholder-class=\"sticky-element__placeholder\"\n    data-sticky-placeholder-auto-height=\"false\"\n    data-sticky-offset-top=\"-20\"\n    data-sticky-offset-bottom=\"-20\"\n  \u003e\n    example\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n## API\n\n```js\n// StickyObserver API\n\n// Creating a new instance\n// [stickyElement]: Array of HTMLElement\n// stickyContainer: HTMLElement\n// options: optional options (see: Options section)\nconst stickyObserver = new StickyObserver([stickyElement], stickyContainer, options);\n\n// Mandatory\n// Lazy initialize function. Binds all internal listeners but does not start the observer.\nstickyObserver.init();\n\n// Mandatory\n// Starts the observer and notifies the listeners of any updates/changes/resizes.\nstickyObserver.observe();\n\n// All global listeners are still active but does not notify the listeners of any updates/changes/resizes.\nstickyObserver.pause();\n\n// Removes all global and element listeners. Deletes the `sticky` property of each sticky element\nstickyObserver.destroy();\n\n// Register a callback function to listen for (unique) state changes only (one event per change/transition).\n// A basic flow is: NORMAL (start) -\u003e STICKY -\u003e STICKY_END_OF_CONTAINER\nstickyObserver.onStateChange(stickyEvent =\u003e {});\n\n// Register a callback function to listen for all window scroll and resize events (not throttled)\nstickyObserver.onUpdate(stickyEvent =\u003e {});\n\n// Register a callback function to listen for all window resize events only (not throttled)\nstickyObserver.onResize(stickyEvent =\u003e {});\n\n// Is the sticky observer still active.\n// (boolean)\nconst isActive = stickyObserver.isActive();\n```\n\n```js\n// StickyState types\nstickyObserver.onStateChange(stickyEvent =\u003e {\n  // element is in default/non-sticky state\n  const isNormal = stickyEvent.nextState === 'NORMAL';\n\n  // element is in sticky state\n  const isSticky = stickyEvent.nextState === 'STICKY';\n\n  // element is below the the sticky container\n  const isStickyEndOfContainer = stickyEvent.nextState === 'STICKY_END_OF_CONTAINER';\n});\n```\n\n```js\n// StickyEvent API\nstickyObserver.onStateChange(stickyEvent =\u003e {\n  // The previous state of the sticky element\n  // (string)\n  const prevState = stickyEvent.prevState;\n\n  // The next or updated/current state of the sticky element\n  // (string)\n  const nextState = stickyEvent.nextState;\n\n  // The native HTMLElement with a `sticky` property for more options.\n  // (HTMLElement)\n  const element = stickyEvent.element;\n\n  // The current scroll position based on `window.pageYOffset`\n  // (number)\n  const scrollTop = stickyEvent.scrollTop;\n});\n```\n\n```js\n// Sticky element API\nstickyObserver.onStateChange(stickyEvent =\u003e {\n  const sticky = stickyEvent.element.sticky;\n\n  // The offset top value used for the sticky calculation.\n  // (number)\n  const offsetTop = sticky.offsetTop;\n\n  // The offset bottom value used for the sticky calculation.\n  // (number)\n  const offsetBottom = sticky.offsetBottom;\n\n  // The default height of the element in a non-sticky state.\n  // This is useful for an additional placeholder somewhere.\n  // (number)\n  const nonStickyHeight = sticky.nonStickyHeight;\n\n  // Containing the `width`, `height`, `top` and `left` of the sticky element.\n  // This is important for styling.\n  // (object)\n  const rect = sticky.rect;\n\n  // Reference to the sticky container element\n  // (HTMLElement)\n  const container = sticky.container;\n\n  // Containing the `width`, `height`, `top` and `left` of the sticky container element.\n  // (object)\n  const containerRect = sticky.container.rect;\n\n  // Active state of the sticky element.\n  // (boolean)\n  const active = sticky.active;\n\n  // Current state of the sticky element.\n  // (string)\n  const state = sticky.state;\n\n  // Configured sticky class\n  // (string)\n  const stickyClass = sticky.stickyClass;\n\n  // Configured placeholder class\n  // (string)\n  const placeholderClass = sticky.placeholderClass;\n\n  // Configured placeholder auto height\n  // (boolean)\n  const placeholderAutoHeight = sticky.placeholderAutoHeight;\n\n  // Adding the css class to the sticky element.\n  // (null safe)\n  sticky.addClass('some-special-class');\n\n  // Removes the css class from the sticky element.\n  // It does not throw an error when a class is not present\n  sticky.removeClass('some-special-class');\n\n  // Adds a configured sticky class.\n  // When no sticky class is configured it does nothing\n  sticky.addStickyClass();\n\n  // Removes the configured sticky class.\n  // When no sticky class is configured it does nothing\n  sticky.removeStickyClass();\n\n  // Adds a configured placeholder class.\n  // When no placeholder class is configured it does nothing.\n  sticky.addPlaceholder();\n\n  // Removes the configured placeholder class.\n  // When no placeholder class is configured it does nothing.\n  sticky.removePlaceholder();\n});\n```\n\n## Usage standalone Browser version\n\nSee [Demo](demo/index.html)\n\n## Browser support\n\nThis library is transpiled to ES5 without any special / custom browser API. This means:\n\n- in order for it to work on IE11 you must include the `classList` [polyfill](https://github.com/yola/classlist-polyfill)\n\nSticky-element works on all other major browsers.\n\n## Build\n\n```bash\nyarn install\nyarn build\n```\n\n## Test + Coverage\n\n```bash\n# You need a locally installed Chrome\nyarn test\n```\n\n## Release\n\nThe npm and GitHub releases are triggered manually (via `release-it`)\n\n```bash\n# You need a valid GITHUB_TOKEN\n# See: https://github.com/webpro/release-it#github-releases\nyarn publish\n```\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspring-media%2Fsticky-observer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspring-media%2Fsticky-observer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspring-media%2Fsticky-observer/lists"}