or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

animation-generators.mdcore-animation.mdeasing.mdindex.mdinertia.mdutilities.md

inertia.mddocs/

0

# Inertia Animation

1

2

Specialized inertia animation that combines decay with spring physics for scroll-like behaviors with boundaries. Perfect for implementing momentum scrolling, drag interactions, and physics-based UI elements.

3

4

## Capabilities

5

6

### Inertia Function

7

8

Creates an inertia-based animation that uses decay motion with spring boundaries.

9

10

```typescript { .api }

11

/**

12

* Creates an inertia animation with decay and boundary springs

13

* @param options - Inertia configuration including boundaries and physics

14

* @returns PlaybackControls for stopping the animation

15

*/

16

function inertia(options: InertiaOptions): PlaybackControls;

17

18

interface InertiaOptions extends DecayOptions {

19

/** Spring stiffness when hitting boundaries */

20

bounceStiffness?: number;

21

/** Spring damping when hitting boundaries */

22

bounceDamping?: number;

23

/** Minimum boundary value */

24

min?: number;

25

/** Maximum boundary value */

26

max?: number;

27

/** Speed threshold for completion detection */

28

restSpeed?: number;

29

/** Distance threshold for completion detection */

30

restDelta?: number;

31

/** Custom driver for animation timing */

32

driver?: Driver;

33

/** Called with latest value on each frame */

34

onUpdate?: (v: number) => void;

35

/** Called when animation completes */

36

onComplete?: () => void;

37

/** Called when animation is stopped */

38

onStop?: () => void;

39

}

40

41

interface DecayOptions {

42

/** Starting value */

43

from?: number;

44

/** Target value (optional, not used in decay calculation) */

45

to?: number;

46

/** Initial velocity */

47

velocity?: number;

48

/** Decay power factor (default: 0.8) */

49

power?: number;

50

/** Time constant for decay rate */

51

timeConstant?: number;

52

/** Function to modify calculated target */

53

modifyTarget?: (target: number) => number;

54

/** Distance threshold for completion */

55

restDelta?: number;

56

}

57

58

interface PlaybackControls {

59

/** Stop the animation immediately */

60

stop: () => void;

61

}

62

```

63

64

**Usage Examples:**

65

66

```typescript

67

import { inertia } from "popmotion";

68

69

// Basic momentum scrolling

70

let scrollY = 0;

71

72

inertia({

73

from: scrollY,

74

velocity: -800, // Initial upward velocity

75

min: 0,

76

max: maxScrollHeight,

77

power: 0.8,

78

timeConstant: 750, // Default value

79

bounceStiffness: 500, // Default value

80

bounceDamping: 10, // Default value

81

onUpdate: (y) => {

82

scrollY = y;

83

scrollContainer.scrollTop = y;

84

},

85

onComplete: () => console.log("Scrolling stopped")

86

});

87

88

// Horizontal drag with boundaries

89

const controls = inertia({

90

from: currentX,

91

velocity: dragVelocity,

92

min: 0,

93

max: containerWidth - elementWidth,

94

power: 0.9,

95

bounceStiffness: 400,

96

bounceDamping: 30,

97

onUpdate: (x) => {

98

element.style.transform = `translateX(${x}px)`;

99

}

100

});

101

102

// Stop animation on user interaction

103

element.addEventListener('pointerdown', () => controls.stop());

104

```

105

106

### Behavior Characteristics

107

108

Inertia animation combines two physics models:

109

110

1. **Decay Phase**: When within boundaries, uses exponential decay

111

2. **Spring Phase**: When beyond boundaries, uses spring physics to bounce back

112

113

```typescript

114

// Example of combined behavior

115

inertia({

116

from: 50,

117

velocity: 1000, // Fast rightward motion

118

min: 0,

119

max: 100,

120

121

// Decay parameters (used within boundaries)

122

power: 0.8,

123

timeConstant: 325,

124

125

// Spring parameters (used at boundaries)

126

bounceStiffness: 300,

127

bounceDamping: 40,

128

129

onUpdate: (value) => {

130

// Animation will:

131

// 1. Decay from 50 towards calculated target (~200)

132

// 2. Hit max boundary (100) and spring back

133

// 3. Settle within 0-100 range

134

console.log(value);

135

}

136

});

137

```

138

139

## Implementation Patterns

140

141

### Momentum Scrolling

142

143

