Skip to content

Using tweens.js

Tip

This course was updated in 2024. For the newer content, please visit Tween.js

Video Lecture

Using tweens.js

Description

Tweenjs is a JavaScript tweening engine.

A tween (from in-between) is a concept that allows you to change the values of the properties of an object smoothly. We can decide how long it should take, and if there should be a delay, and what to do each time the tween is updated, whether it should repeat and other things.

<>

Install Tween

Since Three r151, tween.module.min.js is no longer supplied by the Threejs library as shown in the video. Instead, we can install it from its official repository.

npm install @tweenjs/tween.js

It comes with its own type declarations, so it is also no longer necessary to manually set up a type definition for the library as shown in the video.

The import syntax has also changed. Use,

import TWEEN from '@tweenjs/tween.js'

Tween Easing Options

TWEEN.Easing._equation_._direction_

TWEEN.Easing.*equation*.*direction*

Start Scripts

./src/client/client.ts

Warning

Tween is now installed from the official repository rather than using it directly from the local Threejs libs folder as demonstrated in the video. Don't forget to Install Tween

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import Stats from 'three/examples/jsm/libs/stats.module'
import TWEEN from '@tweenjs/tween.js'

const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 2

const renderer = new THREE.WebGLRenderer()
//renderer.physicallyCorrectLights = true //deprecated
renderer.useLegacyLights = false //use this instead of setting physicallyCorrectLights=true property
renderer.shadowMap.enabled = true
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true

const sceneMeshes: THREE.Mesh[] = []
let monkey: THREE.Mesh

const loader = new GLTFLoader()
loader.load(
  'models/monkey_textured.glb',
  function (gltf) {
    gltf.scene.traverse(function (child) {
      if ((child as THREE.Mesh).isMesh) {
        const m = child as THREE.Mesh
        m.receiveShadow = true
        m.castShadow = true
        if (child.name === 'Plane') {
          sceneMeshes.push(m)
        } else if (child.name === 'Suzanne') {
          monkey = m
        }
      }
      if ((child as THREE.Light).isLight) {
        const l = child as THREE.SpotLight
        l.castShadow = true
        l.shadow.bias = -0.003
        l.shadow.mapSize.width = 2048
        l.shadow.mapSize.height = 2048
      }
    })
    scene.add(gltf.scene)
  },
  (xhr) => {
    console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
  },
  (error) => {
    console.log(error)
  }
)

window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize(window.innerWidth, window.innerHeight)
  render()
}

const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()

function onDoubleClick(event: MouseEvent) {
  mouse.set((event.clientX / renderer.domElement.clientWidth) * 2 - 1, -(event.clientY / renderer.domElement.clientHeight) * 2 + 1)
  raycaster.setFromCamera(mouse, camera)

  const intersects = raycaster.intersectObjects(sceneMeshes, false)

  if (intersects.length > 0) {
    // const p = intersects[0].point
    // controls.target.set(p.x, p.y, p.z)
    // new TWEEN.Tween(controls.target)
    //     .to({
    //         x: p.x,
    //         y: p.y,
    //         z: p.z
    //     }, 500)
    //     //.delay (1000)
    //     .easing(TWEEN.Easing.Cubic.Out)
    //     //.onUpdate(() => render())
    //     .start()
  }
}
renderer.domElement.addEventListener('dblclick', onDoubleClick, false)

const stats = new Stats()
document.body.appendChild(stats.dom)

function animate() {
  requestAnimationFrame(animate)

  controls.update()

  TWEEN.update()

  render()

  stats.update()
}

function render() {
  renderer.render(scene, camera)
}

animate()

./src/typings/tween.js/index.d.ts

Warning

It is no longer necessary to manually set up a type definition for Tween as shown in the video. A type definition is now included with the official Tween install. Don't forget to Install Tween

Create a new folder called ./src/typings/tween.js/ and add the TypeScript definition file for tween.js and name it index.d.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// It is no longer necessary to add this type definition. See warning above.
export class tween {
  Tween: typeof Tween
  update(time?: number): boolean
  getAll(): Tween[]
  removeAll(): void
  add(tween: Tween): void
  remove(tween: Tween): void
  Easing: {
    Linear: {
      None: (amount: number) => number
    }
    Quadratic: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
    Cubic: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
    Quartic: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
    Quintic: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
    Sinusoidal: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
    Exponential: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
    Circular: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
    Elastic: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
    Back: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
    Bounce: {
      In: (amount: number) => number
      Out: (amount: number) => number
      InOut: (amount: number) => number
    }
  }
}
declare type EasingFunction = (amount: number) => number
declare class Tween {
  constructor(object: any)
  isPlaying(): boolean
  isPaused(): boolean
  to(properties: {}, duration?: number): this
  duration(value: number): this
  start(time?: number | string): this
  stop(): this
  end(): this
  pause(time: number): this
  resume(time: number): this
  delay(amount: number): this
  repeat(times: number): this
  repeatDelay(amount: number): this
  onStart(callback: (object: any) => void): this
  onUpdate(callback: (object: any, elapsed: number) => void): this
  onRepeat(callback: (object: any) => void): this
  onComplete(callback: (object: any) => void): this
  onStop(callback: (object: any) => void): this
  easing(easing: EasingFunction): this
  chain(tween: Tween): this
}

