three-layout-box
TypeScript icon, indicating that this package has built-in type declarations

0.2.1 • Public • Published

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

Readme

Keywords

none

Package Sidebar

Install

npm i three-layout-box

Weekly Downloads

2

Version

0.2.1

License

ISC

Unpacked Size

13.4 kB

Total Files

5

Last publish

Collaborators

  • nomorejalapenos