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 🙏

© 2024 – Pkg Stats / Ryan Hefner

three-layout-box

v0.2.1

Published

A small helper for binding Three.js objects to HTML elements.

Downloads

6

Readme

Three Layout Box

A small helper for binding Three.js objects to HTML elements.

What it looks like

The library exports the ThreeLayoutBox class and the resizeCamera function. The resizeCamera function resizes the camera so that the sizes of objects correspond to CSS pixels. The class binds a Three.js object to an HTML element, obtaining its dimensions, position, rotation, and transformation.

import * as THREE from 'three'
import { ThreeLayoutBox, resizeCamera } from 'three-layout-box'

...

const camera = THREE.PerspectiveCamera()

const geo = new THREE.BoxGeometry()
const mat = new THREE.MeshStandardMaterial()
const mesh = new THREE.Mesh()

const box = new ThreeLayoutBox({
  element: '.element-selector',
  object3d: mesh,
})

function resize() {
  ...
  resizeCamera(camera)
  box.resize()
  ...
}

function animationFrame() {
  ...
  box.update()
  ...
}

After binding the object, ThreeLayoutBox starts managing its position, size, and rotation. To intervene in these processes without disrupting the current values, the library implements a system of steps.

// 🛑
mesh.position.x = 100

// ✅
box.setPositionStep('🍓', '+', {
  x: 100,
})

After calling the box.update() method, the values of all steps will be calculated and assigned to the corresponding properties of the object.

ThreeLayoutBox

Constructor parameters

interface ThreeLayotBoxParameters {
  element: HTMLElement | string
  object3d?: Object3D
  containerElement?: HTMLElement | string
  scrollAxis?: 'x' | 'y' | 'z' | 'auto'
  frontSide?: 'left' | 'top'
  sizeStep?: boolean
  positionStep?: boolean
}

element

HTML element or element selector (uses querySelector).

object3d?

Any Three.js object that inherits from THREE.Object3D.

containerElement?

This element should correspond to the element containing the Three.js canvas. By default, document.body is used.

scrollAxis?

The axis along which the Three.js object will scroll. By default, auto is used.

frontSide?

If z is specified in scrollAxis and you have horizontal scrolling, you need to specify left in this parameter for correct position calculations. By default, top is used.

sizeStep?

If set to false, the Three.js object will not receive the element's size (CSS scale will still affect the object's size). By default, true is used.

positionStep?

If set to false, the Three.js object will not receive the element's position (CSS translate will still affect the object's position). By default, true is used.

Instance properties

class ThreeLayoutBox {
  get element(): HTMLElement

  get containerElement(): HTMLElement

  get position(): {
    x: number
    y: number
    z: number
  }

  get rotation(): {
    x: number
    y: number
    z: number
  }

  get scale(): {
    x: number
    y: number
    z: number
  }
}

element

The element used to obtain dimensions and position.

containerElement

The element used to obtain viewport dimensions.

position

An object that stores the calculated position of the element.

scale

An object that stores the calculated size of the element.

rotation

An object that stores the calculated rotation of the element.

Instance methods

class ThreeLayoutBox {
  bindObject(object: Object3D): void

  unbindObject(object: Object3D): void

  resize: () => void

  update: () => void

  destroy(): void

  setScrollStep(callback: () => { axis: 'x' | 'y'; value: number }): () => void

  deleteScrollStep(callback: () => { axis: 'x' | 'y'; value: number }): void

  setPositionStep(
    stepName: string,
    action: '+' | '-' | '*' | '/',
    value: { x?: number; y?: number; z: number }
  ): void

  setRotationStep(
    stepName: string,
    action: '+' | '-' | '*' | '/',
    value: { x?: number; y?: number; z: number }
  ): void

  setScaleStep(
    stepName: string,
    action: '+' | '-' | '*' | '/',
    value: { x?: number; y?: number; z: number }
  ): void