declare const TWEEN: tween

./src/client/tsconfig.json

Warning

It is no longer necessary to modify the tsconfig.json since Tween is now installed from the official repository rather than using it directly from the local Threejs lib folder as demonstrated in the video. A type definition is now included in the official Tween install and is already correctly linked. Don't forget to Install Tween

Update the compiler options paths to point to the new tween.js typescript definition file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// It is no longer necessary to modify the tsconfig.json. See warning above.
{
  "compilerOptions": {
    "target": "ES6",
    "moduleResolution": "node",
    "strict": true,
    "paths": {
      "three/examples/jsm/libs/tween.module.min": ["../typings/tween.js"]
    }
  },
  "include": ["**/*.ts"]
}

Final Script

./src/client/client.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import Stats from 'three/examples/jsm/libs/stats.module'
import TWEEN from '@tweenjs/tween.js'

const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 2

const renderer = new THREE.WebGLRenderer()
//renderer.physicallyCorrectLights = true //deprecated
renderer.useLegacyLights = false //use this instead of setting physicallyCorrectLights=true property
renderer.shadowMap.enabled = true
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true

const sceneMeshes: THREE.Mesh[] = []
let monkey: THREE.Mesh

const loader = new GLTFLoader()
loader.load(
  'models/monkey_textured.glb',
  function (gltf) {
    gltf.scene.traverse(function (child) {
      if ((child as THREE.Mesh).isMesh) {
        let m = child as THREE.Mesh
        m.receiveShadow = true
        m.castShadow = true
        if (child.name === 'Plane') {
          sceneMeshes.push(m)
        } else if (child.name === 'Suzanne') {
          monkey = m
        }
      }
      if ((child as THREE.Light).isLight) {
        const l = child as THREE.SpotLight
        l.castShadow = true
        l.shadow.bias = -0.003
        l.shadow.mapSize.width = 2048
        l.shadow.mapSize.height = 2048
      }
    })
    scene.add(gltf.scene)
  },
  (xhr) => {
    console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
  },
  (error) => {
    console.log(error)
  }
)

window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize(window.innerWidth, window.innerHeight)
  render()
}

const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()

function onDoubleClick(event: MouseEvent) {
  mouse.set((event.clientX / renderer.domElement.clientWidth) * 2 - 1, -(event.clientY / renderer.domElement.clientHeight) * 2 + 1)
  raycaster.setFromCamera(mouse, camera)

  const intersects = raycaster.intersectObjects(sceneMeshes, false)

  if (intersects.length > 0) {
    const p = intersects[0].point

    //controls.target.set(p.x, p.y, p.z)

    // new TWEEN.Tween(controls.target)
    //     .to({
    //         x: p.x,
    //         y: p.y,
    //         z: p.z
    //     }, 500)
    //     //.delay (1000)
    //     .easing(TWEEN.Easing.Cubic.Out)
    //     //.onUpdate(() => render())
    //     .start()

    new TWEEN.Tween(monkey.position)
      .to(
        {
          x: p.x,
          // y: p.y + 1,
          z: p.z,
        },
        500
      )
      .start()

    new TWEEN.Tween(monkey.position)
      .to(
        {
          // x: p.x,
          y: p.y + 3,
          // z: p.z,
        },
        250
      )
      //.delay (1000)
      .easing(TWEEN.Easing.Cubic.Out)
      //.onUpdate(() => render())
      .start()
      .onComplete(() => {
        new TWEEN.Tween(monkey.position)
          .to(
            {
              // x: p.x,
              y: p.y + 1,
              // z: p.z,
            },
            250
          )
          //.delay (250)
          .easing(TWEEN.Easing.Bounce.Out)
          //.onUpdate(() => render())
          .start()
      })
  }
}
renderer.domElement.addEventListener('dblclick', onDoubleClick, false)

const stats = new Stats()
document.body.appendChild(stats.dom)

function animate() {
  requestAnimationFrame(animate)

  controls.update()

  TWEEN.update()

  render()

  stats.update()
}

function render() {
  renderer.render(scene, camera)
}

animate()

tween.js GitHub

Comments