```typescript

144

import { inertia } from "popmotion";

145

146

class MomentumScroller {

147

private currentY = 0;

148

private maxScroll: number;

149

private animation?: PlaybackControls;

150

151

constructor(private element: HTMLElement) {

152

this.maxScroll = element.scrollHeight - element.clientHeight;

153

this.setupEventListeners();

154

}

155

156

private setupEventListeners() {

157

let startY = 0;

158

let lastY = 0;

159

let velocity = 0;

160

161

this.element.addEventListener('pointerdown', (e) => {

162

this.stopAnimation();

163

startY = e.clientY;

164

lastY = e.clientY;

165

velocity = 0;

166

});

167

168

this.element.addEventListener('pointermove', (e) => {

169

if (startY === 0) return;

170

171

const currentY = e.clientY;

172

const delta = currentY - lastY;

173

174

// Calculate velocity (with time consideration)

175

velocity = delta * 60; // Approximate pixels per second

176

177

this.currentY = Math.max(0, Math.min(this.maxScroll,

178

this.currentY - delta

179

));

180

181

this.updateScroll();

182

lastY = currentY;

183

});

184

185

this.element.addEventListener('pointerup', () => {

186

if (Math.abs(velocity) > 100) {

187

this.startInertia(velocity);

188

}

189

startY = 0;

190

});

191

}

192

193

private startInertia(velocity: number) {

194

this.animation = inertia({

195

from: this.currentY,

196

velocity: -velocity, // Invert for scroll direction

197

min: 0,

198

max: this.maxScroll,

199

power: 0.8,

200

timeConstant: 325,

201

bounceStiffness: 300,

202

bounceDamping: 40,

203

onUpdate: (y) => {

204

this.currentY = y;

205

this.updateScroll();

206

}

207

});

208

}

209

210

private updateScroll() {

211

this.element.scrollTop = this.currentY;

212

}

213

214

private stopAnimation() {

215

this.animation?.stop();

216

this.animation = undefined;

217

}

218

}

219

```

220

221

### Draggable Element with Boundaries

222

223

```typescript

224

import { inertia } from "popmotion";

225

226

class BoundedDraggable {

227

private currentX = 0;

228

private currentY = 0;

229

private animation?: PlaybackControls;

230

231

constructor(

232

private element: HTMLElement,

233

private bounds: { left: number; right: number; top: number; bottom: number }

234

) {

235

this.setupDragHandlers();

236

}

237

238

private setupDragHandlers() {

239

let startX = 0;

240

let startY = 0;

241

let velocityX = 0;

242

let velocityY = 0;

243

let lastTime = Date.now();

244

245

this.element.addEventListener('pointerdown', (e) => {

246

this.stopAnimation();

247

startX = e.clientX - this.currentX;

248

startY = e.clientY - this.currentY;

249

lastTime = Date.now();

250

});

251

252

this.element.addEventListener('pointermove', (e) => {

253

if (startX === 0 && startY === 0) return;

254

255

const now = Date.now();

256

const deltaTime = now - lastTime;

257

258

const newX = e.clientX - startX;

259

const newY = e.clientY - startY;

260

261

// Calculate velocity

262

velocityX = (newX - this.currentX) / deltaTime * 1000;

263

velocityY = (newY - this.currentY) / deltaTime * 1000;

264

265

this.currentX = newX;

266

this.currentY = newY;

267

this.updatePosition();

268

269

lastTime = now;

270

});

271

272

this.element.addEventListener('pointerup', () => {

273

if (Math.abs(velocityX) > 50 || Math.abs(velocityY) > 50) {

274

this.startInertia(velocityX, velocityY);

275

}

276

startX = startY = 0;

277

});

278

}

279

280

private startInertia(velocityX: number, velocityY: number) {

281

// Start separate inertia for X and Y axes

282

const xAnimation = inertia({

283

from: this.currentX,

284

velocity: velocityX,

285

min: this.bounds.left,

286

max: this.bounds.right,

287

power: 0.8,

288

bounceStiffness: 400,

289

bounceDamping: 40,

290

onUpdate: (x) => {

291

this.currentX = x;

292

this.updatePosition();

293

}

294

});

295

296

const yAnimation = inertia({

297

from: this.currentY,

298

velocity: velocityY,

299

min: this.bounds.top,

300

max: this.bounds.bottom,

301

power: 0.8,

302

bounceStiffness: 400,

303

bounceDamping: 40,

304

onUpdate: (y) => {

305

this.currentY = y;

306

this.updatePosition();

307

}

308

});

309

310

// Store reference to stop both animations

311

this.animation = {

312

stop: () => {

313

xAnimation.stop();

314

yAnimation.stop();

315

}

316

};

317

}

318

319

private updatePosition() {

320

this.element.style.transform =

321

`translate(${this.currentX}px, ${this.currentY}px)`;

322

}

323

324

private stopAnimation() {

325

this.animation?.stop();

326

this.animation = undefined;

327

}

328

}

329

```

330

331

## Parameter Guidelines

332

333

### Decay Parameters

334

- **Power**: 0.7-0.9 (higher = slower decay, more coasting) - Default: 0.8

335

- **TimeConstant**: 200-800ms (higher = longer momentum) - Default: 750

336

- **Velocity**: Measured in pixels/second from user interaction

337

338

### Bounce Parameters

339

- **BounceStiffness**: 200-600 (higher = snappier bounce back) - Default: 500

340

- **BounceDamping**: 10-60 (higher = less oscillation) - Default: 10

341

- **RestSpeed**: 0.01-1 (threshold for stopping)

342

- **RestDelta**: 0.01-1 (distance threshold for stopping)

343

344

### Boundary Configuration

345

- **Min/Max**: Set appropriate boundaries based on content size

346

- Use `modifyTarget` to implement custom boundary logic

347

- Consider different bounce characteristics for different boundaries