  deletePositionStep(stepName: string): void

  deleteRotationStep(stepName: string): void

  deleteScaleStep(stepName: string): void
}

bindObject(object)

Binds the object to the element and, for convenience, writes the current instance to userData.box. Same as passing the object to the constructor.

unbindObject(object)

Unbinds the object from the element.

resize()

After calling this method, the dimensions, position, rotation, and transformation of the element are read, and these values are written to the steps. This method should be called every time the window is resized.

update()

After calling this method, all steps are calculated, and the results are written to the position, scale, rotation properties of the object. This method should be called before rendering.

destroy()

This method should be called when the box is no longer needed.

set[Position | Rotation | Scale]Step(stepName, operation, object)

After binding the object, ThreeLayoutBox starts managing its position, size, and rotation. To intervene in these processes without disrupting the current values, the library implements a system of steps. The results of your created steps and the steps set by the library will be calculated and written to the corresponding object properties.

box.setPositionStep('any name', '+', {
  x: 100,
  y: 100,
  z: 100,
})

box.setRotationStep('any name', '+', {
  z: Math.PI / 2,
})

box.setScaleStep('any name', '/', {
  y: 2,
})

delete[Position | Rotation | Scale]Step(stepName)

Deletes the specified step.

setScrollStep(callback)

Sets a scroll step slightly differently, but the same mechanism is used inside the library.

box.setScrollStep(() => {
  return {
    axis: 'y',
    value: scrollY,
  }
})

❗️ NOTE ❗️

Native scrolling is not always synchronized with requestAnimationFrame, and if you render the scene inside the animation frame, you may notice a slight offset from the scroll.

The problem can be solved by updating and rendering everything inside the scroll event:

addEventListener('scroll', () => {
  // Render
})

But this approach is suitable only in very rare cases when there is no dynamics, so use scroll libraries that implement requestAnimationFrame.

Here is an example with the Lenis library:

...

const lenis = new Lenis()

box.setScrollStep(() => {
  return {
    axis: lenis.isHorizontal ? 'x' : 'y',
    value: lenis.animatedScroll,
  }
})

function animationFrame(t: number) {
  lenis.raf(t)
  box.update()
  // Render
  ...
}

deleteScrollStep(callback)

Deletes the scroll step.

box.deleteScrollStep(callback)

Static methods

For convenience, several methods have been made to control all boxes at once.

class LayoutBox {
  static resizeBoxes(): void
  static updateBoxes(): void
  static destroyBoxes(): void
}

resizeBoxes()

Resizes all created boxes at once.

function resize() {
  LayoutBox.resizeBoxes()
}

updateBoxes()

Updates all created boxes at once.

function animationFrame() {
  LayoutBox.updateBoxes()
}

destroyBoxes()

Destroys all created boxes at once.

LayoutBox.destroyBoxes()

resizeCamera

function resizeCamera(
  camera: PerspectiveCamera | OrthographicCamera,
  options?: {
    width?: number
    height?: number
    cameraDistance?: number
    cameraFar?: number
  }
)

Resizes the camera so that the sizes of objects correspond to CSS pixels. By default, after such a call:

resizeCamera(camera)

the following values are set:

camera.userData.distance = 1000
camera.position.z = 1000
camera.near = 1
camera.far = 11000

CSS

You can use any CSS values for positioning or setting sizes, but you should understand that they are applied to the object only after calling the resize method and then update. Since the resize method is most often called once after resizing the window, CSS animations and transition effects will not affect the bound object.

Since CSS does not have a property for creating depth, you can use the --depth variable for these purposes:

.element {
  width: 100px;
  height: 100px;
  --depth: 100px;
}

The object in depth is always in the middle of the element. In the example above, the object will be in front of the element by 50px. To move it back to match positions, use transform:

.element {
  width: 100px;
  height: 100px;
  --depth: 100px;

  transform: translateZ(-50px);
}

Examples