Nanopop

Minimalistic, small, positioning engine. Build for high-performance, minima...

README

Logo

    Ultra Tiny, Opinionated Positioning Engine

gzip size brotli size      alt="Build Status"

src="https://img.shields.io/github/workflow/status/Simonwep/nanopop/CI?style=flat-square"/>      alt="Download count"
src="https://img.shields.io/npm/dm/nanopop.svg?style=popout-square"> No dependencies      alt="JSDelivr download count"
src="https://data.jsdelivr.com/v1/package/npm/nanopop/badge">
  <img alt="Current version"
       src="https://img.shields.io/github/tag/Simonwep/nanopop.svg?color=3498DB&label=version&style=flat-square">
     alt="Support me" src="https://img.shields.io/badge/github-support-3498DB.svg?style=popout-square">


NanoPop is an ultra-tiny positioning engine. Hold up, isn't there PopperJS?
Yeah - and PopperJS is great! But there are tons of features that, in most cases, you just might not need. This library is less than a third of PopperJS.

When should I use Nanopop and not PopperJS?

1. Situations where you want full control over positioning, including handling events such as scrolling, and manual resizing.
2. Performance-critical cases with lots of elements [...] nanopop will only makes changes if you say so.
3. Poppers with minimal footprint such as drop-downs and tooltips which don't require that much configurability.
4. You might have some special needs about how your popper behaves. NanoPop exposes a function for the sole purpose of positioning something, use it in your own library!

This library was originally part of pickr - now ported to TS with tests and a few updates / bug-fixes.

Heads up! This is the readme for v2 - if you're looking for the first version head over here (v1 is not maintained anymore).


Getting Started


Install via npm:
  1. ``` sh
  2. $ npm install nanopop
  3. ```

Install via yarn:
  1. ``` sh
  2. $ yarn add nanopop
  3. ```

Include directly via jsdelivr:
  1. ``` html
  2. <script src="https://cdn.jsdelivr.net/npm/nanopop@2.0.0/lib/nanopop.min.js"></script>
  3. ```

Using JavaScript Modules:

  1. ```` js
  2. import {
  3.     reposition,   // Core, stateless function to reposition an element
  4.     createPopper, // Stateful function which keeps track of your configuration
  5.     defaults,     // A subset of nanopops options used as default values
  6.     version       // Current version
  7. } from 'https://cdn.jsdelivr.net/npm/nanopop/lib/nanopop.min.mjs'
  8. ````

🌟 NanoPop is fully tree-shakable! E.g. if you only use reposition you'll probably end up with less than 500B code!


Usage


  1. ``` js
  2. reposition(
  3.     /* reference: */ document.querySelector('.btn'),
  4.     /* popper: */ document.querySelector('.dropdown'),
  5.     /* We're using the default options */
  6. );
  7. ```

⚠ The popper-element must have set position to fixed.


ℹ Because the default-container is document.documentElement you might have to increase the height of the html element to make room for your popper (e.g. html {height: 100vh;})


All options

  1. ```ts
  2. import {reposition, createPopper} from 'nanopop';

  3. // Using a object and reposition directly
  4. const nanopop = reposition(reference, popper, {

  5.     // The DOMRect of the container, it used the html-element as default.
  6.     // You could also create your own boundary using a custon DOMRect (https://developer.mozilla.org/en-US/docs/Web/API/DOMRect)!
  7.     container: document.documentElement.getBoundingClientRect(),

  8.     // Margin between the popper element and the reference
  9.     margin: 8,

  10.     // Minimum space between the popper and the container
  11.     padding: 0,

  12.     // Preferred position, any combination of [top|right|bottom|left]-[start|middle|end] is valid.
  13.     // 'middle' is used as default-variant if you leave it out.
  14.     position: 'bottom-middle',

  15.     // In case the variant-part (start, middle or end) cannot be applied you can specify what (and if)
  16.     // should be tried next.
  17.     variantFlipOrder: {
  18.         start: 'sme', // In case of -start try 'start' first, if that fails 'middle' and 'end' if both doesn't work.
  19.         middle: 'mse',
  20.         end: 'ems'
  21.     },

  22.     // The same as variantFlipOrder, but if all variants fail you might want to try other positions.
  23.     positionFlipOrder: {
  24.         top: 'tbrl', // Try 'top' first, 'bottom' second, 'right' third and 'left' as latest position.
  25.         right: 'rltb',
  26.         bottom: 'btrl',
  27.         left: 'lrbt'
  28.     }
  29. });

  30. /**
  31. * Using the createPopper function to create a stateful wrapper
  32. *
  33. * Correct ways of calling it are:
  34. * createPopper(reference: HTMLElement, popper: HTMLElement, options?: NanoPopOptions)
  35. * createPopper(options?: NanoPopOptions)
  36. * ⚠ If you omit options entierly you'll have to set both the reference and the popper later when calling .update!
  37. */
  38. const popper = createPopper({...});
  39. popper.update(); // You can pass an object to update which will get merged with the existing config.
  40. ```

Calling popper.update(...) or reposition(...) both returns a position-pair (For example te for Top-End) or null based on if it was possible to find a position for the popper without clipping it._

Tip: The returned position-pair is perfect for tool-tips to give them a little arrow!


Caveats

1. The popper-element must have position set to fixed.
2. If nanopop cannot find a position without clipping your popper it'll revert its top and left values - you can use css / js to handle this case.