npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@andynoob/move-it

v1.0.0

Published

DOM rectangle transform controls

Readme

Move it!

hackatime badge

A library that adds simple PowerPoint like DOM object manipulation: resize, drag, and rotate. Pretty lightweight and comes with (very beautifully) styled controls. This library also provides the means for you to calculate collision via an implementation of oriented bounding boxes.

Installing

Run npm i @andynoob/move-it and read on.

The lifecycle

Fully functional sample code can be found here.

Please ensure that the moving element is absolutely positioned, relative to the control root. Otherwise, expect the transform controls to be mis-fit around the moving element.

To start, create an instance of Moving by calling createMoveMe (or MoveIt.createMoveMe). For example:

const el = document.querySelector(/* ... */);
const controlRoot = el.parentElement!;
const snapping = {
  // omitted... we'll get to this later
};

const moving = createMoveMe(el, {
  initialState: { // optional, the code will call computeState on the target (first parameter) if this is absent
    x: 200,
    y: 200,
    width: 200,
    height: 120,
    rotation: 75,
  },
  format: {
    // when asPercent = true, the x, y, width, and height values is in 
    // percentages (in decimal) relative to the control root
    asPercent: false,
    // when centered = true, the RectState represents the pivot of the element (which can be changed
    // by `pivotOffset`). this can be used with usePercent. when this is paired with `autoSize`,
    // the library will keep the element centered on the pivot point whenever resizing happens
    centered: false
  },
  snapping, // optional
  controlRoot, // required, sets the bounds for the object
  // when autoSize = true, the library stops assigning width and height directly via CSS, but rather syncs the 
  // `RectState` automatically whenever the size changes via DOM `ResizeObserver`.   
  // this also implicitly disables the resize feature. 
  autoSize: false,
  // percent (expressed as decimal) of each axis to offset the pivot point of the moving element.
  // this will impact the rotation pivot, the grid snapping location, and `autoSize` if enabled. the default pivot
  // point is the center of the element. to make it top left, do `{x: -0.5, y: -0.5}`. this will affect `centered` states
  pivotOffset: {x: 0, y: 0}
});

If initialState is not present as a part of the option parameter, computeState will be called to calculate a RectState from the target element (width, rotation, etc.).

Calling the createMoveMe function will return an instance of the Moving interface:

interface Moving {
  element: HTMLElement,
  id: string,
  /**
   * @description a copy of the current `RectState`. in the format option provided in the initial options
   */
  getState: () => RectState,
  /**
   * @description partial in the format option provided in the options
   */
  updateState: (partial: Partial<RectState>) => void,
  destroy: () => void,
  render: () => void,
  select: () => void,
  isSelected: () => boolean,
  checkBounds: () => void,
  updateControls: (select: boolean) => Controls,
  getCollisionSiblings: () => Moving[],
  /**
   * You need to do this for both instances, the behavior is not mirrored by default
   * For example, say you have `instanceA` and `instanceB`, you need to run both
   * `instanceA.addCollisionSibling(instanceB)` and `instanceB.addCollisionSibling(instanceA)`
   * for both instances to collide with the other.
   */
  addCollisionSibling: (sibling: Moving) => void,
  removeCollisionSibling: (sibling: Moving) => void,
  getOptions: () => MoveMeOpt
}

Call destroy when you're done moving the object.

The transform controls

The transform control is added immediately when you call the createMoveMe. As seen in the GIF below, it consists of five lines and five dots.

The CSS for these controls can be found here (the CONTROL_ID is E4UKgq3cxN, contrary to its name, it's actually a class). The style is injected into the <head> tag (if not present), under a <style> tag with id mGW3wTwrZ6. The cursor CSS property is updated accordingly to the rotation of the rectangle. Every moving element is given the move cursor.

The transformations

  1. Dragging the rectangle itself will move the rectangle.
  2. Dragging the lone dot protruding from the right side of the rectangle will start rotating.
  3. Dragging the sides of the rectangle will scale the rectangle in that direction only. Whereas the dots on the corners allow free transform. The user may hold shift to keep ratio when free transforming.

Snapping

There are two types of snapping behaviors: rotation snapping, and guideline based snapping.

| Rotation Snap | Guideline Snap | |-------------------------------------------------------------------------------|---------------------------------------------------------------------------------| | | |

When doResize is true, the grid x & y are percentages (0-1)

The SnappingOpt interface is defined as follows:

export interface SnappingOpt {
  rotation?: SnappingRotation,
  grid?: SnappingGrid
}

export interface SnappingGrid {
  /**
   * number of pixels away to snap the element
   */
  threshold: number,
  /**
   * number of pixels away to display the nearest grid/guideline
   */
  displayThreshold: number,
  verticalX?: number[], // relative to the control root
  horizontalY?: number[] // also relative to the control root
}

export interface SnappingRotation {
  anglesDeg: number[],
  threshold: number // degrees also
}

Simply provide that in the option parameter of createMoveMe, like so:

const moving = createMoveMe(el, {
  snapping: {
    rotation: {
      anglesDeg: [0, 180],
      threshold: 5
    },
    grid: {
      displayThreshold: 20,
      threshold: 5,
      verticalX: [controlRoot.offsetWidth / 2],
      horizontalY: [controlRoot.offsetHeight / 10 * 8]
    }
  },
  controlRoot
});

The snapping behavior can be disabled by the user if they hold shift while dragging/rotating the element.

Finding overlap

The Moving interface allows you to add another Moving instance as a collision sibling. When you do so, the current instance will collide with the other instance. You must do the same on the other instance for both collisions to be enabled.

You may manually trigger collision resolution by calling Moving.checkBounds. The underlying function that powers this feature is findOverlap. It takes two RectState parameters. You may check a moving element against a non-moving element by calling computeState on the non-moving element to obtain a RectState.

Auto size

Enabling this feature disables resizing.

demo gif

When enabled in MoveItOpt, the library will sync the width and height of the RectSate to the actual computed width and height of the moving element. Additionally, whenever the moving element resizes, the library will shift the element such that it's center remains anchored in the same location. You should store the RectState of such moving elements as centered=true (i.e. moving.getState(..., true)).

Credits

This project was made with the help of generative AI (GPT-5.3/5.5 & Claude Sonnet & Gemini & Deepseek). The tests are currently mostly generated by AI. The getDistanceToLine was mostly generated by Gemini.

Additionally, code snippets were taken from StackOverflow posts and Mozilla.

  1. The getRotation function was taken from here.
  2. The generateUID function was taken from here.
  3. The appendStylesheetRules function was taken from here.