Repository: tweenjs/tween.js Branch: main Commit: 20079e65f77b Files: 90 Total size: 512.7 KB Directory structure: gitextract_1nyfih5p/ ├── .editorconfig ├── .github/ │ └── workflows/ │ └── tests.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.cjs ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README_zh-CN.md ├── assets/ │ └── style.css ├── dist/ │ ├── tween.amd.js │ ├── tween.cjs │ ├── tween.d.ts │ ├── tween.esm.js │ └── tween.umd.js ├── docs/ │ ├── contributor_guide.md │ ├── contributor_guide_zh-CN.md │ ├── user_guide.md │ └── user_guide_zh-CN.md ├── examples/ │ ├── 00_hello_world.html │ ├── 01_bars.html │ ├── 02_black_and_red.html │ ├── 03_graphs.html │ ├── 04_simplest.html │ ├── 05_video_and_time.html │ ├── 06_array_interpolation.html │ ├── 07_dynamic_to.html │ ├── 07a_dynamic_to_two_array_values.html │ ├── 07b_dynamic_to_an_array_of_values.html │ ├── 08_repeat.html │ ├── 09_relative_values.html │ ├── 09_relative_values.js │ ├── 10_yoyo.html │ ├── 10_yoyo.js │ ├── 11_stop_all_chained_tweens.html │ ├── 12_graphs_custom_functions.html │ ├── 13_relative_start_time.html │ ├── 14_pause_tween.html │ ├── 15_complex_properties.html │ ├── 16_animate_an_array_of_values.html │ ├── 17_generate_pow.html │ ├── 18_start_from_current_values.html │ ├── 19_end_tween.html │ ├── css/ │ │ └── style.css │ ├── example-projects/ │ │ ├── nodejs-commonjs/ │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── nodejs-esmodules/ │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── plain-javascript-modules/ │ │ │ ├── README.md │ │ │ ├── animate.js │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── style.css │ │ └── plain-typescript-modules/ │ │ ├── README.md │ │ ├── animate.js │ │ ├── animate.ts │ │ ├── index.html │ │ ├── index.js │ │ ├── index.ts │ │ ├── package.json │ │ ├── style.css │ │ └── tsconfig.json │ ├── js/ │ │ ├── createGraph.js │ │ ├── createPath.js │ │ ├── drawings.js │ │ └── toPhysicalPx.js │ └── video/ │ └── sintel.webm ├── package.json ├── rollup.config.js ├── scripts/ │ └── write-version.js ├── src/ │ ├── Easing.ts │ ├── Group.ts │ ├── Index.ts │ ├── Interpolation.ts │ ├── Now.ts │ ├── Sequence.ts │ ├── Tween.ts │ ├── Version.ts │ ├── mainGroup.ts │ ├── test-performance-now-fake.ts │ └── tests.ts ├── test/ │ └── unit/ │ ├── nodeunit.html │ ├── nodeunit.js │ └── nodeunitheadless.cjs └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: http://EditorConfig.org # top-most EditorConfig file root = true [*] charset = utf-8 indent_style = tab end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.md] indent_size = 2 indent_style = space ================================================ FILE: .github/workflows/tests.yml ================================================ # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: build and test on: [push] jobs: build-and-test: runs-on: ubuntu-latest strategy: matrix: node-version: [lts/*] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm clean-install - run: npm test ================================================ FILE: .gitignore ================================================ .DS_Store .idea .vscode .tmp build/npm* node_modules npm-debug.log ================================================ FILE: .npmignore ================================================ assets/ bower.json docs/ examples/ CONTRIBUTING.md test/ ================================================ FILE: .prettierignore ================================================ dist/ .tmp/ .vscode/ examples/js/stats.min.js examples/example-projects/plain-typescript-modules/**/*.js ================================================ FILE: .prettierrc.cjs ================================================ module.exports = { arrowParens: 'avoid', bracketSpacing: false, printWidth: 120, semi: false, singleQuote: true, trailingComma: 'all', useTabs: true, } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at coc@soledadpenades.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing ## Code of conduct Please note that this project is released with a [Contributor Code of Conduct](./CODE_OF_CONDUCT.md). **By participating in this project you agree to abide by its terms**. ## Before reporting a bug If you find something that you believe to be a bug, please 1. search the [issue tracker](https://github.com/tweenjs/tween.js/issues) for similar issues 2. check out the [master](https://github.com/tweenjs/tween.js/tree/master) branch and see if the bug still exists there. ## How to report a bug 1. Specify the revision number of the tween.js library where the bug occurred 2. Specify your browser version and operating system (i.e. Chrome 23.0.1271.95, Windows 7) 3. Describe the problem in detail. What happened? What did you expect to happen? 4. Provide a small test case (e.g. using http://jsfiddle.net). Or if not possible, provide a link to a live version of your application. ## Contributing 1. Get a GitHub account (if you don't have one yet). 2. Fork the project in GitHub. 3. Check the [contribution guidelines](https://github.com/tweenjs/tween.js/wiki/Contributing-to-tween.js). 4. Make changes to your clone of the repository 5. Submit a pull request. _If you tried all of the above and still can't fix the bug, or you're not sure you're doing things right, [let us know](https://github.com/tween.js/tween.js/issues)._ ================================================ FILE: LICENSE ================================================ The MIT License Copyright (c) 2010-2012 Tween.js authors. Easing equations Copyright (c) 2001 Robert Penner http://robertpenner.com/easing/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # tween.js JavaScript (TypeScript) tweening engine for easy animations, incorporating optimised Robert Penner's equations. [![NPM Version][npm-image]][npm-url] [![CDNJS][cdnjs-image]][cdnjs-url] [![NPM Downloads][downloads-image]][downloads-url] [![Build and Tests][ci-image]][ci-url] More languages: [English](./README.md), [简体中文](./README_zh-CN.md) --- ```html
``` [Try this example on CodePen](https://codepen.io/trusktr/pen/KKGaBVz?editors=1000) # Features - Does one thing only and does it well: tweens properties of an object - Doesn't take care of CSS units (e.g. appending `px`) - Doesn't interpolate colors - Easing functions are reusable outside of Tween - Can also use custom easing functions - Doesn't make its own animation loop, making it flexible for integration into any animation loop. # Examples
hello world hello world
(source)
Bars Bars
(source)
Black and red Black and red
(source)
Graphs Graphs
(source)
Simplest possible example Simplest possible example
(source)
Video and time Video and time
(source)
Array interpolation Array interpolation
(source)
Dynamic to, object Dynamic to, object
(source)
Dynamic to, interpolation array Dynamic to, interpolation array
(source)
Dynamic to, large interpolation array Dynamic to, large interpolation array
(source)
Repeat Repeat
(source)
Relative values Relative values
(source)
Yoyo Yoyo
(source)
Stop all chained tweens Stop all chained tweens
(source)
Custom functions Custom functions
(source)
Relative start time Relative start time
(source)
Pause tween Pause tween
(source)
Complex properties Complex properties
(source)
Animate an array of values Animate an array of values
(source)
# Installation The recommended method is to use `import` syntax. Here we've listed various install methods starting roughly with the most recommended first and least desirable last. Evaluate all of the following methods to pick what is most suitable for your project. ## With `npm install` and `import` from `node_modules` You can add tween.js as an npm dependency: ```bash npm install @tweenjs/tween.js ``` ### Without a build tool #### Installed locally You can import from `node_modules` if you serve `node_modules` as part of your website, using a standard `importmap` script tag. First, assuming `node_modules` is at the root of your website, you can write an import map like so in your HTML file: ```html ``` Now in any of your module scripts you can import Tween.js by its package name: ```html ``` #### Import from CDN Note that, without the `importmap`, you can import directly from a CDN as with the first example above, like so: ```html ``` You can also link your `importmap` to the CDN instead of a local `node_modules` folder, if you prefer that: ```html ``` ### With a build tool If you are using [Node.js](https://nodejs.org/), [Parcel](https://parceljs.org/), [Webpack](https://webpack.js.org/), [Rollup](https://rollupjs.org/), [Vite](https://vitejs.dev/), or another build tool, then you can install `@tweenjs/tween.js` with `npm install @tweenjs/tween.js`, and `import` the library into your JavaScript (or TypeScript) file, and the build tool will know how to find the source code from `node_modules` without needing to create an `importmap` script: ```javascript import * as TWEEN from '@tweenjs/tween.js' ``` However, note that this approach requires always running a build tool for your app to work, while the `importmap` approach will simply work without any build tools as a simple static HTML site. ## Manual build Another approach is to download the source code with git, manually build the library, then place the output in your project. Node.js is required for this. ```bash git clone https://github.com/tweenjs/tween.js cd tween.js npm install npm run build ``` This will create some builds in the `dist` directory. There are currently two different builds of the library: - ES6 Module in `/dist/tween.esm.js` (recommended) - UMD in `/dist/tween.umd.js` (deprecated, will be removed in a future major version) You are now able to copy one of those two files into your project, and use like this (recommended): ```html ``` or (deprecated, to be removed in future major): ```html ``` where `path/to` is replaced with the location where you placed the file. > [!Note] > You can also download these files from unpkg, for example here: > https://unpkg.com/browse/@tweenjs/tween.js@23.1.3/dist/ ## Global variable from CDN (deprecated) > [!Note] > This method is deprecated and will be removed in a future major version! Install a global `TWEEN` variable from a content-delivery network (CDN) using the UMD file. From cdnjs: ```html ``` Or from unpkg.com: ```html ``` Then use the `TWEEN` variable in any script: ```html ``` > [!Note] > unpkg.com supports a semver version in the URL, where the `^` in the > URL tells unpkg to give you the latest version 20.x.x. ## CommonJS (deprecated) Skip this section if you don't know what CommonJS is! > [!Note] > This method is deprecated and will be removed in a future major version! Any of the above methods work in older systems that still use CommonJS. Repeat any of the above methods but using `dist/tween.cjs` instead of `dist/tween.esm.js` or `dist/tween.umd.js`. # Documentation - [User guide](./docs/user_guide.md) - [Contributor guide](./docs/contributor_guide.md) - [Tutorial](https://web.archive.org/web/20220601192930/http://learningthreejs.com/blog/2011/08/17/tweenjs-for-smooth-animation/) using tween.js with three.js - Also: [libtween](https://github.com/jsm174/libtween), a port of tween.js to C by [jsm174](https://github.com/jsm174) # Tests You need to install `npm` first--this comes with node.js, so install that one first. Then, cd to `tween.js`'s (or wherever you cloned the repo) directory and run: ```bash npm install ``` To run the tests run: ```bash npm test ``` If you want to add any feature or change existing features, you _must_ run the tests to make sure you didn't break anything else. Any pull request (PR) needs to have updated passing tests for feature changes (or new passing tests for new features or fixes) in `src/tests.ts` to be accepted. See [contributing](CONTRIBUTING.md) for more information. # People Maintainers: [Joe Pea (@trusktr)](https://github.com/trusktr). [All contributors](http://github.com/tweenjs/tween.js/contributors). # Projects using tween.js [Lume](https://lume.io) [![A-Frame VR](https://tweenjs.github.io/tween.js/assets/projects/10_aframe.png)](https://aframe.io) [![MOMA Inventing Abstraction 1910-1925](https://tweenjs.github.io/tween.js/assets/projects/09_moma.png)](http://www.moma.org/interactives/exhibitions/2012/inventingabstraction/) [![Web Lab](https://tweenjs.github.io/tween.js/assets/projects/08_web_lab.png)](http://www.chromeweblab.com/) [![MACCHINA I](https://tweenjs.github.io/tween.js/assets/projects/07_macchina.png)](http://5013.es/toys/macchina) [![Minesweeper 3D](https://tweenjs.github.io/tween.js/assets/projects/06_minesweeper3d.png)](http://egraether.com/mine3d/) [![ROME](https://tweenjs.github.io/tween.js/assets/projects/05_rome.png)](http://ro.me) [![WebGL Globe](https://tweenjs.github.io/tween.js/assets/projects/04_webgl_globe.png)](http://data-arts.appspot.com/globe) [![Androidify](https://tweenjs.github.io/tween.js/assets/projects/03_androidify.png)](http://www.androidify.com/) [![The Wilderness Downtown](https://tweenjs.github.io/tween.js/assets/projects/01_wilderness.png)](http://thewildernessdowntown.com/) [![Linechart](https://tweenjs.github.io/tween.js/assets/projects/00_linechart.png)](http://dejavis.org/linechart) [npm-image]: https://img.shields.io/npm/v/@tweenjs/tween.js.svg [npm-url]: https://npmjs.org/package/@tweenjs/tween.js [downloads-image]: https://img.shields.io/npm/dm/@tweenjs/tween.js.svg [downloads-url]: https://npmjs.org/package/@tweenjs/tween.js [ci-image]: https://github.com/tweenjs/tween.js/workflows/build%20and%20tests/badge.svg?branch=master [ci-url]: https://github.com/tweenjs/tween.js/actions [cdnjs-image]: https://img.shields.io/cdnjs/v/tween.js.svg [cdnjs-url]: https://cdnjs.com/libraries/tween.js ================================================ FILE: README_zh-CN.md ================================================ # tween.js 用于简单动画的 JavaScript (TypeScript) 补间引擎,结合优化的 Robert Penner 方程式。 [![NPM Version][npm-image]][npm-url] [![CDNJS][cdnjs-image]][cdnjs-url] [![NPM Downloads][downloads-image]][downloads-url] [![Build and Tests][ci-image]][ci-url] 更多语言: [English](./README.md), [简体中文](./README_zh-CN.md) --- ```html
``` [在 CodePen 上试试这个例子](https://codepen.io/trusktr/pen/KKGaBVz?editors=1000) ## 安装 ## 从 CDN 安装 从上例中的内容分发网络 (CDN) 安装。 cdnjs: ```html ``` 或者 unpkg.com: ```html ``` 请注意,unpkg.com 支持 URL 中的 semver 版本,其中 URL 中的 `^` 告诉 unpkg 为你提供最新版本 20.x.x。 ## 使用 script 标签构建并包含在你的项目中 目前需要 npm 来构建项目。 ```bash git clone https://github.com/tweenjs/tween.js cd tween.js npm install npm run build ``` 这将在 `dist` 目录中创建一些构建。 目前有两种不同的库版本: - UMD : `tween.umd.js` - ES6 Module : `tween.esm.js` 你现在可以将 tween.umd.js 复制到你的项目中,然后将其包含在一个 script 标签,它将 TWEEN 添加到全局范围, ```html ``` 或将 TWEEN 作为 JavaScript 模块导入, ```html ``` 其中 `path/to` 替换为你放置文件的位置。 ## 使用 `npm install` 和 `import` 从 `node_modules` 中添加 你可以将 tween.js 添加为 npm 依赖项: ```bash npm install @tweenjs/tween.js ``` ### 使用构建工具 如果你使用 [Node.js](https://nodejs.org/)、[Parcel](https://parceljs.org/)、[Webpack](https://webpack.js.org/), [Rollup](https://rollupjs.org/)、[Vite](https://vitejs.dev/) 或者其他的构建工具,那么你现在可以使用以下方式来导入 tween.js: ```javascript import * as TWEEN from '@tweenjs/tween.js' ``` ### 没有构建工具 如果你将 `node_modules` 作为网站的一部分提供服务,则可以使用 `importmap` script 标签从 `node_modules` 导入。 首先,假设 `node_modules` 位于你网站的根目录,你可以编写一个导入映射: ```html ``` 现在,在任何 module script 中,你都可以通过包名导入它: ```javascript import * as TWEEN from '@tweenjs/tween.js' ``` # 特性 - 做一件事并且只做一件事:补间属性 - 不处理 CSS 单位(例如附加 `px`) - 不插值颜色 - 缓动函数可在 Tween 之外重复使用 - 也可以使用自定义缓动函数 # 文档
hello world hello world
(source)
Bars Bars
(source)
Black and red Black and red
(source)
Graphs Graphs
(source)
Simplest possible example Simplest possible example
(source)
Video and time Video and time
(source)
Array interpolation Array interpolation
(source)
Dynamic to, object Dynamic to, object
(source)
Dynamic to, interpolation array Dynamic to, interpolation array
(source)
Dynamic to, large interpolation array Dynamic to, large interpolation array
(source)
Repeat Repeat
(source)
Relative values Relative values
(source)
Yoyo Yoyo
(source)
Stop all chained tweens Stop all chained tweens
(source)
Custom functions Custom functions
(source)
Relative start time Relative start time
(source)
Pause tween Pause tween
(source)
Complex properties Complex properties
(source)
Animate an array of values Animate an array of values
(source)
# 测试 你需要先安装 `npm`——它随 node.js 一起提供,因此请先安装它。 然后,cd 到 `tween.js` 的(或你克隆 repo 的任何地方)目录并运行: ```bash npm install ``` 运行测试: ```bash npm test ``` 如果你想添加任何功能或更改现有功能,你 _必须_ 运行测试以确保你没有破坏任何其他功能。任何拉取请求 (PR) 都需要在 `src/tests.ts` 中更新通过功能更改测试(或通过新功能或修复的新测试)才能接受 PR。 有关更多信息,请参阅 [贡献](CONTRIBUTING.md)。 # 使用 tween.js 的项目 [Lume](https://lume.io) [![A-Frame VR](https://tweenjs.github.io/tween.js/assets/projects/10_aframe.png)](https://aframe.io) [![MOMA Inventing Abstraction 1910-1925](https://tweenjs.github.io/tween.js/assets/projects/09_moma.png)](http://www.moma.org/interactives/exhibitions/2012/inventingabstraction/) [![Web Lab](https://tweenjs.github.io/tween.js/assets/projects/08_web_lab.png)](http://www.chromeweblab.com/) [![MACCHINA I](https://tweenjs.github.io/tween.js/assets/projects/07_macchina.png)](http://5013.es/toys/macchina) [![Minesweeper 3D](https://tweenjs.github.io/tween.js/assets/projects/06_minesweeper3d.png)](http://egraether.com/mine3d/) [![ROME](https://tweenjs.github.io/tween.js/assets/projects/05_rome.png)](http://ro.me) [![WebGL Globe](https://tweenjs.github.io/tween.js/assets/projects/04_webgl_globe.png)](http://data-arts.appspot.com/globe) [![Androidify](https://tweenjs.github.io/tween.js/assets/projects/03_androidify.png)](http://www.androidify.com/) [![The Wilderness Downtown](https://tweenjs.github.io/tween.js/assets/projects/01_wilderness.png)](http://thewildernessdowntown.com/) [![Linechart](https://tweenjs.github.io/tween.js/assets/projects/00_linechart.png)](http://dejavis.org/linechart) [npm-image]: https://img.shields.io/npm/v/@tweenjs/tween.js.svg [npm-url]: https://npmjs.org/package/@tweenjs/tween.js [downloads-image]: https://img.shields.io/npm/dm/@tweenjs/tween.js.svg [downloads-url]: https://npmjs.org/package/@tweenjs/tween.js [ci-image]: https://github.com/tweenjs/tween.js/workflows/build%20and%20tests/badge.svg?branch=master [ci-url]: https://github.com/tweenjs/tween.js/actions [cdnjs-image]: https://img.shields.io/cdnjs/v/tween.js.svg [cdnjs-url]: https://cdnjs.com/libraries/tween.js ================================================ FILE: assets/style.css ================================================ body { background: #fff; font-family: Helvetica, Arial, sans; } a { color: #333; } h2 { font-weight: normal; } #info { position: absolute; top: 0; left: 0; padding: 1.5em 2em; } #info h1 { font-size: 3em; color: #333; margin-top: 0; letter-spacing: -0.05em; } #info h2 { font-size: 2.5em; text-transform: uppercase; color: #666; margin-top: 0; } #info p { font-size: 2em; line-height: 1em; color: #aaa; max-width: 10em; } ================================================ FILE: dist/tween.amd.js ================================================ define(['exports'], (function (exports) { 'use strict'; /** * The Ease class provides a collection of easing functions for use with tween.js. */ var Easing = Object.freeze({ Linear: Object.freeze({ None: function (amount) { return amount; }, In: function (amount) { return amount; }, Out: function (amount) { return amount; }, InOut: function (amount) { return amount; }, }), Quadratic: Object.freeze({ In: function (amount) { return amount * amount; }, Out: function (amount) { return amount * (2 - amount); }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount; } return -0.5 * (--amount * (amount - 2) - 1); }, }), Cubic: Object.freeze({ In: function (amount) { return amount * amount * amount; }, Out: function (amount) { return --amount * amount * amount + 1; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount; } return 0.5 * ((amount -= 2) * amount * amount + 2); }, }), Quartic: Object.freeze({ In: function (amount) { return amount * amount * amount * amount; }, Out: function (amount) { return 1 - --amount * amount * amount * amount; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount; } return -0.5 * ((amount -= 2) * amount * amount * amount - 2); }, }), Quintic: Object.freeze({ In: function (amount) { return amount * amount * amount * amount * amount; }, Out: function (amount) { return --amount * amount * amount * amount * amount + 1; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount * amount; } return 0.5 * ((amount -= 2) * amount * amount * amount * amount + 2); }, }), Sinusoidal: Object.freeze({ In: function (amount) { return 1 - Math.sin(((1.0 - amount) * Math.PI) / 2); }, Out: function (amount) { return Math.sin((amount * Math.PI) / 2); }, InOut: function (amount) { return 0.5 * (1 - Math.sin(Math.PI * (0.5 - amount))); }, }), Exponential: Object.freeze({ In: function (amount) { return amount === 0 ? 0 : Math.pow(1024, amount - 1); }, Out: function (amount) { return amount === 1 ? 1 : 1 - Math.pow(2, -10 * amount); }, InOut: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } if ((amount *= 2) < 1) { return 0.5 * Math.pow(1024, amount - 1); } return 0.5 * (-Math.pow(2, -10 * (amount - 1)) + 2); }, }), Circular: Object.freeze({ In: function (amount) { return 1 - Math.sqrt(1 - amount * amount); }, Out: function (amount) { return Math.sqrt(1 - --amount * amount); }, InOut: function (amount) { if ((amount *= 2) < 1) { return -0.5 * (Math.sqrt(1 - amount * amount) - 1); } return 0.5 * (Math.sqrt(1 - (amount -= 2) * amount) + 1); }, }), Elastic: Object.freeze({ In: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } return -Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI); }, Out: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } return Math.pow(2, -10 * amount) * Math.sin((amount - 0.1) * 5 * Math.PI) + 1; }, InOut: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } amount *= 2; if (amount < 1) { return -0.5 * Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI); } return 0.5 * Math.pow(2, -10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI) + 1; }, }), Back: Object.freeze({ In: function (amount) { var s = 1.70158; return amount === 1 ? 1 : amount * amount * ((s + 1) * amount - s); }, Out: function (amount) { var s = 1.70158; return amount === 0 ? 0 : --amount * amount * ((s + 1) * amount + s) + 1; }, InOut: function (amount) { var s = 1.70158 * 1.525; if ((amount *= 2) < 1) { return 0.5 * (amount * amount * ((s + 1) * amount - s)); } return 0.5 * ((amount -= 2) * amount * ((s + 1) * amount + s) + 2); }, }), Bounce: Object.freeze({ In: function (amount) { return 1 - Easing.Bounce.Out(1 - amount); }, Out: function (amount) { if (amount < 1 / 2.75) { return 7.5625 * amount * amount; } else if (amount < 2 / 2.75) { return 7.5625 * (amount -= 1.5 / 2.75) * amount + 0.75; } else if (amount < 2.5 / 2.75) { return 7.5625 * (amount -= 2.25 / 2.75) * amount + 0.9375; } else { return 7.5625 * (amount -= 2.625 / 2.75) * amount + 0.984375; } }, InOut: function (amount) { if (amount < 0.5) { return Easing.Bounce.In(amount * 2) * 0.5; } return Easing.Bounce.Out(amount * 2 - 1) * 0.5 + 0.5; }, }), generatePow: function (power) { if (power === void 0) { power = 4; } power = power < Number.EPSILON ? Number.EPSILON : power; power = power > 10000 ? 10000 : power; return { In: function (amount) { return Math.pow(amount, power); }, Out: function (amount) { return 1 - Math.pow((1 - amount), power); }, InOut: function (amount) { if (amount < 0.5) { return Math.pow((amount * 2), power) / 2; } return (1 - Math.pow((2 - amount * 2), power)) / 2 + 0.5; }, }; }, }); var _nowFunc = function () { return performance.now(); }; var now = function () { return _nowFunc(); }; function setNow(nowFunction) { _nowFunc = nowFunction; } /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tween */ var Group = /** @class */ (function () { function Group() { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } this._tweens = {}; this._tweensAddedDuringUpdate = {}; this.add.apply(this, tweens); } Group.prototype.getAll = function () { var _this = this; return Object.keys(this._tweens).map(function (tweenId) { return _this._tweens[tweenId]; }); }; Group.prototype.removeAll = function () { this._tweens = {}; }; Group.prototype.add = function () { var _a; var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } for (var _b = 0, tweens_1 = tweens; _b < tweens_1.length; _b++) { var tween = tweens_1[_b]; // Remove from any other group first, a tween can only be in one group at a time. // @ts-expect-error library internal access (_a = tween._group) === null || _a === void 0 ? void 0 : _a.remove(tween); // @ts-expect-error library internal access tween._group = this; this._tweens[tween.getId()] = tween; this._tweensAddedDuringUpdate[tween.getId()] = tween; } }; Group.prototype.remove = function () { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } for (var _a = 0, tweens_2 = tweens; _a < tweens_2.length; _a++) { var tween = tweens_2[_a]; // @ts-expect-error library internal access tween._group = undefined; delete this._tweens[tween.getId()]; delete this._tweensAddedDuringUpdate[tween.getId()]; } }; /** Return true if all tweens in the group are not paused or playing. */ Group.prototype.allStopped = function () { return this.getAll().every(function (tween) { return !tween.isPlaying(); }); }; Group.prototype.update = function (time, preserve) { if (time === void 0) { time = now(); } if (preserve === void 0) { preserve = true; } var tweenIds = Object.keys(this._tweens); if (tweenIds.length === 0) return; // Tweens are updated in "batches". If you add a new tween during an // update, then the new tween will be updated in the next batch. // If you remove a tween during an update, it may or may not be updated. // However, if the removed tween was added during the current batch, // then it will not be updated. while (tweenIds.length > 0) { this._tweensAddedDuringUpdate = {}; for (var i = 0; i < tweenIds.length; i++) { var tween = this._tweens[tweenIds[i]]; var autoStart = !preserve; if (tween && tween.update(time, autoStart) === false && !preserve) this.remove(tween); } tweenIds = Object.keys(this._tweensAddedDuringUpdate); } }; Group.prototype.onComplete = function (callback) { var group = this.getAll(); group.forEach(function (tween) { var prevCallback = tween.getCompleteCallback(); tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); if (completedGroup.length === group.length - 1) callback(group); }); }); }; return Group; }()); /** * */ var Interpolation = { Linear: function (v, k) { var m = v.length - 1; var f = m * k; var i = Math.floor(f); var fn = Interpolation.Utils.Linear; if (k < 0) { return fn(v[0], v[1], f); } if (k > 1) { return fn(v[m], v[m - 1], m - f); } return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); }, Bezier: function (v, k) { var b = 0; var n = v.length - 1; var pw = Math.pow; var bn = Interpolation.Utils.Bernstein; for (var i = 0; i <= n; i++) { b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); } return b; }, CatmullRom: function (v, k) { var m = v.length - 1; var f = m * k; var i = Math.floor(f); var fn = Interpolation.Utils.CatmullRom; if (v[0] === v[m]) { if (k < 0) { i = Math.floor((f = m * (1 + k))); } return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); } else { if (k < 0) { return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); } if (k > 1) { return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); } return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); } }, Utils: { Linear: function (p0, p1, t) { return (p1 - p0) * t + p0; }, Bernstein: function (n, i) { var fc = Interpolation.Utils.Factorial; return fc(n) / fc(i) / fc(n - i); }, Factorial: (function () { var a = [1]; return function (n) { var s = 1; if (a[n]) { return a[n]; } for (var i = n; i > 1; i--) { s *= i; } a[n] = s; return s; }; })(), CatmullRom: function (p0, p1, p2, p3, t) { var v0 = (p2 - p0) * 0.5; var v1 = (p3 - p1) * 0.5; var t2 = t * t; var t3 = t * t2; return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; }, }, }; /** * Utils */ var Sequence = /** @class */ (function () { function Sequence() { } Sequence.nextId = function () { return Sequence._nextId++; }; Sequence._nextId = 0; return Sequence; }()); var mainGroup = new Group(); /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ var Tween = /** @class */ (function () { function Tween(object, group) { this._isPaused = false; this._pauseStart = 0; this._valuesStart = {}; this._valuesEnd = {}; this._valuesStartRepeat = {}; this._duration = 1000; this._isDynamic = false; this._initialRepeat = 0; this._repeat = 0; this._yoyo = false; this._isPlaying = false; this._reversed = false; this._delayTime = 0; this._startTime = 0; this._easingFunction = Easing.Linear.None; this._interpolationFunction = Interpolation.Linear; // eslint-disable-next-line this._chainedTweens = []; this._onStartCallbackFired = false; this._onEveryStartCallbackFired = false; this._id = Sequence.nextId(); this._isChainStopped = false; this._propertiesAreSetUp = false; this._goToEnd = false; this._object = object; if (typeof group === 'object') { this._group = group; group.add(this); } // Use "true" to restore old behavior (will be removed in future release). else if (group === true) { this._group = mainGroup; mainGroup.add(this); } } Tween.prototype.getId = function () { return this._id; }; Tween.prototype.getCompleteCallback = function () { return this._onCompleteCallback; }; Tween.prototype.isPlaying = function () { return this._isPlaying; }; Tween.prototype.isPaused = function () { return this._isPaused; }; Tween.prototype.getDuration = function () { return this._duration; }; Tween.prototype.to = function (target, duration) { if (duration === void 0) { duration = 1000; } if (this._isPlaying) throw new Error('Can not call Tween.to() while Tween is already started or paused. Stop the Tween first.'); this._valuesEnd = target; this._propertiesAreSetUp = false; this._duration = duration < 0 ? 0 : duration; return this; }; Tween.prototype.duration = function (duration) { if (duration === void 0) { duration = 1000; } this._duration = duration < 0 ? 0 : duration; return this; }; Tween.prototype.dynamic = function (dynamic) { if (dynamic === void 0) { dynamic = false; } this._isDynamic = dynamic; return this; }; Tween.prototype.start = function (time, overrideStartingValues) { if (time === void 0) { time = now(); } if (overrideStartingValues === void 0) { overrideStartingValues = false; } if (this._isPlaying) { return this; } this._repeat = this._initialRepeat; if (this._reversed) { // If we were reversed (f.e. using the yoyo feature) then we need to // flip the tween direction back to forward. this._reversed = false; for (var property in this._valuesStartRepeat) { this._swapEndStartRepeatValues(property); this._valuesStart[property] = this._valuesStartRepeat[property]; } } this._isPlaying = true; this._isPaused = false; this._onStartCallbackFired = false; this._onEveryStartCallbackFired = false; this._isChainStopped = false; this._startTime = time; this._startTime += this._delayTime; if (!this._propertiesAreSetUp || overrideStartingValues) { this._propertiesAreSetUp = true; // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { var tmp = {}; for (var prop in this._valuesEnd) tmp[prop] = this._valuesEnd[prop]; this._valuesEnd = tmp; } this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues); } return this; }; Tween.prototype.startFromCurrentValues = function (time) { return this.start(time, true); }; Tween.prototype._setupProperties = function (_object, _valuesStart, _valuesEnd, _valuesStartRepeat, overrideStartingValues) { for (var property in _valuesEnd) { var startValue = _object[property]; var startValueIsArray = Array.isArray(startValue); var propType = startValueIsArray ? 'array' : typeof startValue; var isInterpolationList = !startValueIsArray && Array.isArray(_valuesEnd[property]); // If `to()` specifies a property that doesn't exist in the source object, // we should not set that property in the object if (propType === 'undefined' || propType === 'function') { continue; } // Check if an Array was provided as property value if (isInterpolationList) { var endValues = _valuesEnd[property]; if (endValues.length === 0) { continue; } // Handle an array of relative values. // Creates a local copy of the Array with the start value at the front var temp = [startValue]; for (var i = 0, l = endValues.length; i < l; i += 1) { var value = this._handleRelativeValue(startValue, endValues[i]); if (isNaN(value)) { isInterpolationList = false; console.warn('Found invalid interpolation list. Skipping.'); break; } temp.push(value); } if (isInterpolationList) { // if (_valuesStart[property] === undefined) { // handle end values only the first time. NOT NEEDED? setupProperties is now guarded by _propertiesAreSetUp. _valuesEnd[property] = temp; // } } } // handle the deepness of the values if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) { _valuesStart[property] = startValueIsArray ? [] : {}; var nestedObject = startValue; for (var prop in nestedObject) { _valuesStart[property][prop] = nestedObject[prop]; } // TODO? repeat nested values? And yoyo? And array values? _valuesStartRepeat[property] = startValueIsArray ? [] : {}; var endValues = _valuesEnd[property]; // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { var tmp = {}; for (var prop in endValues) tmp[prop] = endValues[prop]; _valuesEnd[property] = endValues = tmp; } this._setupProperties(nestedObject, _valuesStart[property], endValues, _valuesStartRepeat[property], overrideStartingValues); } else { // Save the starting value, but only once unless override is requested. if (typeof _valuesStart[property] === 'undefined' || overrideStartingValues) { _valuesStart[property] = startValue; } if (!startValueIsArray) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings } if (isInterpolationList) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStartRepeat[property] = _valuesEnd[property].slice().reverse(); } else { _valuesStartRepeat[property] = _valuesStart[property] || 0; } } } }; Tween.prototype.stop = function () { if (!this._isChainStopped) { this._isChainStopped = true; this.stopChainedTweens(); } if (!this._isPlaying) { return this; } this._isPlaying = false; this._isPaused = false; if (this._onStopCallback) { this._onStopCallback(this._object); } return this; }; Tween.prototype.end = function () { this._goToEnd = true; this.update(this._startTime + this._duration); return this; }; Tween.prototype.pause = function (time) { if (time === void 0) { time = now(); } if (this._isPaused || !this._isPlaying) { return this; } this._isPaused = true; this._pauseStart = time; return this; }; Tween.prototype.resume = function (time) { if (time === void 0) { time = now(); } if (!this._isPaused || !this._isPlaying) { return this; } this._isPaused = false; this._startTime += time - this._pauseStart; this._pauseStart = 0; return this; }; Tween.prototype.stopChainedTweens = function () { for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { this._chainedTweens[i].stop(); } return this; }; Tween.prototype.group = function (group) { if (!group) { console.warn('tween.group() without args has been removed, use group.add(tween) instead.'); return this; } group.add(this); return this; }; /** * Removes the tween from whichever group it is in. */ Tween.prototype.remove = function () { var _a; (_a = this._group) === null || _a === void 0 ? void 0 : _a.remove(this); return this; }; Tween.prototype.delay = function (amount) { if (amount === void 0) { amount = 0; } this._delayTime = amount; return this; }; Tween.prototype.repeat = function (times) { if (times === void 0) { times = 0; } this._initialRepeat = times; this._repeat = times; return this; }; Tween.prototype.repeatDelay = function (amount) { this._repeatDelayTime = amount; return this; }; Tween.prototype.yoyo = function (yoyo) { if (yoyo === void 0) { yoyo = false; } this._yoyo = yoyo; return this; }; Tween.prototype.easing = function (easingFunction) { if (easingFunction === void 0) { easingFunction = Easing.Linear.None; } this._easingFunction = easingFunction; return this; }; Tween.prototype.interpolation = function (interpolationFunction) { if (interpolationFunction === void 0) { interpolationFunction = Interpolation.Linear; } this._interpolationFunction = interpolationFunction; return this; }; // eslint-disable-next-line Tween.prototype.chain = function () { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } this._chainedTweens = tweens; return this; }; Tween.prototype.onStart = function (callback) { this._onStartCallback = callback; return this; }; Tween.prototype.onEveryStart = function (callback) { this._onEveryStartCallback = callback; return this; }; Tween.prototype.onUpdate = function (callback) { this._onUpdateCallback = callback; return this; }; Tween.prototype.onRepeat = function (callback) { this._onRepeatCallback = callback; return this; }; Tween.prototype.onComplete = function (callback) { this._onCompleteCallback = callback; return this; }; Tween.prototype.onStop = function (callback) { this._onStopCallback = callback; return this; }; /** * @returns true if the tween is still playing after the update, false * otherwise (calling update on a paused tween still returns true because * it is still playing, just paused). * * @param autoStart - When true, calling update will implicitly call start() * as well. Note, if you stop() or end() the tween, but are still calling * update(), it will start again! */ Tween.prototype.update = function (time, autoStart) { var _this = this; var _a; if (time === void 0) { time = now(); } if (autoStart === void 0) { autoStart = Tween.autoStartOnUpdate; } if (this._isPaused) return true; var property; if (!this._goToEnd && !this._isPlaying) { if (autoStart) this.start(time, true); else return false; } this._goToEnd = false; if (time < this._startTime) { return true; } if (this._onStartCallbackFired === false) { if (this._onStartCallback) { this._onStartCallback(this._object); } this._onStartCallbackFired = true; } if (this._onEveryStartCallbackFired === false) { if (this._onEveryStartCallback) { this._onEveryStartCallback(this._object); } this._onEveryStartCallbackFired = true; } var elapsedTime = time - this._startTime; var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime); var totalTime = this._duration + this._repeat * durationAndDelay; var calculateElapsedPortion = function () { if (_this._duration === 0) return 1; if (elapsedTime > totalTime) { return 1; } var timesRepeated = Math.trunc(elapsedTime / durationAndDelay); var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay; // TODO use %? // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1); if (portion === 0 && elapsedTime === _this._duration) { return 1; } return portion; }; var elapsed = calculateElapsedPortion(); var value = this._easingFunction(elapsed); // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed); } if (this._duration === 0 || elapsedTime >= this._duration) { if (this._repeat > 0) { var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); if (isFinite(this._repeat)) { this._repeat -= completeCount; } // Reassign starting values, restart by making startTime = now for (property in this._valuesStartRepeat) { if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { this._valuesStartRepeat[property] = // eslint-disable-next-line // @ts-ignore FIXME? this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); } if (this._yoyo) { this._swapEndStartRepeatValues(property); } this._valuesStart[property] = this._valuesStartRepeat[property]; } if (this._yoyo) { this._reversed = !this._reversed; } this._startTime += durationAndDelay * completeCount; if (this._onRepeatCallback) { this._onRepeatCallback(this._object); } this._onEveryStartCallbackFired = false; return true; } else { if (this._onCompleteCallback) { this._onCompleteCallback(this._object); } for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { // Make the chained tweens start exactly at the time they should, // even if the `update()` method was called way past the duration of the tween this._chainedTweens[i].start(this._startTime + this._duration, false); } this._isPlaying = false; return false; } } return true; }; Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { for (var property in _valuesEnd) { // Don't update properties that do not exist in the source object if (_valuesStart[property] === undefined) { continue; } var start = _valuesStart[property] || 0; var end = _valuesEnd[property]; var startIsArray = Array.isArray(_object[property]); var endIsArray = Array.isArray(end); var isInterpolationList = !startIsArray && endIsArray; if (isInterpolationList) { _object[property] = this._interpolationFunction(end, value); } else if (typeof end === 'object' && end) { // eslint-disable-next-line // @ts-ignore FIXME? this._updateProperties(_object[property], start, end, value); } else { // Parses relative end values with start as base (e.g.: +10, -3) end = this._handleRelativeValue(start, end); // Protect against non numeric properties. if (typeof end === 'number') { // eslint-disable-next-line // @ts-ignore FIXME? _object[property] = start + (end - start) * value; } } } }; Tween.prototype._handleRelativeValue = function (start, end) { if (typeof end !== 'string') { return end; } if (end.charAt(0) === '+' || end.charAt(0) === '-') { return start + parseFloat(end); } return parseFloat(end); }; Tween.prototype._swapEndStartRepeatValues = function (property) { var tmp = this._valuesStartRepeat[property]; var endValue = this._valuesEnd[property]; if (typeof endValue === 'string') { this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(endValue); } else { this._valuesStartRepeat[property] = this._valuesEnd[property]; } this._valuesEnd[property] = tmp; }; Tween.autoStartOnUpdate = false; return Tween; }()); var VERSION = '25.0.0'; /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ var nextId = Sequence.nextId; /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tweens. */ var TWEEN = mainGroup; // This is the best way to export things in a way that's compatible with both ES // Modules and CommonJS, without build hacks, and so as not to break the // existing API. // https://github.com/rollup/rollup/issues/1961#issuecomment-423037881 /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var getAll = TWEEN.getAll.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var removeAll = TWEEN.removeAll.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var add = TWEEN.add.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var remove = TWEEN.remove.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var update = TWEEN.update.bind(TWEEN); var exports$1 = { Easing: Easing, Group: Group, Interpolation: Interpolation, now: now, setNow: setNow, Sequence: Sequence, nextId: nextId, Tween: Tween, VERSION: VERSION, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ getAll: getAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ removeAll: removeAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ add: add, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ remove: remove, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ update: update, }; exports.Easing = Easing; exports.Group = Group; exports.Interpolation = Interpolation; exports.Sequence = Sequence; exports.Tween = Tween; exports.VERSION = VERSION; exports.add = add; exports.default = exports$1; exports.getAll = getAll; exports.nextId = nextId; exports.now = now; exports.remove = remove; exports.removeAll = removeAll; exports.setNow = setNow; exports.update = update; Object.defineProperty(exports, '__esModule', { value: true }); })); ================================================ FILE: dist/tween.cjs ================================================ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /** * The Ease class provides a collection of easing functions for use with tween.js. */ var Easing = Object.freeze({ Linear: Object.freeze({ None: function (amount) { return amount; }, In: function (amount) { return amount; }, Out: function (amount) { return amount; }, InOut: function (amount) { return amount; }, }), Quadratic: Object.freeze({ In: function (amount) { return amount * amount; }, Out: function (amount) { return amount * (2 - amount); }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount; } return -0.5 * (--amount * (amount - 2) - 1); }, }), Cubic: Object.freeze({ In: function (amount) { return amount * amount * amount; }, Out: function (amount) { return --amount * amount * amount + 1; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount; } return 0.5 * ((amount -= 2) * amount * amount + 2); }, }), Quartic: Object.freeze({ In: function (amount) { return amount * amount * amount * amount; }, Out: function (amount) { return 1 - --amount * amount * amount * amount; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount; } return -0.5 * ((amount -= 2) * amount * amount * amount - 2); }, }), Quintic: Object.freeze({ In: function (amount) { return amount * amount * amount * amount * amount; }, Out: function (amount) { return --amount * amount * amount * amount * amount + 1; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount * amount; } return 0.5 * ((amount -= 2) * amount * amount * amount * amount + 2); }, }), Sinusoidal: Object.freeze({ In: function (amount) { return 1 - Math.sin(((1.0 - amount) * Math.PI) / 2); }, Out: function (amount) { return Math.sin((amount * Math.PI) / 2); }, InOut: function (amount) { return 0.5 * (1 - Math.sin(Math.PI * (0.5 - amount))); }, }), Exponential: Object.freeze({ In: function (amount) { return amount === 0 ? 0 : Math.pow(1024, amount - 1); }, Out: function (amount) { return amount === 1 ? 1 : 1 - Math.pow(2, -10 * amount); }, InOut: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } if ((amount *= 2) < 1) { return 0.5 * Math.pow(1024, amount - 1); } return 0.5 * (-Math.pow(2, -10 * (amount - 1)) + 2); }, }), Circular: Object.freeze({ In: function (amount) { return 1 - Math.sqrt(1 - amount * amount); }, Out: function (amount) { return Math.sqrt(1 - --amount * amount); }, InOut: function (amount) { if ((amount *= 2) < 1) { return -0.5 * (Math.sqrt(1 - amount * amount) - 1); } return 0.5 * (Math.sqrt(1 - (amount -= 2) * amount) + 1); }, }), Elastic: Object.freeze({ In: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } return -Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI); }, Out: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } return Math.pow(2, -10 * amount) * Math.sin((amount - 0.1) * 5 * Math.PI) + 1; }, InOut: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } amount *= 2; if (amount < 1) { return -0.5 * Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI); } return 0.5 * Math.pow(2, -10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI) + 1; }, }), Back: Object.freeze({ In: function (amount) { var s = 1.70158; return amount === 1 ? 1 : amount * amount * ((s + 1) * amount - s); }, Out: function (amount) { var s = 1.70158; return amount === 0 ? 0 : --amount * amount * ((s + 1) * amount + s) + 1; }, InOut: function (amount) { var s = 1.70158 * 1.525; if ((amount *= 2) < 1) { return 0.5 * (amount * amount * ((s + 1) * amount - s)); } return 0.5 * ((amount -= 2) * amount * ((s + 1) * amount + s) + 2); }, }), Bounce: Object.freeze({ In: function (amount) { return 1 - Easing.Bounce.Out(1 - amount); }, Out: function (amount) { if (amount < 1 / 2.75) { return 7.5625 * amount * amount; } else if (amount < 2 / 2.75) { return 7.5625 * (amount -= 1.5 / 2.75) * amount + 0.75; } else if (amount < 2.5 / 2.75) { return 7.5625 * (amount -= 2.25 / 2.75) * amount + 0.9375; } else { return 7.5625 * (amount -= 2.625 / 2.75) * amount + 0.984375; } }, InOut: function (amount) { if (amount < 0.5) { return Easing.Bounce.In(amount * 2) * 0.5; } return Easing.Bounce.Out(amount * 2 - 1) * 0.5 + 0.5; }, }), generatePow: function (power) { if (power === void 0) { power = 4; } power = power < Number.EPSILON ? Number.EPSILON : power; power = power > 10000 ? 10000 : power; return { In: function (amount) { return Math.pow(amount, power); }, Out: function (amount) { return 1 - Math.pow((1 - amount), power); }, InOut: function (amount) { if (amount < 0.5) { return Math.pow((amount * 2), power) / 2; } return (1 - Math.pow((2 - amount * 2), power)) / 2 + 0.5; }, }; }, }); var _nowFunc = function () { return performance.now(); }; var now = function () { return _nowFunc(); }; function setNow(nowFunction) { _nowFunc = nowFunction; } /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tween */ var Group = /** @class */ (function () { function Group() { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } this._tweens = {}; this._tweensAddedDuringUpdate = {}; this.add.apply(this, tweens); } Group.prototype.getAll = function () { var _this = this; return Object.keys(this._tweens).map(function (tweenId) { return _this._tweens[tweenId]; }); }; Group.prototype.removeAll = function () { this._tweens = {}; }; Group.prototype.add = function () { var _a; var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } for (var _b = 0, tweens_1 = tweens; _b < tweens_1.length; _b++) { var tween = tweens_1[_b]; // Remove from any other group first, a tween can only be in one group at a time. // @ts-expect-error library internal access (_a = tween._group) === null || _a === void 0 ? void 0 : _a.remove(tween); // @ts-expect-error library internal access tween._group = this; this._tweens[tween.getId()] = tween; this._tweensAddedDuringUpdate[tween.getId()] = tween; } }; Group.prototype.remove = function () { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } for (var _a = 0, tweens_2 = tweens; _a < tweens_2.length; _a++) { var tween = tweens_2[_a]; // @ts-expect-error library internal access tween._group = undefined; delete this._tweens[tween.getId()]; delete this._tweensAddedDuringUpdate[tween.getId()]; } }; /** Return true if all tweens in the group are not paused or playing. */ Group.prototype.allStopped = function () { return this.getAll().every(function (tween) { return !tween.isPlaying(); }); }; Group.prototype.update = function (time, preserve) { if (time === void 0) { time = now(); } if (preserve === void 0) { preserve = true; } var tweenIds = Object.keys(this._tweens); if (tweenIds.length === 0) return; // Tweens are updated in "batches". If you add a new tween during an // update, then the new tween will be updated in the next batch. // If you remove a tween during an update, it may or may not be updated. // However, if the removed tween was added during the current batch, // then it will not be updated. while (tweenIds.length > 0) { this._tweensAddedDuringUpdate = {}; for (var i = 0; i < tweenIds.length; i++) { var tween = this._tweens[tweenIds[i]]; var autoStart = !preserve; if (tween && tween.update(time, autoStart) === false && !preserve) this.remove(tween); } tweenIds = Object.keys(this._tweensAddedDuringUpdate); } }; Group.prototype.onComplete = function (callback) { var group = this.getAll(); group.forEach(function (tween) { var prevCallback = tween.getCompleteCallback(); tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); if (completedGroup.length === group.length - 1) callback(group); }); }); }; return Group; }()); /** * */ var Interpolation = { Linear: function (v, k) { var m = v.length - 1; var f = m * k; var i = Math.floor(f); var fn = Interpolation.Utils.Linear; if (k < 0) { return fn(v[0], v[1], f); } if (k > 1) { return fn(v[m], v[m - 1], m - f); } return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); }, Bezier: function (v, k) { var b = 0; var n = v.length - 1; var pw = Math.pow; var bn = Interpolation.Utils.Bernstein; for (var i = 0; i <= n; i++) { b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); } return b; }, CatmullRom: function (v, k) { var m = v.length - 1; var f = m * k; var i = Math.floor(f); var fn = Interpolation.Utils.CatmullRom; if (v[0] === v[m]) { if (k < 0) { i = Math.floor((f = m * (1 + k))); } return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); } else { if (k < 0) { return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); } if (k > 1) { return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); } return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); } }, Utils: { Linear: function (p0, p1, t) { return (p1 - p0) * t + p0; }, Bernstein: function (n, i) { var fc = Interpolation.Utils.Factorial; return fc(n) / fc(i) / fc(n - i); }, Factorial: (function () { var a = [1]; return function (n) { var s = 1; if (a[n]) { return a[n]; } for (var i = n; i > 1; i--) { s *= i; } a[n] = s; return s; }; })(), CatmullRom: function (p0, p1, p2, p3, t) { var v0 = (p2 - p0) * 0.5; var v1 = (p3 - p1) * 0.5; var t2 = t * t; var t3 = t * t2; return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; }, }, }; /** * Utils */ var Sequence = /** @class */ (function () { function Sequence() { } Sequence.nextId = function () { return Sequence._nextId++; }; Sequence._nextId = 0; return Sequence; }()); var mainGroup = new Group(); /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ var Tween = /** @class */ (function () { function Tween(object, group) { this._isPaused = false; this._pauseStart = 0; this._valuesStart = {}; this._valuesEnd = {}; this._valuesStartRepeat = {}; this._duration = 1000; this._isDynamic = false; this._initialRepeat = 0; this._repeat = 0; this._yoyo = false; this._isPlaying = false; this._reversed = false; this._delayTime = 0; this._startTime = 0; this._easingFunction = Easing.Linear.None; this._interpolationFunction = Interpolation.Linear; // eslint-disable-next-line this._chainedTweens = []; this._onStartCallbackFired = false; this._onEveryStartCallbackFired = false; this._id = Sequence.nextId(); this._isChainStopped = false; this._propertiesAreSetUp = false; this._goToEnd = false; this._object = object; if (typeof group === 'object') { this._group = group; group.add(this); } // Use "true" to restore old behavior (will be removed in future release). else if (group === true) { this._group = mainGroup; mainGroup.add(this); } } Tween.prototype.getId = function () { return this._id; }; Tween.prototype.getCompleteCallback = function () { return this._onCompleteCallback; }; Tween.prototype.isPlaying = function () { return this._isPlaying; }; Tween.prototype.isPaused = function () { return this._isPaused; }; Tween.prototype.getDuration = function () { return this._duration; }; Tween.prototype.to = function (target, duration) { if (duration === void 0) { duration = 1000; } if (this._isPlaying) throw new Error('Can not call Tween.to() while Tween is already started or paused. Stop the Tween first.'); this._valuesEnd = target; this._propertiesAreSetUp = false; this._duration = duration < 0 ? 0 : duration; return this; }; Tween.prototype.duration = function (duration) { if (duration === void 0) { duration = 1000; } this._duration = duration < 0 ? 0 : duration; return this; }; Tween.prototype.dynamic = function (dynamic) { if (dynamic === void 0) { dynamic = false; } this._isDynamic = dynamic; return this; }; Tween.prototype.start = function (time, overrideStartingValues) { if (time === void 0) { time = now(); } if (overrideStartingValues === void 0) { overrideStartingValues = false; } if (this._isPlaying) { return this; } this._repeat = this._initialRepeat; if (this._reversed) { // If we were reversed (f.e. using the yoyo feature) then we need to // flip the tween direction back to forward. this._reversed = false; for (var property in this._valuesStartRepeat) { this._swapEndStartRepeatValues(property); this._valuesStart[property] = this._valuesStartRepeat[property]; } } this._isPlaying = true; this._isPaused = false; this._onStartCallbackFired = false; this._onEveryStartCallbackFired = false; this._isChainStopped = false; this._startTime = time; this._startTime += this._delayTime; if (!this._propertiesAreSetUp || overrideStartingValues) { this._propertiesAreSetUp = true; // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { var tmp = {}; for (var prop in this._valuesEnd) tmp[prop] = this._valuesEnd[prop]; this._valuesEnd = tmp; } this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues); } return this; }; Tween.prototype.startFromCurrentValues = function (time) { return this.start(time, true); }; Tween.prototype._setupProperties = function (_object, _valuesStart, _valuesEnd, _valuesStartRepeat, overrideStartingValues) { for (var property in _valuesEnd) { var startValue = _object[property]; var startValueIsArray = Array.isArray(startValue); var propType = startValueIsArray ? 'array' : typeof startValue; var isInterpolationList = !startValueIsArray && Array.isArray(_valuesEnd[property]); // If `to()` specifies a property that doesn't exist in the source object, // we should not set that property in the object if (propType === 'undefined' || propType === 'function') { continue; } // Check if an Array was provided as property value if (isInterpolationList) { var endValues = _valuesEnd[property]; if (endValues.length === 0) { continue; } // Handle an array of relative values. // Creates a local copy of the Array with the start value at the front var temp = [startValue]; for (var i = 0, l = endValues.length; i < l; i += 1) { var value = this._handleRelativeValue(startValue, endValues[i]); if (isNaN(value)) { isInterpolationList = false; console.warn('Found invalid interpolation list. Skipping.'); break; } temp.push(value); } if (isInterpolationList) { // if (_valuesStart[property] === undefined) { // handle end values only the first time. NOT NEEDED? setupProperties is now guarded by _propertiesAreSetUp. _valuesEnd[property] = temp; // } } } // handle the deepness of the values if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) { _valuesStart[property] = startValueIsArray ? [] : {}; var nestedObject = startValue; for (var prop in nestedObject) { _valuesStart[property][prop] = nestedObject[prop]; } // TODO? repeat nested values? And yoyo? And array values? _valuesStartRepeat[property] = startValueIsArray ? [] : {}; var endValues = _valuesEnd[property]; // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { var tmp = {}; for (var prop in endValues) tmp[prop] = endValues[prop]; _valuesEnd[property] = endValues = tmp; } this._setupProperties(nestedObject, _valuesStart[property], endValues, _valuesStartRepeat[property], overrideStartingValues); } else { // Save the starting value, but only once unless override is requested. if (typeof _valuesStart[property] === 'undefined' || overrideStartingValues) { _valuesStart[property] = startValue; } if (!startValueIsArray) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings } if (isInterpolationList) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStartRepeat[property] = _valuesEnd[property].slice().reverse(); } else { _valuesStartRepeat[property] = _valuesStart[property] || 0; } } } }; Tween.prototype.stop = function () { if (!this._isChainStopped) { this._isChainStopped = true; this.stopChainedTweens(); } if (!this._isPlaying) { return this; } this._isPlaying = false; this._isPaused = false; if (this._onStopCallback) { this._onStopCallback(this._object); } return this; }; Tween.prototype.end = function () { this._goToEnd = true; this.update(this._startTime + this._duration); return this; }; Tween.prototype.pause = function (time) { if (time === void 0) { time = now(); } if (this._isPaused || !this._isPlaying) { return this; } this._isPaused = true; this._pauseStart = time; return this; }; Tween.prototype.resume = function (time) { if (time === void 0) { time = now(); } if (!this._isPaused || !this._isPlaying) { return this; } this._isPaused = false; this._startTime += time - this._pauseStart; this._pauseStart = 0; return this; }; Tween.prototype.stopChainedTweens = function () { for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { this._chainedTweens[i].stop(); } return this; }; Tween.prototype.group = function (group) { if (!group) { console.warn('tween.group() without args has been removed, use group.add(tween) instead.'); return this; } group.add(this); return this; }; /** * Removes the tween from whichever group it is in. */ Tween.prototype.remove = function () { var _a; (_a = this._group) === null || _a === void 0 ? void 0 : _a.remove(this); return this; }; Tween.prototype.delay = function (amount) { if (amount === void 0) { amount = 0; } this._delayTime = amount; return this; }; Tween.prototype.repeat = function (times) { if (times === void 0) { times = 0; } this._initialRepeat = times; this._repeat = times; return this; }; Tween.prototype.repeatDelay = function (amount) { this._repeatDelayTime = amount; return this; }; Tween.prototype.yoyo = function (yoyo) { if (yoyo === void 0) { yoyo = false; } this._yoyo = yoyo; return this; }; Tween.prototype.easing = function (easingFunction) { if (easingFunction === void 0) { easingFunction = Easing.Linear.None; } this._easingFunction = easingFunction; return this; }; Tween.prototype.interpolation = function (interpolationFunction) { if (interpolationFunction === void 0) { interpolationFunction = Interpolation.Linear; } this._interpolationFunction = interpolationFunction; return this; }; // eslint-disable-next-line Tween.prototype.chain = function () { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } this._chainedTweens = tweens; return this; }; Tween.prototype.onStart = function (callback) { this._onStartCallback = callback; return this; }; Tween.prototype.onEveryStart = function (callback) { this._onEveryStartCallback = callback; return this; }; Tween.prototype.onUpdate = function (callback) { this._onUpdateCallback = callback; return this; }; Tween.prototype.onRepeat = function (callback) { this._onRepeatCallback = callback; return this; }; Tween.prototype.onComplete = function (callback) { this._onCompleteCallback = callback; return this; }; Tween.prototype.onStop = function (callback) { this._onStopCallback = callback; return this; }; /** * @returns true if the tween is still playing after the update, false * otherwise (calling update on a paused tween still returns true because * it is still playing, just paused). * * @param autoStart - When true, calling update will implicitly call start() * as well. Note, if you stop() or end() the tween, but are still calling * update(), it will start again! */ Tween.prototype.update = function (time, autoStart) { var _this = this; var _a; if (time === void 0) { time = now(); } if (autoStart === void 0) { autoStart = Tween.autoStartOnUpdate; } if (this._isPaused) return true; var property; if (!this._goToEnd && !this._isPlaying) { if (autoStart) this.start(time, true); else return false; } this._goToEnd = false; if (time < this._startTime) { return true; } if (this._onStartCallbackFired === false) { if (this._onStartCallback) { this._onStartCallback(this._object); } this._onStartCallbackFired = true; } if (this._onEveryStartCallbackFired === false) { if (this._onEveryStartCallback) { this._onEveryStartCallback(this._object); } this._onEveryStartCallbackFired = true; } var elapsedTime = time - this._startTime; var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime); var totalTime = this._duration + this._repeat * durationAndDelay; var calculateElapsedPortion = function () { if (_this._duration === 0) return 1; if (elapsedTime > totalTime) { return 1; } var timesRepeated = Math.trunc(elapsedTime / durationAndDelay); var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay; // TODO use %? // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1); if (portion === 0 && elapsedTime === _this._duration) { return 1; } return portion; }; var elapsed = calculateElapsedPortion(); var value = this._easingFunction(elapsed); // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed); } if (this._duration === 0 || elapsedTime >= this._duration) { if (this._repeat > 0) { var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); if (isFinite(this._repeat)) { this._repeat -= completeCount; } // Reassign starting values, restart by making startTime = now for (property in this._valuesStartRepeat) { if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { this._valuesStartRepeat[property] = // eslint-disable-next-line // @ts-ignore FIXME? this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); } if (this._yoyo) { this._swapEndStartRepeatValues(property); } this._valuesStart[property] = this._valuesStartRepeat[property]; } if (this._yoyo) { this._reversed = !this._reversed; } this._startTime += durationAndDelay * completeCount; if (this._onRepeatCallback) { this._onRepeatCallback(this._object); } this._onEveryStartCallbackFired = false; return true; } else { if (this._onCompleteCallback) { this._onCompleteCallback(this._object); } for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { // Make the chained tweens start exactly at the time they should, // even if the `update()` method was called way past the duration of the tween this._chainedTweens[i].start(this._startTime + this._duration, false); } this._isPlaying = false; return false; } } return true; }; Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { for (var property in _valuesEnd) { // Don't update properties that do not exist in the source object if (_valuesStart[property] === undefined) { continue; } var start = _valuesStart[property] || 0; var end = _valuesEnd[property]; var startIsArray = Array.isArray(_object[property]); var endIsArray = Array.isArray(end); var isInterpolationList = !startIsArray && endIsArray; if (isInterpolationList) { _object[property] = this._interpolationFunction(end, value); } else if (typeof end === 'object' && end) { // eslint-disable-next-line // @ts-ignore FIXME? this._updateProperties(_object[property], start, end, value); } else { // Parses relative end values with start as base (e.g.: +10, -3) end = this._handleRelativeValue(start, end); // Protect against non numeric properties. if (typeof end === 'number') { // eslint-disable-next-line // @ts-ignore FIXME? _object[property] = start + (end - start) * value; } } } }; Tween.prototype._handleRelativeValue = function (start, end) { if (typeof end !== 'string') { return end; } if (end.charAt(0) === '+' || end.charAt(0) === '-') { return start + parseFloat(end); } return parseFloat(end); }; Tween.prototype._swapEndStartRepeatValues = function (property) { var tmp = this._valuesStartRepeat[property]; var endValue = this._valuesEnd[property]; if (typeof endValue === 'string') { this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(endValue); } else { this._valuesStartRepeat[property] = this._valuesEnd[property]; } this._valuesEnd[property] = tmp; }; Tween.autoStartOnUpdate = false; return Tween; }()); var VERSION = '25.0.0'; /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ var nextId = Sequence.nextId; /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tweens. */ var TWEEN = mainGroup; // This is the best way to export things in a way that's compatible with both ES // Modules and CommonJS, without build hacks, and so as not to break the // existing API. // https://github.com/rollup/rollup/issues/1961#issuecomment-423037881 /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var getAll = TWEEN.getAll.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var removeAll = TWEEN.removeAll.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var add = TWEEN.add.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var remove = TWEEN.remove.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var update = TWEEN.update.bind(TWEEN); var exports$1 = { Easing: Easing, Group: Group, Interpolation: Interpolation, now: now, setNow: setNow, Sequence: Sequence, nextId: nextId, Tween: Tween, VERSION: VERSION, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ getAll: getAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ removeAll: removeAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ add: add, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ remove: remove, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ update: update, }; exports.Easing = Easing; exports.Group = Group; exports.Interpolation = Interpolation; exports.Sequence = Sequence; exports.Tween = Tween; exports.VERSION = VERSION; exports.add = add; exports.default = exports$1; exports.getAll = getAll; exports.nextId = nextId; exports.now = now; exports.remove = remove; exports.removeAll = removeAll; exports.setNow = setNow; exports.update = update; ================================================ FILE: dist/tween.d.ts ================================================ type EasingFunction = (amount: number) => number; type EasingFunctionGroup = { In: EasingFunction; Out: EasingFunction; InOut: EasingFunction; }; /** * The Ease class provides a collection of easing functions for use with tween.js. */ declare const Easing: Readonly<{ Linear: Readonly; Quadratic: Readonly; Cubic: Readonly; Quartic: Readonly; Quintic: Readonly; Sinusoidal: Readonly; Exponential: Readonly; Circular: Readonly; Elastic: Readonly; Back: Readonly; Bounce: Readonly; generatePow(power?: number): EasingFunctionGroup; }>; /** * */ type InterpolationFunction = (v: number[], k: number) => number; /** * */ declare const Interpolation: { Linear: (v: number[], k: number) => number; Bezier: (v: number[], k: number) => number; CatmullRom: (v: number[], k: number) => number; Utils: { Linear: (p0: number, p1: number, t: number) => number; Bernstein: (n: number, i: number) => number; Factorial: (n: number) => number; CatmullRom: (p0: number, p1: number, p2: number, p3: number, t: number) => number; }; }; /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ declare class Tween { static autoStartOnUpdate: boolean; private _isPaused; private _pauseStart; private _valuesStart; private _valuesEnd; private _valuesStartRepeat; private _duration; private _isDynamic; private _initialRepeat; private _repeat; private _repeatDelayTime?; private _yoyo; private _isPlaying; private _reversed; private _delayTime; private _startTime; private _easingFunction; private _interpolationFunction; private _chainedTweens; private _onStartCallback?; private _onStartCallbackFired; private _onEveryStartCallback?; private _onEveryStartCallbackFired; private _onUpdateCallback?; private _onRepeatCallback?; private _onCompleteCallback?; private _onStopCallback?; private _id; private _isChainStopped; private _propertiesAreSetUp; private _object; private _group?; /** * @param object - The object whose properties this Tween will animate. * @param group - The object whose properties this Tween will animate. */ constructor(object: T, group?: Group); /** * @deprecated The group parameter is now deprecated, instead use `new * Tween(object)` then `group.add(tween)` to add a tween to a group. Use * `new Tween(object, true)` to restore the old behavior for now, but this * will be removed in the future. */ constructor(object: T, group: true); getId(): number; getCompleteCallback(): ((object: T) => void) | undefined; isPlaying(): boolean; isPaused(): boolean; getDuration(): number; to(target: UnknownProps, duration?: number): this; duration(duration?: number): this; dynamic(dynamic?: boolean): this; start(time?: number, overrideStartingValues?: boolean): this; startFromCurrentValues(time?: number): this; private _setupProperties; stop(): this; end(): this; pause(time?: number): this; resume(time?: number): this; stopChainedTweens(): this; /** * Removes the tween from the current group it is in, if any, then adds the * tween to the specified `group`. */ group(group: Group): this; /** * @deprecated The argless call signature has been removed. Use * `tween.group(group)` or `group.add(tween)`, instead. */ group(): this; /** * Removes the tween from whichever group it is in. */ remove(): this; delay(amount?: number): this; repeat(times?: number): this; repeatDelay(amount?: number): this; yoyo(yoyo?: boolean): this; easing(easingFunction?: EasingFunction): this; interpolation(interpolationFunction?: InterpolationFunction): this; chain(...tweens: Array>): this; onStart(callback?: (object: T) => void): this; onEveryStart(callback?: (object: T) => void): this; onUpdate(callback?: (object: T, elapsed: number) => void): this; onRepeat(callback?: (object: T) => void): this; onComplete(callback?: (object: T) => void): this; onStop(callback?: (object: T) => void): this; private _goToEnd; /** * @returns true if the tween is still playing after the update, false * otherwise (calling update on a paused tween still returns true because * it is still playing, just paused). * * @param autoStart - When true, calling update will implicitly call start() * as well. Note, if you stop() or end() the tween, but are still calling * update(), it will start again! */ update(time?: number, autoStart?: boolean): boolean; private _updateProperties; private _handleRelativeValue; private _swapEndStartRepeatValues; } type UnknownProps = Record; /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tween */ declare class Group { private _tweens; private _tweensAddedDuringUpdate; constructor(...tweens: Tween[]); getAll(): Array; removeAll(): void; add(...tweens: Tween[]): void; remove(...tweens: Tween[]): void; /** Return true if all tweens in the group are not paused or playing. */ allStopped(): boolean; update(time?: number): void; /** * @deprecated The `preserve` parameter is now defaulted to `true` and will * be removed in a future major release, at which point all tweens of a * group will always be preserved when calling update. To migrate, always * use `group.add(tween)` or `group.remove(tween)` to manually add or remove * tweens, and do not rely on tweens being automatically added or removed. */ update(time?: number, preserve?: boolean): void; onComplete(callback: (object: Tween[]) => void): void; } declare const now: () => number; declare function setNow(nowFunction: Function): void; /** * Utils */ declare class Sequence { private static _nextId; static nextId(): number; } declare const VERSION = "25.0.0"; declare const nextId: typeof Sequence.nextId; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ declare const getAll: () => Tween[]; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ declare const removeAll: () => void; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ declare const add: (...tweens: Tween[]) => void; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ declare const remove: (...tweens: Tween[]) => void; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ declare const update: { (time?: number | undefined): void; (time?: number | undefined, preserve?: boolean | undefined): void; }; declare const exports: { Easing: Readonly<{ Linear: Readonly; Quadratic: Readonly; Cubic: Readonly; Quartic: Readonly; Quintic: Readonly; Sinusoidal: Readonly; Exponential: Readonly; Circular: Readonly; Elastic: Readonly; Back: Readonly; Bounce: Readonly; generatePow(power?: number): EasingFunctionGroup; }>; Group: typeof Group; Interpolation: { Linear: (v: number[], k: number) => number; Bezier: (v: number[], k: number) => number; CatmullRom: (v: number[], k: number) => number; Utils: { Linear: (p0: number, p1: number, t: number) => number; Bernstein: (n: number, i: number) => number; Factorial: (n: number) => number; CatmullRom: (p0: number, p1: number, p2: number, p3: number, t: number) => number; }; }; now: () => number; setNow: typeof setNow; Sequence: typeof Sequence; nextId: typeof Sequence.nextId; Tween: typeof Tween; VERSION: string; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ getAll: () => Tween[]; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ removeAll: () => void; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ add: (...tweens: Tween[]) => void; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ remove: (...tweens: Tween[]) => void; /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ update: { (time?: number | undefined): void; (time?: number | undefined, preserve?: boolean | undefined): void; }; }; export { Easing, Group, Interpolation, Sequence, Tween, VERSION, add, exports as default, getAll, nextId, now, remove, removeAll, setNow, update }; ================================================ FILE: dist/tween.esm.js ================================================ /** * The Ease class provides a collection of easing functions for use with tween.js. */ var Easing = Object.freeze({ Linear: Object.freeze({ None: function (amount) { return amount; }, In: function (amount) { return amount; }, Out: function (amount) { return amount; }, InOut: function (amount) { return amount; }, }), Quadratic: Object.freeze({ In: function (amount) { return amount * amount; }, Out: function (amount) { return amount * (2 - amount); }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount; } return -0.5 * (--amount * (amount - 2) - 1); }, }), Cubic: Object.freeze({ In: function (amount) { return amount * amount * amount; }, Out: function (amount) { return --amount * amount * amount + 1; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount; } return 0.5 * ((amount -= 2) * amount * amount + 2); }, }), Quartic: Object.freeze({ In: function (amount) { return amount * amount * amount * amount; }, Out: function (amount) { return 1 - --amount * amount * amount * amount; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount; } return -0.5 * ((amount -= 2) * amount * amount * amount - 2); }, }), Quintic: Object.freeze({ In: function (amount) { return amount * amount * amount * amount * amount; }, Out: function (amount) { return --amount * amount * amount * amount * amount + 1; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount * amount; } return 0.5 * ((amount -= 2) * amount * amount * amount * amount + 2); }, }), Sinusoidal: Object.freeze({ In: function (amount) { return 1 - Math.sin(((1.0 - amount) * Math.PI) / 2); }, Out: function (amount) { return Math.sin((amount * Math.PI) / 2); }, InOut: function (amount) { return 0.5 * (1 - Math.sin(Math.PI * (0.5 - amount))); }, }), Exponential: Object.freeze({ In: function (amount) { return amount === 0 ? 0 : Math.pow(1024, amount - 1); }, Out: function (amount) { return amount === 1 ? 1 : 1 - Math.pow(2, -10 * amount); }, InOut: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } if ((amount *= 2) < 1) { return 0.5 * Math.pow(1024, amount - 1); } return 0.5 * (-Math.pow(2, -10 * (amount - 1)) + 2); }, }), Circular: Object.freeze({ In: function (amount) { return 1 - Math.sqrt(1 - amount * amount); }, Out: function (amount) { return Math.sqrt(1 - --amount * amount); }, InOut: function (amount) { if ((amount *= 2) < 1) { return -0.5 * (Math.sqrt(1 - amount * amount) - 1); } return 0.5 * (Math.sqrt(1 - (amount -= 2) * amount) + 1); }, }), Elastic: Object.freeze({ In: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } return -Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI); }, Out: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } return Math.pow(2, -10 * amount) * Math.sin((amount - 0.1) * 5 * Math.PI) + 1; }, InOut: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } amount *= 2; if (amount < 1) { return -0.5 * Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI); } return 0.5 * Math.pow(2, -10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI) + 1; }, }), Back: Object.freeze({ In: function (amount) { var s = 1.70158; return amount === 1 ? 1 : amount * amount * ((s + 1) * amount - s); }, Out: function (amount) { var s = 1.70158; return amount === 0 ? 0 : --amount * amount * ((s + 1) * amount + s) + 1; }, InOut: function (amount) { var s = 1.70158 * 1.525; if ((amount *= 2) < 1) { return 0.5 * (amount * amount * ((s + 1) * amount - s)); } return 0.5 * ((amount -= 2) * amount * ((s + 1) * amount + s) + 2); }, }), Bounce: Object.freeze({ In: function (amount) { return 1 - Easing.Bounce.Out(1 - amount); }, Out: function (amount) { if (amount < 1 / 2.75) { return 7.5625 * amount * amount; } else if (amount < 2 / 2.75) { return 7.5625 * (amount -= 1.5 / 2.75) * amount + 0.75; } else if (amount < 2.5 / 2.75) { return 7.5625 * (amount -= 2.25 / 2.75) * amount + 0.9375; } else { return 7.5625 * (amount -= 2.625 / 2.75) * amount + 0.984375; } }, InOut: function (amount) { if (amount < 0.5) { return Easing.Bounce.In(amount * 2) * 0.5; } return Easing.Bounce.Out(amount * 2 - 1) * 0.5 + 0.5; }, }), generatePow: function (power) { if (power === void 0) { power = 4; } power = power < Number.EPSILON ? Number.EPSILON : power; power = power > 10000 ? 10000 : power; return { In: function (amount) { return Math.pow(amount, power); }, Out: function (amount) { return 1 - Math.pow((1 - amount), power); }, InOut: function (amount) { if (amount < 0.5) { return Math.pow((amount * 2), power) / 2; } return (1 - Math.pow((2 - amount * 2), power)) / 2 + 0.5; }, }; }, }); var _nowFunc = function () { return performance.now(); }; var now = function () { return _nowFunc(); }; function setNow(nowFunction) { _nowFunc = nowFunction; } /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tween */ var Group = /** @class */ (function () { function Group() { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } this._tweens = {}; this._tweensAddedDuringUpdate = {}; this.add.apply(this, tweens); } Group.prototype.getAll = function () { var _this = this; return Object.keys(this._tweens).map(function (tweenId) { return _this._tweens[tweenId]; }); }; Group.prototype.removeAll = function () { this._tweens = {}; }; Group.prototype.add = function () { var _a; var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } for (var _b = 0, tweens_1 = tweens; _b < tweens_1.length; _b++) { var tween = tweens_1[_b]; // Remove from any other group first, a tween can only be in one group at a time. // @ts-expect-error library internal access (_a = tween._group) === null || _a === void 0 ? void 0 : _a.remove(tween); // @ts-expect-error library internal access tween._group = this; this._tweens[tween.getId()] = tween; this._tweensAddedDuringUpdate[tween.getId()] = tween; } }; Group.prototype.remove = function () { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } for (var _a = 0, tweens_2 = tweens; _a < tweens_2.length; _a++) { var tween = tweens_2[_a]; // @ts-expect-error library internal access tween._group = undefined; delete this._tweens[tween.getId()]; delete this._tweensAddedDuringUpdate[tween.getId()]; } }; /** Return true if all tweens in the group are not paused or playing. */ Group.prototype.allStopped = function () { return this.getAll().every(function (tween) { return !tween.isPlaying(); }); }; Group.prototype.update = function (time, preserve) { if (time === void 0) { time = now(); } if (preserve === void 0) { preserve = true; } var tweenIds = Object.keys(this._tweens); if (tweenIds.length === 0) return; // Tweens are updated in "batches". If you add a new tween during an // update, then the new tween will be updated in the next batch. // If you remove a tween during an update, it may or may not be updated. // However, if the removed tween was added during the current batch, // then it will not be updated. while (tweenIds.length > 0) { this._tweensAddedDuringUpdate = {}; for (var i = 0; i < tweenIds.length; i++) { var tween = this._tweens[tweenIds[i]]; var autoStart = !preserve; if (tween && tween.update(time, autoStart) === false && !preserve) this.remove(tween); } tweenIds = Object.keys(this._tweensAddedDuringUpdate); } }; Group.prototype.onComplete = function (callback) { var group = this.getAll(); group.forEach(function (tween) { var prevCallback = tween.getCompleteCallback(); tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); if (completedGroup.length === group.length - 1) callback(group); }); }); }; return Group; }()); /** * */ var Interpolation = { Linear: function (v, k) { var m = v.length - 1; var f = m * k; var i = Math.floor(f); var fn = Interpolation.Utils.Linear; if (k < 0) { return fn(v[0], v[1], f); } if (k > 1) { return fn(v[m], v[m - 1], m - f); } return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); }, Bezier: function (v, k) { var b = 0; var n = v.length - 1; var pw = Math.pow; var bn = Interpolation.Utils.Bernstein; for (var i = 0; i <= n; i++) { b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); } return b; }, CatmullRom: function (v, k) { var m = v.length - 1; var f = m * k; var i = Math.floor(f); var fn = Interpolation.Utils.CatmullRom; if (v[0] === v[m]) { if (k < 0) { i = Math.floor((f = m * (1 + k))); } return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); } else { if (k < 0) { return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); } if (k > 1) { return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); } return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); } }, Utils: { Linear: function (p0, p1, t) { return (p1 - p0) * t + p0; }, Bernstein: function (n, i) { var fc = Interpolation.Utils.Factorial; return fc(n) / fc(i) / fc(n - i); }, Factorial: (function () { var a = [1]; return function (n) { var s = 1; if (a[n]) { return a[n]; } for (var i = n; i > 1; i--) { s *= i; } a[n] = s; return s; }; })(), CatmullRom: function (p0, p1, p2, p3, t) { var v0 = (p2 - p0) * 0.5; var v1 = (p3 - p1) * 0.5; var t2 = t * t; var t3 = t * t2; return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; }, }, }; /** * Utils */ var Sequence = /** @class */ (function () { function Sequence() { } Sequence.nextId = function () { return Sequence._nextId++; }; Sequence._nextId = 0; return Sequence; }()); var mainGroup = new Group(); /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ var Tween = /** @class */ (function () { function Tween(object, group) { this._isPaused = false; this._pauseStart = 0; this._valuesStart = {}; this._valuesEnd = {}; this._valuesStartRepeat = {}; this._duration = 1000; this._isDynamic = false; this._initialRepeat = 0; this._repeat = 0; this._yoyo = false; this._isPlaying = false; this._reversed = false; this._delayTime = 0; this._startTime = 0; this._easingFunction = Easing.Linear.None; this._interpolationFunction = Interpolation.Linear; // eslint-disable-next-line this._chainedTweens = []; this._onStartCallbackFired = false; this._onEveryStartCallbackFired = false; this._id = Sequence.nextId(); this._isChainStopped = false; this._propertiesAreSetUp = false; this._goToEnd = false; this._object = object; if (typeof group === 'object') { this._group = group; group.add(this); } // Use "true" to restore old behavior (will be removed in future release). else if (group === true) { this._group = mainGroup; mainGroup.add(this); } } Tween.prototype.getId = function () { return this._id; }; Tween.prototype.getCompleteCallback = function () { return this._onCompleteCallback; }; Tween.prototype.isPlaying = function () { return this._isPlaying; }; Tween.prototype.isPaused = function () { return this._isPaused; }; Tween.prototype.getDuration = function () { return this._duration; }; Tween.prototype.to = function (target, duration) { if (duration === void 0) { duration = 1000; } if (this._isPlaying) throw new Error('Can not call Tween.to() while Tween is already started or paused. Stop the Tween first.'); this._valuesEnd = target; this._propertiesAreSetUp = false; this._duration = duration < 0 ? 0 : duration; return this; }; Tween.prototype.duration = function (duration) { if (duration === void 0) { duration = 1000; } this._duration = duration < 0 ? 0 : duration; return this; }; Tween.prototype.dynamic = function (dynamic) { if (dynamic === void 0) { dynamic = false; } this._isDynamic = dynamic; return this; }; Tween.prototype.start = function (time, overrideStartingValues) { if (time === void 0) { time = now(); } if (overrideStartingValues === void 0) { overrideStartingValues = false; } if (this._isPlaying) { return this; } this._repeat = this._initialRepeat; if (this._reversed) { // If we were reversed (f.e. using the yoyo feature) then we need to // flip the tween direction back to forward. this._reversed = false; for (var property in this._valuesStartRepeat) { this._swapEndStartRepeatValues(property); this._valuesStart[property] = this._valuesStartRepeat[property]; } } this._isPlaying = true; this._isPaused = false; this._onStartCallbackFired = false; this._onEveryStartCallbackFired = false; this._isChainStopped = false; this._startTime = time; this._startTime += this._delayTime; if (!this._propertiesAreSetUp || overrideStartingValues) { this._propertiesAreSetUp = true; // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { var tmp = {}; for (var prop in this._valuesEnd) tmp[prop] = this._valuesEnd[prop]; this._valuesEnd = tmp; } this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues); } return this; }; Tween.prototype.startFromCurrentValues = function (time) { return this.start(time, true); }; Tween.prototype._setupProperties = function (_object, _valuesStart, _valuesEnd, _valuesStartRepeat, overrideStartingValues) { for (var property in _valuesEnd) { var startValue = _object[property]; var startValueIsArray = Array.isArray(startValue); var propType = startValueIsArray ? 'array' : typeof startValue; var isInterpolationList = !startValueIsArray && Array.isArray(_valuesEnd[property]); // If `to()` specifies a property that doesn't exist in the source object, // we should not set that property in the object if (propType === 'undefined' || propType === 'function') { continue; } // Check if an Array was provided as property value if (isInterpolationList) { var endValues = _valuesEnd[property]; if (endValues.length === 0) { continue; } // Handle an array of relative values. // Creates a local copy of the Array with the start value at the front var temp = [startValue]; for (var i = 0, l = endValues.length; i < l; i += 1) { var value = this._handleRelativeValue(startValue, endValues[i]); if (isNaN(value)) { isInterpolationList = false; console.warn('Found invalid interpolation list. Skipping.'); break; } temp.push(value); } if (isInterpolationList) { // if (_valuesStart[property] === undefined) { // handle end values only the first time. NOT NEEDED? setupProperties is now guarded by _propertiesAreSetUp. _valuesEnd[property] = temp; // } } } // handle the deepness of the values if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) { _valuesStart[property] = startValueIsArray ? [] : {}; var nestedObject = startValue; for (var prop in nestedObject) { _valuesStart[property][prop] = nestedObject[prop]; } // TODO? repeat nested values? And yoyo? And array values? _valuesStartRepeat[property] = startValueIsArray ? [] : {}; var endValues = _valuesEnd[property]; // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { var tmp = {}; for (var prop in endValues) tmp[prop] = endValues[prop]; _valuesEnd[property] = endValues = tmp; } this._setupProperties(nestedObject, _valuesStart[property], endValues, _valuesStartRepeat[property], overrideStartingValues); } else { // Save the starting value, but only once unless override is requested. if (typeof _valuesStart[property] === 'undefined' || overrideStartingValues) { _valuesStart[property] = startValue; } if (!startValueIsArray) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings } if (isInterpolationList) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStartRepeat[property] = _valuesEnd[property].slice().reverse(); } else { _valuesStartRepeat[property] = _valuesStart[property] || 0; } } } }; Tween.prototype.stop = function () { if (!this._isChainStopped) { this._isChainStopped = true; this.stopChainedTweens(); } if (!this._isPlaying) { return this; } this._isPlaying = false; this._isPaused = false; if (this._onStopCallback) { this._onStopCallback(this._object); } return this; }; Tween.prototype.end = function () { this._goToEnd = true; this.update(this._startTime + this._duration); return this; }; Tween.prototype.pause = function (time) { if (time === void 0) { time = now(); } if (this._isPaused || !this._isPlaying) { return this; } this._isPaused = true; this._pauseStart = time; return this; }; Tween.prototype.resume = function (time) { if (time === void 0) { time = now(); } if (!this._isPaused || !this._isPlaying) { return this; } this._isPaused = false; this._startTime += time - this._pauseStart; this._pauseStart = 0; return this; }; Tween.prototype.stopChainedTweens = function () { for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { this._chainedTweens[i].stop(); } return this; }; Tween.prototype.group = function (group) { if (!group) { console.warn('tween.group() without args has been removed, use group.add(tween) instead.'); return this; } group.add(this); return this; }; /** * Removes the tween from whichever group it is in. */ Tween.prototype.remove = function () { var _a; (_a = this._group) === null || _a === void 0 ? void 0 : _a.remove(this); return this; }; Tween.prototype.delay = function (amount) { if (amount === void 0) { amount = 0; } this._delayTime = amount; return this; }; Tween.prototype.repeat = function (times) { if (times === void 0) { times = 0; } this._initialRepeat = times; this._repeat = times; return this; }; Tween.prototype.repeatDelay = function (amount) { this._repeatDelayTime = amount; return this; }; Tween.prototype.yoyo = function (yoyo) { if (yoyo === void 0) { yoyo = false; } this._yoyo = yoyo; return this; }; Tween.prototype.easing = function (easingFunction) { if (easingFunction === void 0) { easingFunction = Easing.Linear.None; } this._easingFunction = easingFunction; return this; }; Tween.prototype.interpolation = function (interpolationFunction) { if (interpolationFunction === void 0) { interpolationFunction = Interpolation.Linear; } this._interpolationFunction = interpolationFunction; return this; }; // eslint-disable-next-line Tween.prototype.chain = function () { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } this._chainedTweens = tweens; return this; }; Tween.prototype.onStart = function (callback) { this._onStartCallback = callback; return this; }; Tween.prototype.onEveryStart = function (callback) { this._onEveryStartCallback = callback; return this; }; Tween.prototype.onUpdate = function (callback) { this._onUpdateCallback = callback; return this; }; Tween.prototype.onRepeat = function (callback) { this._onRepeatCallback = callback; return this; }; Tween.prototype.onComplete = function (callback) { this._onCompleteCallback = callback; return this; }; Tween.prototype.onStop = function (callback) { this._onStopCallback = callback; return this; }; /** * @returns true if the tween is still playing after the update, false * otherwise (calling update on a paused tween still returns true because * it is still playing, just paused). * * @param autoStart - When true, calling update will implicitly call start() * as well. Note, if you stop() or end() the tween, but are still calling * update(), it will start again! */ Tween.prototype.update = function (time, autoStart) { var _this = this; var _a; if (time === void 0) { time = now(); } if (autoStart === void 0) { autoStart = Tween.autoStartOnUpdate; } if (this._isPaused) return true; var property; if (!this._goToEnd && !this._isPlaying) { if (autoStart) this.start(time, true); else return false; } this._goToEnd = false; if (time < this._startTime) { return true; } if (this._onStartCallbackFired === false) { if (this._onStartCallback) { this._onStartCallback(this._object); } this._onStartCallbackFired = true; } if (this._onEveryStartCallbackFired === false) { if (this._onEveryStartCallback) { this._onEveryStartCallback(this._object); } this._onEveryStartCallbackFired = true; } var elapsedTime = time - this._startTime; var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime); var totalTime = this._duration + this._repeat * durationAndDelay; var calculateElapsedPortion = function () { if (_this._duration === 0) return 1; if (elapsedTime > totalTime) { return 1; } var timesRepeated = Math.trunc(elapsedTime / durationAndDelay); var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay; // TODO use %? // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1); if (portion === 0 && elapsedTime === _this._duration) { return 1; } return portion; }; var elapsed = calculateElapsedPortion(); var value = this._easingFunction(elapsed); // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed); } if (this._duration === 0 || elapsedTime >= this._duration) { if (this._repeat > 0) { var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); if (isFinite(this._repeat)) { this._repeat -= completeCount; } // Reassign starting values, restart by making startTime = now for (property in this._valuesStartRepeat) { if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { this._valuesStartRepeat[property] = // eslint-disable-next-line // @ts-ignore FIXME? this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); } if (this._yoyo) { this._swapEndStartRepeatValues(property); } this._valuesStart[property] = this._valuesStartRepeat[property]; } if (this._yoyo) { this._reversed = !this._reversed; } this._startTime += durationAndDelay * completeCount; if (this._onRepeatCallback) { this._onRepeatCallback(this._object); } this._onEveryStartCallbackFired = false; return true; } else { if (this._onCompleteCallback) { this._onCompleteCallback(this._object); } for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { // Make the chained tweens start exactly at the time they should, // even if the `update()` method was called way past the duration of the tween this._chainedTweens[i].start(this._startTime + this._duration, false); } this._isPlaying = false; return false; } } return true; }; Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { for (var property in _valuesEnd) { // Don't update properties that do not exist in the source object if (_valuesStart[property] === undefined) { continue; } var start = _valuesStart[property] || 0; var end = _valuesEnd[property]; var startIsArray = Array.isArray(_object[property]); var endIsArray = Array.isArray(end); var isInterpolationList = !startIsArray && endIsArray; if (isInterpolationList) { _object[property] = this._interpolationFunction(end, value); } else if (typeof end === 'object' && end) { // eslint-disable-next-line // @ts-ignore FIXME? this._updateProperties(_object[property], start, end, value); } else { // Parses relative end values with start as base (e.g.: +10, -3) end = this._handleRelativeValue(start, end); // Protect against non numeric properties. if (typeof end === 'number') { // eslint-disable-next-line // @ts-ignore FIXME? _object[property] = start + (end - start) * value; } } } }; Tween.prototype._handleRelativeValue = function (start, end) { if (typeof end !== 'string') { return end; } if (end.charAt(0) === '+' || end.charAt(0) === '-') { return start + parseFloat(end); } return parseFloat(end); }; Tween.prototype._swapEndStartRepeatValues = function (property) { var tmp = this._valuesStartRepeat[property]; var endValue = this._valuesEnd[property]; if (typeof endValue === 'string') { this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(endValue); } else { this._valuesStartRepeat[property] = this._valuesEnd[property]; } this._valuesEnd[property] = tmp; }; Tween.autoStartOnUpdate = false; return Tween; }()); var VERSION = '25.0.0'; /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ var nextId = Sequence.nextId; /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tweens. */ var TWEEN = mainGroup; // This is the best way to export things in a way that's compatible with both ES // Modules and CommonJS, without build hacks, and so as not to break the // existing API. // https://github.com/rollup/rollup/issues/1961#issuecomment-423037881 /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var getAll = TWEEN.getAll.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var removeAll = TWEEN.removeAll.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var add = TWEEN.add.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var remove = TWEEN.remove.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var update = TWEEN.update.bind(TWEEN); var exports = { Easing: Easing, Group: Group, Interpolation: Interpolation, now: now, setNow: setNow, Sequence: Sequence, nextId: nextId, Tween: Tween, VERSION: VERSION, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ getAll: getAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ removeAll: removeAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ add: add, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ remove: remove, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ update: update, }; export { Easing, Group, Interpolation, Sequence, Tween, VERSION, add, exports as default, getAll, nextId, now, remove, removeAll, setNow, update }; ================================================ FILE: dist/tween.umd.js ================================================ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.TWEEN = {})); })(this, (function (exports) { 'use strict'; /** * The Ease class provides a collection of easing functions for use with tween.js. */ var Easing = Object.freeze({ Linear: Object.freeze({ None: function (amount) { return amount; }, In: function (amount) { return amount; }, Out: function (amount) { return amount; }, InOut: function (amount) { return amount; }, }), Quadratic: Object.freeze({ In: function (amount) { return amount * amount; }, Out: function (amount) { return amount * (2 - amount); }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount; } return -0.5 * (--amount * (amount - 2) - 1); }, }), Cubic: Object.freeze({ In: function (amount) { return amount * amount * amount; }, Out: function (amount) { return --amount * amount * amount + 1; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount; } return 0.5 * ((amount -= 2) * amount * amount + 2); }, }), Quartic: Object.freeze({ In: function (amount) { return amount * amount * amount * amount; }, Out: function (amount) { return 1 - --amount * amount * amount * amount; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount; } return -0.5 * ((amount -= 2) * amount * amount * amount - 2); }, }), Quintic: Object.freeze({ In: function (amount) { return amount * amount * amount * amount * amount; }, Out: function (amount) { return --amount * amount * amount * amount * amount + 1; }, InOut: function (amount) { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount * amount; } return 0.5 * ((amount -= 2) * amount * amount * amount * amount + 2); }, }), Sinusoidal: Object.freeze({ In: function (amount) { return 1 - Math.sin(((1.0 - amount) * Math.PI) / 2); }, Out: function (amount) { return Math.sin((amount * Math.PI) / 2); }, InOut: function (amount) { return 0.5 * (1 - Math.sin(Math.PI * (0.5 - amount))); }, }), Exponential: Object.freeze({ In: function (amount) { return amount === 0 ? 0 : Math.pow(1024, amount - 1); }, Out: function (amount) { return amount === 1 ? 1 : 1 - Math.pow(2, -10 * amount); }, InOut: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } if ((amount *= 2) < 1) { return 0.5 * Math.pow(1024, amount - 1); } return 0.5 * (-Math.pow(2, -10 * (amount - 1)) + 2); }, }), Circular: Object.freeze({ In: function (amount) { return 1 - Math.sqrt(1 - amount * amount); }, Out: function (amount) { return Math.sqrt(1 - --amount * amount); }, InOut: function (amount) { if ((amount *= 2) < 1) { return -0.5 * (Math.sqrt(1 - amount * amount) - 1); } return 0.5 * (Math.sqrt(1 - (amount -= 2) * amount) + 1); }, }), Elastic: Object.freeze({ In: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } return -Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI); }, Out: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } return Math.pow(2, -10 * amount) * Math.sin((amount - 0.1) * 5 * Math.PI) + 1; }, InOut: function (amount) { if (amount === 0) { return 0; } if (amount === 1) { return 1; } amount *= 2; if (amount < 1) { return -0.5 * Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI); } return 0.5 * Math.pow(2, -10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI) + 1; }, }), Back: Object.freeze({ In: function (amount) { var s = 1.70158; return amount === 1 ? 1 : amount * amount * ((s + 1) * amount - s); }, Out: function (amount) { var s = 1.70158; return amount === 0 ? 0 : --amount * amount * ((s + 1) * amount + s) + 1; }, InOut: function (amount) { var s = 1.70158 * 1.525; if ((amount *= 2) < 1) { return 0.5 * (amount * amount * ((s + 1) * amount - s)); } return 0.5 * ((amount -= 2) * amount * ((s + 1) * amount + s) + 2); }, }), Bounce: Object.freeze({ In: function (amount) { return 1 - Easing.Bounce.Out(1 - amount); }, Out: function (amount) { if (amount < 1 / 2.75) { return 7.5625 * amount * amount; } else if (amount < 2 / 2.75) { return 7.5625 * (amount -= 1.5 / 2.75) * amount + 0.75; } else if (amount < 2.5 / 2.75) { return 7.5625 * (amount -= 2.25 / 2.75) * amount + 0.9375; } else { return 7.5625 * (amount -= 2.625 / 2.75) * amount + 0.984375; } }, InOut: function (amount) { if (amount < 0.5) { return Easing.Bounce.In(amount * 2) * 0.5; } return Easing.Bounce.Out(amount * 2 - 1) * 0.5 + 0.5; }, }), generatePow: function (power) { if (power === void 0) { power = 4; } power = power < Number.EPSILON ? Number.EPSILON : power; power = power > 10000 ? 10000 : power; return { In: function (amount) { return Math.pow(amount, power); }, Out: function (amount) { return 1 - Math.pow((1 - amount), power); }, InOut: function (amount) { if (amount < 0.5) { return Math.pow((amount * 2), power) / 2; } return (1 - Math.pow((2 - amount * 2), power)) / 2 + 0.5; }, }; }, }); var _nowFunc = function () { return performance.now(); }; var now = function () { return _nowFunc(); }; function setNow(nowFunction) { _nowFunc = nowFunction; } /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tween */ var Group = /** @class */ (function () { function Group() { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } this._tweens = {}; this._tweensAddedDuringUpdate = {}; this.add.apply(this, tweens); } Group.prototype.getAll = function () { var _this = this; return Object.keys(this._tweens).map(function (tweenId) { return _this._tweens[tweenId]; }); }; Group.prototype.removeAll = function () { this._tweens = {}; }; Group.prototype.add = function () { var _a; var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } for (var _b = 0, tweens_1 = tweens; _b < tweens_1.length; _b++) { var tween = tweens_1[_b]; // Remove from any other group first, a tween can only be in one group at a time. // @ts-expect-error library internal access (_a = tween._group) === null || _a === void 0 ? void 0 : _a.remove(tween); // @ts-expect-error library internal access tween._group = this; this._tweens[tween.getId()] = tween; this._tweensAddedDuringUpdate[tween.getId()] = tween; } }; Group.prototype.remove = function () { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } for (var _a = 0, tweens_2 = tweens; _a < tweens_2.length; _a++) { var tween = tweens_2[_a]; // @ts-expect-error library internal access tween._group = undefined; delete this._tweens[tween.getId()]; delete this._tweensAddedDuringUpdate[tween.getId()]; } }; /** Return true if all tweens in the group are not paused or playing. */ Group.prototype.allStopped = function () { return this.getAll().every(function (tween) { return !tween.isPlaying(); }); }; Group.prototype.update = function (time, preserve) { if (time === void 0) { time = now(); } if (preserve === void 0) { preserve = true; } var tweenIds = Object.keys(this._tweens); if (tweenIds.length === 0) return; // Tweens are updated in "batches". If you add a new tween during an // update, then the new tween will be updated in the next batch. // If you remove a tween during an update, it may or may not be updated. // However, if the removed tween was added during the current batch, // then it will not be updated. while (tweenIds.length > 0) { this._tweensAddedDuringUpdate = {}; for (var i = 0; i < tweenIds.length; i++) { var tween = this._tweens[tweenIds[i]]; var autoStart = !preserve; if (tween && tween.update(time, autoStart) === false && !preserve) this.remove(tween); } tweenIds = Object.keys(this._tweensAddedDuringUpdate); } }; Group.prototype.onComplete = function (callback) { var group = this.getAll(); group.forEach(function (tween) { var prevCallback = tween.getCompleteCallback(); tween.onComplete(function () { prevCallback === null || prevCallback === void 0 ? void 0 : prevCallback(tween); // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. var completedGroup = group.filter(function (tween) { return !tween.isPlaying(); }); if (completedGroup.length === group.length - 1) callback(group); }); }); }; return Group; }()); /** * */ var Interpolation = { Linear: function (v, k) { var m = v.length - 1; var f = m * k; var i = Math.floor(f); var fn = Interpolation.Utils.Linear; if (k < 0) { return fn(v[0], v[1], f); } if (k > 1) { return fn(v[m], v[m - 1], m - f); } return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); }, Bezier: function (v, k) { var b = 0; var n = v.length - 1; var pw = Math.pow; var bn = Interpolation.Utils.Bernstein; for (var i = 0; i <= n; i++) { b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); } return b; }, CatmullRom: function (v, k) { var m = v.length - 1; var f = m * k; var i = Math.floor(f); var fn = Interpolation.Utils.CatmullRom; if (v[0] === v[m]) { if (k < 0) { i = Math.floor((f = m * (1 + k))); } return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); } else { if (k < 0) { return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); } if (k > 1) { return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); } return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); } }, Utils: { Linear: function (p0, p1, t) { return (p1 - p0) * t + p0; }, Bernstein: function (n, i) { var fc = Interpolation.Utils.Factorial; return fc(n) / fc(i) / fc(n - i); }, Factorial: (function () { var a = [1]; return function (n) { var s = 1; if (a[n]) { return a[n]; } for (var i = n; i > 1; i--) { s *= i; } a[n] = s; return s; }; })(), CatmullRom: function (p0, p1, p2, p3, t) { var v0 = (p2 - p0) * 0.5; var v1 = (p3 - p1) * 0.5; var t2 = t * t; var t3 = t * t2; return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; }, }, }; /** * Utils */ var Sequence = /** @class */ (function () { function Sequence() { } Sequence.nextId = function () { return Sequence._nextId++; }; Sequence._nextId = 0; return Sequence; }()); var mainGroup = new Group(); /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ var Tween = /** @class */ (function () { function Tween(object, group) { this._isPaused = false; this._pauseStart = 0; this._valuesStart = {}; this._valuesEnd = {}; this._valuesStartRepeat = {}; this._duration = 1000; this._isDynamic = false; this._initialRepeat = 0; this._repeat = 0; this._yoyo = false; this._isPlaying = false; this._reversed = false; this._delayTime = 0; this._startTime = 0; this._easingFunction = Easing.Linear.None; this._interpolationFunction = Interpolation.Linear; // eslint-disable-next-line this._chainedTweens = []; this._onStartCallbackFired = false; this._onEveryStartCallbackFired = false; this._id = Sequence.nextId(); this._isChainStopped = false; this._propertiesAreSetUp = false; this._goToEnd = false; this._object = object; if (typeof group === 'object') { this._group = group; group.add(this); } // Use "true" to restore old behavior (will be removed in future release). else if (group === true) { this._group = mainGroup; mainGroup.add(this); } } Tween.prototype.getId = function () { return this._id; }; Tween.prototype.getCompleteCallback = function () { return this._onCompleteCallback; }; Tween.prototype.isPlaying = function () { return this._isPlaying; }; Tween.prototype.isPaused = function () { return this._isPaused; }; Tween.prototype.getDuration = function () { return this._duration; }; Tween.prototype.to = function (target, duration) { if (duration === void 0) { duration = 1000; } if (this._isPlaying) throw new Error('Can not call Tween.to() while Tween is already started or paused. Stop the Tween first.'); this._valuesEnd = target; this._propertiesAreSetUp = false; this._duration = duration < 0 ? 0 : duration; return this; }; Tween.prototype.duration = function (duration) { if (duration === void 0) { duration = 1000; } this._duration = duration < 0 ? 0 : duration; return this; }; Tween.prototype.dynamic = function (dynamic) { if (dynamic === void 0) { dynamic = false; } this._isDynamic = dynamic; return this; }; Tween.prototype.start = function (time, overrideStartingValues) { if (time === void 0) { time = now(); } if (overrideStartingValues === void 0) { overrideStartingValues = false; } if (this._isPlaying) { return this; } this._repeat = this._initialRepeat; if (this._reversed) { // If we were reversed (f.e. using the yoyo feature) then we need to // flip the tween direction back to forward. this._reversed = false; for (var property in this._valuesStartRepeat) { this._swapEndStartRepeatValues(property); this._valuesStart[property] = this._valuesStartRepeat[property]; } } this._isPlaying = true; this._isPaused = false; this._onStartCallbackFired = false; this._onEveryStartCallbackFired = false; this._isChainStopped = false; this._startTime = time; this._startTime += this._delayTime; if (!this._propertiesAreSetUp || overrideStartingValues) { this._propertiesAreSetUp = true; // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { var tmp = {}; for (var prop in this._valuesEnd) tmp[prop] = this._valuesEnd[prop]; this._valuesEnd = tmp; } this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues); } return this; }; Tween.prototype.startFromCurrentValues = function (time) { return this.start(time, true); }; Tween.prototype._setupProperties = function (_object, _valuesStart, _valuesEnd, _valuesStartRepeat, overrideStartingValues) { for (var property in _valuesEnd) { var startValue = _object[property]; var startValueIsArray = Array.isArray(startValue); var propType = startValueIsArray ? 'array' : typeof startValue; var isInterpolationList = !startValueIsArray && Array.isArray(_valuesEnd[property]); // If `to()` specifies a property that doesn't exist in the source object, // we should not set that property in the object if (propType === 'undefined' || propType === 'function') { continue; } // Check if an Array was provided as property value if (isInterpolationList) { var endValues = _valuesEnd[property]; if (endValues.length === 0) { continue; } // Handle an array of relative values. // Creates a local copy of the Array with the start value at the front var temp = [startValue]; for (var i = 0, l = endValues.length; i < l; i += 1) { var value = this._handleRelativeValue(startValue, endValues[i]); if (isNaN(value)) { isInterpolationList = false; console.warn('Found invalid interpolation list. Skipping.'); break; } temp.push(value); } if (isInterpolationList) { // if (_valuesStart[property] === undefined) { // handle end values only the first time. NOT NEEDED? setupProperties is now guarded by _propertiesAreSetUp. _valuesEnd[property] = temp; // } } } // handle the deepness of the values if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) { _valuesStart[property] = startValueIsArray ? [] : {}; var nestedObject = startValue; for (var prop in nestedObject) { _valuesStart[property][prop] = nestedObject[prop]; } // TODO? repeat nested values? And yoyo? And array values? _valuesStartRepeat[property] = startValueIsArray ? [] : {}; var endValues = _valuesEnd[property]; // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { var tmp = {}; for (var prop in endValues) tmp[prop] = endValues[prop]; _valuesEnd[property] = endValues = tmp; } this._setupProperties(nestedObject, _valuesStart[property], endValues, _valuesStartRepeat[property], overrideStartingValues); } else { // Save the starting value, but only once unless override is requested. if (typeof _valuesStart[property] === 'undefined' || overrideStartingValues) { _valuesStart[property] = startValue; } if (!startValueIsArray) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings } if (isInterpolationList) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStartRepeat[property] = _valuesEnd[property].slice().reverse(); } else { _valuesStartRepeat[property] = _valuesStart[property] || 0; } } } }; Tween.prototype.stop = function () { if (!this._isChainStopped) { this._isChainStopped = true; this.stopChainedTweens(); } if (!this._isPlaying) { return this; } this._isPlaying = false; this._isPaused = false; if (this._onStopCallback) { this._onStopCallback(this._object); } return this; }; Tween.prototype.end = function () { this._goToEnd = true; this.update(this._startTime + this._duration); return this; }; Tween.prototype.pause = function (time) { if (time === void 0) { time = now(); } if (this._isPaused || !this._isPlaying) { return this; } this._isPaused = true; this._pauseStart = time; return this; }; Tween.prototype.resume = function (time) { if (time === void 0) { time = now(); } if (!this._isPaused || !this._isPlaying) { return this; } this._isPaused = false; this._startTime += time - this._pauseStart; this._pauseStart = 0; return this; }; Tween.prototype.stopChainedTweens = function () { for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { this._chainedTweens[i].stop(); } return this; }; Tween.prototype.group = function (group) { if (!group) { console.warn('tween.group() without args has been removed, use group.add(tween) instead.'); return this; } group.add(this); return this; }; /** * Removes the tween from whichever group it is in. */ Tween.prototype.remove = function () { var _a; (_a = this._group) === null || _a === void 0 ? void 0 : _a.remove(this); return this; }; Tween.prototype.delay = function (amount) { if (amount === void 0) { amount = 0; } this._delayTime = amount; return this; }; Tween.prototype.repeat = function (times) { if (times === void 0) { times = 0; } this._initialRepeat = times; this._repeat = times; return this; }; Tween.prototype.repeatDelay = function (amount) { this._repeatDelayTime = amount; return this; }; Tween.prototype.yoyo = function (yoyo) { if (yoyo === void 0) { yoyo = false; } this._yoyo = yoyo; return this; }; Tween.prototype.easing = function (easingFunction) { if (easingFunction === void 0) { easingFunction = Easing.Linear.None; } this._easingFunction = easingFunction; return this; }; Tween.prototype.interpolation = function (interpolationFunction) { if (interpolationFunction === void 0) { interpolationFunction = Interpolation.Linear; } this._interpolationFunction = interpolationFunction; return this; }; // eslint-disable-next-line Tween.prototype.chain = function () { var tweens = []; for (var _i = 0; _i < arguments.length; _i++) { tweens[_i] = arguments[_i]; } this._chainedTweens = tweens; return this; }; Tween.prototype.onStart = function (callback) { this._onStartCallback = callback; return this; }; Tween.prototype.onEveryStart = function (callback) { this._onEveryStartCallback = callback; return this; }; Tween.prototype.onUpdate = function (callback) { this._onUpdateCallback = callback; return this; }; Tween.prototype.onRepeat = function (callback) { this._onRepeatCallback = callback; return this; }; Tween.prototype.onComplete = function (callback) { this._onCompleteCallback = callback; return this; }; Tween.prototype.onStop = function (callback) { this._onStopCallback = callback; return this; }; /** * @returns true if the tween is still playing after the update, false * otherwise (calling update on a paused tween still returns true because * it is still playing, just paused). * * @param autoStart - When true, calling update will implicitly call start() * as well. Note, if you stop() or end() the tween, but are still calling * update(), it will start again! */ Tween.prototype.update = function (time, autoStart) { var _this = this; var _a; if (time === void 0) { time = now(); } if (autoStart === void 0) { autoStart = Tween.autoStartOnUpdate; } if (this._isPaused) return true; var property; if (!this._goToEnd && !this._isPlaying) { if (autoStart) this.start(time, true); else return false; } this._goToEnd = false; if (time < this._startTime) { return true; } if (this._onStartCallbackFired === false) { if (this._onStartCallback) { this._onStartCallback(this._object); } this._onStartCallbackFired = true; } if (this._onEveryStartCallbackFired === false) { if (this._onEveryStartCallback) { this._onEveryStartCallback(this._object); } this._onEveryStartCallbackFired = true; } var elapsedTime = time - this._startTime; var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime); var totalTime = this._duration + this._repeat * durationAndDelay; var calculateElapsedPortion = function () { if (_this._duration === 0) return 1; if (elapsedTime > totalTime) { return 1; } var timesRepeated = Math.trunc(elapsedTime / durationAndDelay); var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay; // TODO use %? // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1); if (portion === 0 && elapsedTime === _this._duration) { return 1; } return portion; }; var elapsed = calculateElapsedPortion(); var value = this._easingFunction(elapsed); // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed); } if (this._duration === 0 || elapsedTime >= this._duration) { if (this._repeat > 0) { var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); if (isFinite(this._repeat)) { this._repeat -= completeCount; } // Reassign starting values, restart by making startTime = now for (property in this._valuesStartRepeat) { if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { this._valuesStartRepeat[property] = // eslint-disable-next-line // @ts-ignore FIXME? this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); } if (this._yoyo) { this._swapEndStartRepeatValues(property); } this._valuesStart[property] = this._valuesStartRepeat[property]; } if (this._yoyo) { this._reversed = !this._reversed; } this._startTime += durationAndDelay * completeCount; if (this._onRepeatCallback) { this._onRepeatCallback(this._object); } this._onEveryStartCallbackFired = false; return true; } else { if (this._onCompleteCallback) { this._onCompleteCallback(this._object); } for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { // Make the chained tweens start exactly at the time they should, // even if the `update()` method was called way past the duration of the tween this._chainedTweens[i].start(this._startTime + this._duration, false); } this._isPlaying = false; return false; } } return true; }; Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { for (var property in _valuesEnd) { // Don't update properties that do not exist in the source object if (_valuesStart[property] === undefined) { continue; } var start = _valuesStart[property] || 0; var end = _valuesEnd[property]; var startIsArray = Array.isArray(_object[property]); var endIsArray = Array.isArray(end); var isInterpolationList = !startIsArray && endIsArray; if (isInterpolationList) { _object[property] = this._interpolationFunction(end, value); } else if (typeof end === 'object' && end) { // eslint-disable-next-line // @ts-ignore FIXME? this._updateProperties(_object[property], start, end, value); } else { // Parses relative end values with start as base (e.g.: +10, -3) end = this._handleRelativeValue(start, end); // Protect against non numeric properties. if (typeof end === 'number') { // eslint-disable-next-line // @ts-ignore FIXME? _object[property] = start + (end - start) * value; } } } }; Tween.prototype._handleRelativeValue = function (start, end) { if (typeof end !== 'string') { return end; } if (end.charAt(0) === '+' || end.charAt(0) === '-') { return start + parseFloat(end); } return parseFloat(end); }; Tween.prototype._swapEndStartRepeatValues = function (property) { var tmp = this._valuesStartRepeat[property]; var endValue = this._valuesEnd[property]; if (typeof endValue === 'string') { this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(endValue); } else { this._valuesStartRepeat[property] = this._valuesEnd[property]; } this._valuesEnd[property] = tmp; }; Tween.autoStartOnUpdate = false; return Tween; }()); var VERSION = '25.0.0'; /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ var nextId = Sequence.nextId; /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tweens. */ var TWEEN = mainGroup; // This is the best way to export things in a way that's compatible with both ES // Modules and CommonJS, without build hacks, and so as not to break the // existing API. // https://github.com/rollup/rollup/issues/1961#issuecomment-423037881 /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var getAll = TWEEN.getAll.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var removeAll = TWEEN.removeAll.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var add = TWEEN.add.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var remove = TWEEN.remove.bind(TWEEN); /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ var update = TWEEN.update.bind(TWEEN); var exports$1 = { Easing: Easing, Group: Group, Interpolation: Interpolation, now: now, setNow: setNow, Sequence: Sequence, nextId: nextId, Tween: Tween, VERSION: VERSION, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ getAll: getAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ removeAll: removeAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ add: add, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ remove: remove, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ update: update, }; exports.Easing = Easing; exports.Group = Group; exports.Interpolation = Interpolation; exports.Sequence = Sequence; exports.Tween = Tween; exports.VERSION = VERSION; exports.add = add; exports.default = exports$1; exports.getAll = getAll; exports.nextId = nextId; exports.now = now; exports.remove = remove; exports.removeAll = removeAll; exports.setNow = setNow; exports.update = update; Object.defineProperty(exports, '__esModule', { value: true }); })); ================================================ FILE: docs/contributor_guide.md ================================================ # tween.js contributor guide More languages: [English](./contributor_guide.md), [简体中文](./contributor_guide_zh-CN.md) This guide is for people who want to _contribute_ to the library or are curious to learn what's behind the scenes: how is it tested? what do we automate? how do we do releases? etc. If you are looking for documentation on _how to use_ the library, the [user guide](./user_guide.md) is for you. **NOTE: this document is a work in progress. More content will be added in stages. If you have questions you'd like to see answered, please add them [as comments on this issue](https://github.com/tweenjs/tween.js/issues/323). THANKS!** In this guide: - [tween.js contributor guide](#tweenjs-contributor-guide) - [Developer requirements](#developer-requirements) - [Testing](#testing) - [Unit tests](#unit-tests) - [Code style and lint tests](#code-style-and-lint-tests) - [Other types of tests](#other-types-of-tests) - [Continuous integration](#continuous-integration) - [Release process](#release-process) ## Developer requirements Although tween.js does _not_ need node.js to work, we use it for development. So you will need to [install node.js](https://nodejs.org/en/download/) before you can work on the library. Node.js includes the `npm` tool that we use to run scripts such as the one for packaging, running tests, etc. Please make sure it is working in your system before you attempt to run any of the steps detailed below. Once node.js is installed, clone the tween.js repository: ```bash git clone https://github.com/tweenjs/tween.js.git ``` Change to the folder: ```bash cd tween.js ``` And run the script to install development dependencies: ```bash npm install ``` Or in three lines: ```bash git clone https://github.com/tweenjs/tween.js.git cd tween.js npm install ``` Once `npm install` completes successfully, try having a go at running the tests: ```bash npm test ``` If you get issues running any of the above, try to search for the text of the error using your search engine of choice. This is normally the fastest route, as many people might have encountered the same issue already. ## Testing There's a suite of automated tests in the `test` directory. These can quickly spot regressions on the code--useful when new features are added or when code is changed to fix a bug; we don't want to introduce new bugs! They also spot style issues, which helps us keep the library cohesive. To run the tests, type: ```bash npm test ``` You should run the tests after you change code in the library. If you change the behaviour the tests describe, the tests won't pass and you'll get an error pointing to the test(s) that failed. This might be either because... - you overlooked something or there's an error in your code, or... - the library or the tests themselves are wrong The one that happens more frequently is the first one, but the second one has happened, with edge cases. **Another thing you should do once the automated tests pass is to run the examples in the `examples` folder**. It is rare, but it might happen that your changes introduced cosmetic differences that the automated tests are not checking for, and the only way to notice this is by running the examples and have a human spot the difference in output. If you don't want to checkout two copies of the library, you can look at [the online examples](https://github.com/tweenjs/tween.js#examples). ### Unit tests Tests are in the `src/tests.ts` file. The tests are executed using [nodeunit](https://www.npmjs.com/package/nodeunit). **TODO:** the tests should also work if opening `test/unit/nodeunit.html` in a browser, but they are broken right now. There is [an open issue](https://github.com/tweenjs/tween.js/issues/307) to make them work again. ### Code style and lint tests We use [Prettier](https://prettier.io) and [ESLint](https://eslint.org) to ensure the code style is uniform. To automatically format code and report any errors for pieces of code that aren't automatically formattable, run: ```bash npm run test-lint ``` The Prettier rules are in `.prettierrc.js` and ESLint rules are in `.eslintrc.js`. ### Other types of tests We would like to test for performance regressions i.e. if a change made things go slower, or simply, for performance, so we can compare the performance of the same code between different browsers. There's an [open issue](https://github.com/tweenjs/discuss/issues/3) to track work on this, but we have not made progress on it yet. Help! :-) ## Continuous integration We use GitHub Actions for continuous integration so that a build and tests will run for every pull request. The `.github/workflows/tests.yml` file tells GitHub what to run; in our case we run `npm install` followed by `npm test` in the OSes and versions of Node.js specified in that file. **TODO:** Add macOS and Windows to OSes that the tests run on. Help! :) ## Release process Currently the release process is manual. When ready to make a release on the `master` branch, ensure there are no un-committed changes, then run `npm run release:patch` to release a new version with its patch number bumped, `npm run release:minor` to release a new version with its minor number bumped, or `npm run release:major` to release a new version with its major number bumped. Tip: see [semver.org](https://semver.org) and the [npm-semver](https://docs.npmjs.com/misc/semver) docs to learn about semantic versioning. ================================================ FILE: docs/contributor_guide_zh-CN.md ================================================ # tween.js 贡献者指南 更多语言: [English](./contributor_guide.md), [简体中文](./contributor_guide_zh-CN.md) 本指南适用于想要向库贡献的人,或者想了解背后的内容:如何进行测试?我们自动化什么?我们如何做发布?等等。 如果你正在查找*如何使用*库的文档,[用户指南](./user_guide_zh-CN.md) 是适合你的。 **NOTE: 这个文档是一个正在进行的工作。更多内容将分阶段添加。如果你有问题,你想看到回答,请在 [as comments on this issue](https://github.com/tweenjs/tween.js/issues/323) 中提出,谢谢!** 目录: - [tween.js 贡献者指南](#tween.js-贡献者指南) - [开发者要求](#开发者要求) - [测试](#测试) - [单元测试](#单元测试) - [代码风格和 lint 测试](#代码风格和-lint-测试) - [其他类型的测试](#其他类型的测试) - [持续集成](#持续集成) - [发布流程](#发布流程) ## 开发者要求 虽然 tween.js *不*需要依赖 node.js 运行,但我们使用 node.js 来进行开发。所以我们需要先 [安装 node.js](https://nodejs.org/en/download/),然后才能在库中工作。 Node.js 包括我们用来运行脚本的 npm 工具,例如打包、运行测试等的脚本。在你尝试运行下面详述的任何步骤之前,请确保它在你的系统中正常工作。 安装 node.js 后,克隆 tween.js 存储库: ```bash git clone https://github.com/tweenjs/tween.js.git ``` 进入该文件夹: ```bash cd tween.js ``` 并运行脚本来安装开发依赖项: ```bash npm install ``` 或者分三行: ```bash git clone https://github.com/tweenjs/tween.js.git cd tween.js npm install ``` `npm install` 成功完成后,尝试运行测试: ```bash npm test ``` 如果你在运行上述任何步骤时遇到问题,请尝试使用你选择的搜索引擎搜索错误文本。这通常是最快的解决方法,因为很多人可能已经遇到过同样的问题。 ## 测试 `test` 目录中有一套自动化测试。 这些可以快速发现代码上的回归,在添加新功能或更改代码以修复 Bug 时很有用;我们不想引入新的 Bugs!自动化测试还会发现风格问题,这有助于我们保持库的凝聚力。 要运行测试,请输入: ```bash npm test ``` 你应该在更改库中的代码后运行测试。如果你更改测试描述的行为,测试将不会通过,你将得到指向失败测试的错误。 这可能是因为... - 你忽略了一些东西,或者你的代码有错误,或者... - 库或测试本身是错误的 以上两种情况,发生频率更高的是第一个,但第如果二个发生了,可能是边缘情况。 **自动化测试通过之后,你应该做的另一件事是运行 `examples` 文件夹中的示例**。有种情况很少见,但可能会发生:你的更改引入了自动测试未检查的外观差异,注意到这一点的唯一方法是运行示例并让人工发现输出中的差异。如果你不想签出库的两个副本,可以查看[在线例子](https://github.com/tweenjs/tween.js#examples)。 ### 单元测试 测试用例在 `src/tests.ts` 文件中。 测试使用 [nodeunit](https://www.npmjs.com/package/nodeunit) 执行。 **TODO:** 如果在浏览器中打开 `test/unit/nodeunit.html`,测试也应该能够正常运行,但是现在已经被损坏。有一个 [打开的问题](https://github.com/tweenjs/tween.js/issues/307) 可以使他们再次工作。 ### 代码风格和 lint 测试 我们使用 [JSCS](http://jscs.info/) 和 [JSHint](http://jshint.com/) 来保证代码风格的统一。 要自动格式化代码并报告不可被格式化的代码片段的任何错误,请运行: ```base npm run test-lint ``` Prettier 规则在 `.prettierrc.js` 中,ESLint 规则在 `.eslintrc.js` 中。 ### 其他类型的测试 我们想回归测试性能,即如果更改使运行变得更慢,或者简单地测试性能,那么我们可以比较不同浏览器之间相同代码的性能。 有一个[open issue](https://github.com/tweenjs/discuss/issues/3)来跟踪这方面的工作,但我们还没有取得进展。请求帮助! :-) ## 持续集成 我们使用 GitHub Actions 进行持续集成,以便为每个 pull request 运行构建和测试。 `.github/workflows/tests.yml` 文件告诉 GitHub 要运行什么;在我们的例子中,我们在该文件中指定的操作系统和 Node.js 版本中运行 `npm install`,然后运行 `npm test`。 **TODO**:将 macOS 和 Windows 添加到运行测试的操作系统。 请求帮助! :) ## 发布流程 目前发布过程是手动的。 当准备好在 `master` 分支上发布时,确保没有未提交的更改,然后运行 `npm run release:patch` 发布一个新版本,其补丁号已被修改,`npm run release:minor` ,修改 minor 版本号并发布一个新版本,或者 `npm run release:major` 修改 major 版本号并发布一个新版本。 Tip: 请参阅 [semver.org](https://semver.org/) 和 [npm-semver](https://docs.npmjs.com/misc/semver) 文档以了解语义版本控制。 ================================================ FILE: docs/user_guide.md ================================================ # tween.js user guide More languages: [English](./user_guide.md), [简体中文](./user_guide_zh-CN.md) _**NOTE** This is a work in progress. If you find that something is unclear or missing details, please [file an issue](https://github.com/tweenjs/tween.js/issues/new) and help make this guide better. Or feel free to submit clarifications or improvements of your own if you feel you can help too!_ ## What is a tween? How do they work? Why do you want to use them? A tween (from [_in-between_](http://en.wikipedia.org/wiki/Inbetweening)) is a concept that allows you to change the values of the properties of an object in a smooth way. You just tell it which properties you want to change, which final values should they have when the tween finishes running, and how long should this take, and the tweening engine will take care of finding the intermediate values from the starting to the ending point. For example, suppose you have a `position` object with `x` and `y` coordinates: ```javascript const position = {x: 100, y: 0} ``` If you wanted to change the `x` value from `100` to `200`, you'd do this: ```javascript import {Tween} from '@tweenjs/tween.js' // Create a tween for position first const tween = new Tween(position) // Then tell the tween we want to animate the x property over 1000 milliseconds tween.to({x: 200}, 1000) ``` Actually this won't do anything yet. The tween has been created but it's not active. You need to start it: ```javascript // And set it to start tween.start() ``` Finally in order to run as smoothly as possible you should call the `tween.update()` function in the same main loop you're using for animating. This generally looks like this: ```javascript animate() function animate() { requestAnimationFrame(animate) // [...] tween.update() // [...] } ``` After 1 second (i.e. 1000 milliseconds) `position.x` will be `200`. But unless you print the value of `x` to the console, you can't see its value changing. You might want to use the `onUpdate` callback: ```javascript tween.onUpdate(function (object) { console.log(object.x) }) ``` This function will be called each time the tween is updated; how often this happens depends on many factors--how fast (and how busy!) your computer or device is, for example. So far we've only used tweens to print values to the console, but you could use it for things such as animating positions of three.js objects: ```javascript const tween = new Tween(cube.position).to({x: 100, y: 100, z: 100}, 10000).start() animate() function animate() { requestAnimationFrame(animate) tween.update() threeRenderer.render(scene, camera) } ``` In this case, because the three.js renderer will look at the object's position before rendering, you don't need to use an explicit `onUpdate` callback. You might have noticed something different here too: we're chaining the tween function calls! Each tween function returns the tween instance, so you can rewrite the following code: ```javascript const tween = new Tween(position) tween.to({x: 200}, 1000) tween.start() ``` into this ```javascript const tween = new Tween(position).to({x: 200}, 1000).start() ``` You'll see this a lot in the examples, so it's good to be familiar with it! Check [04-simplest](../examples/04_simplest.html) for a working example. ## Animating with tween.js Tween.js doesn't run by itself. You need to tell it when to run, by explicitly calling the `update` method of each tween, or the `update` method of a `Group` that has multiple tweens (more on Groups below). The recommended method is to do this inside your main animation loop, which should be called with `requestAnimationFrame` for getting the best graphics performance: We've seen this example before: ```javascript animate() function animate() { requestAnimationFrame(animate) // [...] tween.update() // [...] } ``` If called without parameters, `update` will determine the current time in order to find out how long has it been since the last time it ran. However you can also pass an explicit time parameter to `update`. Thus, ```javascript tween.update(100) ``` means "update with time = 100 milliseconds". You can use this to make sure that all the time-dependent functions in your code are using the very same time value. For example, suppose you've got a player and want to run tweens in sync. Your `animate` code could look like this: ```javascript let currentTime = player.currentTime tween.update(currentTime) ``` We use explicit time values for the unit tests. You can have a look at [tests.ts](../src/tests.ts) to see how we call `tween.update()` with different values in order to simulate time passing. ## Controlling a tween ### `start` and `stop` So far we've learnt about the `Tween.start` method, but there are more methods that control individual tweens. Probably the most important one is the `start` counterpart: `stop`. If you want to cancel a tween, just call this method over an individual tween: ```js tween.stop() ``` Stopping a tween that was never started or that has already been stopped has no effect. No errors are thrown either. The `start` method also accepts a `time` argument. If you use it, the tween won't start until that particular moment in time; otherwise it will start as soon as possible (i.e. on the next call to `tween.update()`). The `start` method accepts a second boolean argument: when `true`, a tween that we previously used will start from the values in the target object, instead of starting from the beginning. Useful for stopping a tween, then starting another one that will continue from the current location. ### `startFromCurrentValues` This is an alias for `tween.start(undefined, true)`, to make a previously-used tween start from the last values of the target object, instead of from the beginning. ### `update` Individual tweens have an `update` method to so that they can be updated over time in an animation loop, and on each update they will apply updated values to their target object. ```js const tween = new Tween(someObject).to(/*...*/).start() function animate(time) { tween.update(time) requestAnimationFrame(animate) } ``` ### `pause` While an tween is running (i.e. it has already been `start`ed and may have already been `update`d numerous times) it can be paused. Even if `update()` is called while the tween is paused, its time will not move forward. `isPlaying` will still be true while a tween is `paused` and its `update()` method continues to track time progression (you can continue to call `update()` while a tween is paused). ```js tween.start() function animate() { tween.update() requestAnimationFrame(animate) } animate() // at any time while the tween is running tween.pause() ``` ### `chain` Things get more interesting when you sequence different tweens in order, i.e. setup one tween to start once a previous one has finished. We call this _chaining tweens_, and it's done with the `chain` method. Thus, to make `tweenB` start after `tweenA` finishes: ```javascript tweenA.chain(tweenB) ``` Or, for an infinite chain, set `tweenA` to start once `tweenB` finishes: ```javascript tweenA.chain(tweenB) tweenB.chain(tweenA) ``` Check [Hello world](../examples/00_hello_world.html) to see an example of these infinite chains. In other cases, you may want to chain multiple tweens to another tween in a way that they (the chained tweens) all start animating at the same time: ```javascript tweenA.chain(tweenB, tweenC) ``` > **Warning** Calling `tweenA.chain(tweenB)` actually modifies tweenA so that tweenB is always started when tweenA finishes. The return value of `chain` is just tweenA, not a new tween. ### `repeat` If you wanted a tween to repeat forever you could chain it to itself, but a better way is to use the `repeat` method. It accepts a parameter that describes how many repetitions you want after the first tween is completed: ```javascript tween.repeat(10) // repeats 10 times after the first tween and stops tween.repeat(Infinity) // repeats forever ``` The total number of tweens will be the repeat parameter plus one for the initial tween. Check the [Repeat](../examples/08_repeat.html) example. ### `yoyo` This function only has effect if used along with `repeat`. When active, the behaviour of the tween will be _like a yoyo_, i.e. it will bounce to and from the start and end values, instead of just repeating the same sequence from the beginning: ```js tween.yoyo(false) // default value, animation will only go from start to end value tween.yoyo(true) // tween will 'yoyo' between start and end values ``` ### `delay` More complex arrangements might require delaying a tween before it actually starts running. You can do that using the `delay` method: ```javascript tween.delay(1000) tween.start() ``` will start executing 1 second after the `start` method has been called. ### `repeatDelay` Normally the `delay` time is applied between repetitions of a tween, but if a value is provided to the `repeatDelay` function then that value will determine the total time elapsed between repetitions of a tween. Consider this example: ```javascript tween.delay(1000) tween.repeatDelay(500) tween.start() ``` The first iteration of the tween will happen after one second, the second iteration will happen a half second after the first iteration ends, the third iteration will happen a half second after the second iteration ends, etc. If you want to delay the initial iteration but you don't want any delay between iterations, then make sure to call `tween.repeatDelay(0)`. ### `dynamic` If `dynamic` is set to `true` (it defaults to `false`) objects passed to `tween.to()` can be modified on the outside of a tween while the tween is animating. This can be used to dynamically modify the outcome of a tween while it is running. See the [Dynamic to](http://tweenjs.github.io/tween.js/examples/07_dynamic_to.html) example. In that example, in both scenes, the position of the rabbit is updated during the animation. The rabbit position happens to be the object passed into the fox's `tween.to()` method. As the rabbit position is updated, in the first scene with `.dynamic(false)` the fox moves towards the initial position of the rabbit and does not chase the rabbit, and in the second scene with `.dynamic(true)` the final destination of the fox is hence also updated which makes the fox chase the rabbit. See the other `dynamic to` examples for more ideas. > **Warning** When `dynamic` is set to `false`, Tween makes a copy of the object passed into `tween.to()` and will never modify it (hence updating the original object from the outside is not dynamic). When `dynamic` is `true`, Tween uses the original object as the source of values during animation (every update reads the values, hence they can be modified dynamically) but note that **in dynamic mode, Tween will modify any interpolation arrays of the object passed into `tween.to()` which may cause side-effects on any external code that may also rely on the same object**. ## Controlling groups of tweens Sometimes you want to update multiple tweens at once, which can be useful when grouping a set of tweens into a logical component in your application. You can do this with a `Group`. First add multiple tweens to a group: ```js import {Group, Tween} from '@tweenjs/tween.js' const tween1 = new Tween(obj1).to(...).start() const tween2 = new Tween(obj2).to(...).start() const group = new Group() group.add(tween1) group.add(tween2) ``` Then call `group.update()` in your animation loop instead of on individual tweens: ```js animate() function animate() { requestAnimationFrame(animate) // [...] group.update() // [...] } ``` Note that a tween can only belong to a single group. Adding a tween to a group automatically removes it from any previous group. ### `group.getAll()` Returns an array of all tweens added to a group. ### `group.add(tween)` Add a tween to a group. ### `group.remove(tween)` Remove a tween from a group. ### `group.removeAll()` Remove all tween from a group. ### `group.update(time?)` Update all tweens in a group, with an optional time value. If time value is not supplied, it default to the current time. ## Changing the easing function (AKA make it bouncy) Tween.js will perform the interpolation between values (i.e. the easing) in a linear manner, so the change will be directly proportional to the elapsed time. This is predictable but also quite uninteresting visually wise. Worry not--this behaviour can be easily changed using the `easing` method. For example: ```javascript import {Tween, Easing} from '@tweenjs/tween.js' // ... tween.easing(Easing.Quadratic.In) ``` This will result in the tween slowly starting to change towards the final value, accelerating towards the middle, and then quickly reaching its final value. In contrast, `Easing.Quadratic.Out` would start changing quickly towards the value, but then slow down as it approaches the final value. ### Available `Easing` functions There are a few existing easing functions provided with tween.js. They are grouped by the type of equation they represent: Linear, Quadratic, Cubic, Quartic, Quintic, Sinusoidal, Exponential, Circular, Elastic, Back and Bounce, and then by the easing type: In, Out and InOut. Probably the names won't be saying anything to you unless you're familiar with these concepts already, so it is probably the time to check the [Graphs](../examples/03_graphs.html) example, which graphs all the curves in one page so you can compare how they look at a glance. `Easing` also has a function called `generatePow()`. This function generates easing functions for different curves depending on arguments. You can check the relevance of the arguments to curves in the [example of pow easing](../examples/17_generate_pow.html) page. _Credit where credit is due:_ these functions are derived from the original set of equations that Robert Penner graciously made available as free software a few years ago, but have been optimised to play nicely with JavaScript. ### Using a custom easing function Not only can you use any of the existing functions, but you can also provide your own, as long as it follows a couple of conventions: - it must accept one parameter: - `k`: the easing progress, or how far along the duration of the tween we are. Allowed values are in the range [0, 1]. - it must return a value based on the input parameters. The easing function is only called _once per tween_ on each update, no matter how many properties are to be changed. The result is then used with the initial value and the difference (the _deltas_) between this and the final values, as in this pseudocode: ``` easedElapsed = easing(k); for each property: newPropertyValue = initialPropertyValue + propertyDelta * easedElapsed; ``` For the performance-obsessed people out there: the deltas are calculated only when `start()` is called on a tween. So let's suppose you wanted to use a custom easing function that eased the values but applied a Math.floor to the output, so only the integer part would be returned, resulting in a sort of step-ladder output: ```javascript function tenStepEasing(k) { return Math.floor(k * 10) / 10 } ``` And you could use it in a tween by simply calling its easing method, as we've seen before: ```javascript tween.easing(tenStepEasing) ``` Check the [graphs for custom easing functions](../examples/12_graphs_custom_functions.html) example to see this in action (and also some _metaprogramming_ for generating step functions). ## Callbacks Another powerful feature is to be able to run your own functions at specific times in each tween's life cycle. This is usually required when changing properties is not enough. For example, suppose you're trying to animate some object whose properties can't be accessed directly but require you to call a setter instead. You can use an `update` callback to read the new updated values and then manually call the setters. All callbacks are passed the tweened object as the only parameter. ```javascript const trickyObjTween = new Tween({ propertyA: trickyObj.getPropertyA(), propertyB: trickyObj.getPropertyB(), }) .to({propertyA: 100, propertyB: 200}) .onUpdate(function (object) { object.setA(object.propertyA) object.setB(object.propertyB) }) ``` Or imagine you want to play a sound when a tween is started. You can use a `start` callback: ```javascript const tween = new Tween(obj).to({x: 100}).onStart(function () { sound.play() }) ``` The scope for each callback is the tweened object--in this case, `obj`. ### onStart Executed right before the tween starts animating, after any delay time specified by the `delay` method. This will be executed only once per tween, i.e. it will _not_ be run when the tween is repeated via `repeat()`. It is great for synchronising to other events or triggering actions you want to happen when a tween starts. The tweened object is passed in as the first parameter. ### onEveryStart As per `onStart`, except that it _will_ be run on every repeat of the tween. The tweened object is passed in as the first parameter. ### onStop Executed when a tween is explicitly stopped via `stop()`, but not when it is completed normally, and before stopping any possible chained tween. The tweened object is passed in as the first parameter. ### onUpdate Executed each time the tween is updated, after the values have been actually updated. The tweened object is passed in as the first parameter. ### onComplete Executed when a tween is finished normally (i.e. not stopped). The tweened object is passed in as the first parameter. ### onRepeat Executed whenever a tween has just finished one repetition and will begin another. The tweened object is passed in as the first parameter. To clarify when `onStart`, `onEveryStart` and `onRepeat` are called, consider: ```javascript const obj = {x: 0} const t = new Tween(obj) .to({x: 5}, 5) .repeat(Infinity) .onStart(() => { console.log('onStart') }) .onRepeat(() => { console.log('onRepeat') }) .onEveryStart(() => { console.log('onEveryStart') }) .start(0) for (let ticks = 0; ticks < 22; ticks += 1) { console.log('Tick', ticks) t.update(ticks) console.log(obj) console.log() } ``` The output would look like this, on the left as above, and on the right with `.delay(5)`: ``` Tick 0 Tick 0 onStart { x: 0 } onEveryStart { x: 0 } Tick 1 Tick 1 { x: 1 } { x: 0 } Tick 2 Tick 2 { x: 2 } { x: 0 } Tick 3 Tick 3 { x: 3 } { x: 0 } Tick 4 Tick 4 { x: 4 } { x: 0 } Tick 5 Tick 5 onRepeat onStart { x: 5 } onEveryStart { x: 0 } Tick 6 Tick 6 onEveryStart { x: 1 } { x: 1 } Tick 7 Tick 7 { x: 2 } { x: 2 } Tick 8 Tick 8 { x: 3 } { x: 3 } Tick 9 Tick 9 { x: 4 } { x: 4 } Tick 10 Tick 10 onRepeat onRepeat { x: 5 } { x: 5 } Tick 11 Tick 11 onEveryStart { x: 5 } { x: 1 } Tick 12 Tick 12 { x: 2 } { x: 5 } Tick 13 Tick 13 { x: 3 } { x: 5 } Tick 14 Tick 14 { x: 4 } { x: 5 } Tick 15 Tick 15 onRepeat onEveryStart { x: 5 } { x: 0 } Tick 16 Tick 16 onEveryStart { x: 1 } { x: 1 } Tick 17 Tick 17 { x: 2 } { x: 2 } Tick 18 Tick 18 { x: 3 } { x: 3 } Tick 19 Tick 19 { x: 4 } { x: 4 } Tick 20 Tick 20 onRepeat onRepeat { x: 5 } { x: 5 } Tick 21 Tick 21 onEveryStart { x: 5 } { x: 1 } ``` ## Tween State ### `isPlaying` `tween.isPlaying` is `true` when a tween is started, even if it is paused. When a tween is stopped, `isPlaying` and `isPaused` will both be `false`, so `!tween.isPlaying()` can be used to detect if a tween is stopped (regardless if the tween has completed). ### `isPaused` `tween.isPaused` is `true` when a tween is paused. `isPlaying` will also be `true`. If a tween is started, but not paused, `isPlaying` will be `true` and `isPaused` will be `false`. ## Advanced tweening ### Relative values You can also use relative values when using the `to` method. When the tween is started, Tween.js will read the current property values and apply the relative values to find out the new final values. But **you need to use quotes** or the values will be taken as absolute. Let's see this with an example: ```javascript // This will make the `x` property be 100, always const absoluteTween = new Tween(absoluteObj).to({x: 100}) // Suppose absoluteObj.x is 0 now absoluteTween.start() // Makes x go to 100 // Suppose absoluteObj.x is -100 now absoluteTween.start() // Makes x go to 100 // In contrast... // This will make the `x` property be 100 units more, // relative to the actual value when it starts const relativeTween = new Tween(relativeObj).to({x: '+100'}) // Suppose relativeObj.x is 0 now relativeTween.start() // Makes x go to 0 +100 = 100 // Suppose relativeObj.x is -100 now relativeTween.start() // Makes x go to -100 +100 = 0 ``` Check [09_relative_values](../examples/09_relative_values.html) for an example. ### Tweening nested objects Tween.js can also change properties across nested objects. For example: ```javascript const nestedObject = {scale: {x: 0, y: 0}, alpha: 0} const tween = new Tween(nestedObject).to({scale: {x: 100, y: 100}, alpha: 1}) ``` ### Tweening to arrays of values In addition to tweening to an absolute or a relative value, you can also have Tween.js change properties across a series of values. To do this, you just need to specify an array of values instead of a single value for a property. For example: ```javascript const tween = new Tween(relativeObj).to({x: [0, -100, 100]}) ``` will make `x` go from its initial value to 0, -100 and 100. The way these values are calculated is as follows: - first the tween progress is calculated as usual - the progress (from 0 to 1) is used as input for the interpolation function - based on the progress and the array of values, an interpolated value is generated For example, when the tween has just started (progress is 0), the interpolation function will return the first value in the array. When the tween is halfway, the interpolation function will return a value approximately in the middle of the array, and when the tween is at the end, the interpolation function will return the last value. You can change the interpolation mode by passing an `Interpolation` function into the `tween.interpolation` method. For example: ```javascript import {Interpolation} from '@tweenjs/tween.js' // ... tween.interpolation(Interpolation.Bezier) ``` The following values are available: - `Interpolation.Linear` - `Interpolation.Bezier` - `Interpolation.CatmullRom` The default is `Linear`. Note that the interpolation function is global to all properties that are tweened with arrays in the same tween. You can't make property A change with an array and a Linear function, and property B with an array too and a Bezier function using the same tween; you should use two tween objects running over the same object but modifying different properties and using different interpolation functions. Check [06_array_interpolation](../examples/06_array_interpolation.html) for an example. ## Changing the Definition of "Now" When working with tweening, you inevitably rely on a definition of what "now" is. By default, Tween.js uses performance.now, which is a reliable and precise approach. However, if you need to adjust the flow of time—for instance, to slow it down or manipulate it for a custom purpose—you may encounter discrepancies between your internal definition of "now" and what Tween.js considers "now." To address this, a new function, setNow, has been introduced. This function allows you to redefine the internal "now" used by Tween.js. You can pass a custom function to setNow, which will replace the default definition. This provides greater flexibility and enables synchronization with your specific requirements for time control. ## Getting the best performance While Tween.js tries to be performant on its own, nothing prevents you from using it in a way that is counterperformant. Here are some of the ways you can avoid slowing down your projects when using Tween.js (or when animating in the web, in general). ### Use performant CSS When you try to animate the position of an element in the page, the easiest solution is to animate the `top` and `left` style properties, like this: ```javascript const element = document.getElementById('myElement') const tween = new Tween({top: 0, left: 0}).to({top: 100, left: 100}, 1000).onUpdate(function (object) { element.style.top = object.top + 'px' element.style.left = object.left + 'px' }) ``` but this is really inefficient because altering these properties forces the browser to recalculate the layout on each update, and this is a very costly operation. Instead of using these, you should use `transform`, which doesn't invalidate the layout and will also be hardware accelerated when possible, like this: ```javascript const element = document.getElementById('myElement') const tween = new Tween({top: 0, left: 0}).to({top: 100, left: 100}, 1000).onUpdate(function (object) { element.style.transform = 'translate(' + object.left + 'px, ' + object.top + 'px)' }) ``` If you want to read more about this, have a look at [this article](http://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/). However, if your animation needs are _that_ simple, it might be better to just use CSS animations or transitions, where applicable, so that the browser can optimise as much as possible. Tween.js is most useful when your animation needs involve complex arrangements, i.e. you need to sync several tweens together, have some start after one has finished, loop them a number of times, have graphics that are not rendered with CSS but with Canvas or WebGL, etc. ### Be good to the Garbage collector (alias the GC) If you use an `onUpdate` callback, you need to be very careful with what you put on it. This function will be called many times per second, so if you're doing costly operations on each update, you might block the main thread and cause horrible _jank_, or---if your operations involve memory allocations, you'll end up getting the garbage collector to run too often, and cause _jank_ too. So just don't do either of those things. Keep your `onUpdate` callbacks very lightweight, and be sure to also use a memory profiler while you're developing. ## Crazy tweening This is something you might not use often, but you can use the tweening equations outside of Tween.js. They're just functions, after all. So you could use them to calculate smooth curves as input data. For example, they're used to generate audio data in [this experiment](http://5013.es/toys/tween.audio/). ================================================ FILE: docs/user_guide_zh-CN.md ================================================ ## tween.js 用户指南 更多语言: [English](./user_guide.md), [简体中文](./user_guide_zh-CN.md) 中文用户指南最近更新为 Tween.js v20.0.3 _**NOTE** 这是一个正在进行的工作。 如果你发现某些内容不清楚或缺少详细信息,请 [提出 issue](https://github.com/tweenjs/tween.js/issues/new) 并帮助改进本指南。 或者,如果你觉得自己也能提供帮助,请随时提交你自己的说明或改进!_ ## 什么是 tween ?tween 是如何工作的?为什么要使用 tween ? 补间(动画)(来自 [in-between](https://en.wikipedia.org/wiki/Inbetweening))是一个概念,允许你以平滑的方式更改对象的属性。你只需告诉它哪些属性要更改,当补间结束运行时它们应该具有哪些最终值,以及这需要多长时间,补间引擎将负责计算从起始点到结束点的值。 例如,`position` 对象拥有 `x` 和 `y` 两个坐标: ```js const position = {x: 100, y: 0} ``` 如果你想将 `x` 坐标的值从 `100` 变成 `200` ,你应该这么做: ```js // 首先为位置创建一个补间(tween) const tween = new TWEEN.Tween(position) // 然后告诉 tween 我们想要在1000毫秒内以动画的形式移动 x 的位置 tween.to({x: 200}, 1000) ``` 通常来说这样还不够,tween 已经被创建了,但是它还没被激活(使用),你需要这样启动: ```js // 启动 tween.start() ``` 最后,想要成功的完成这种效果,你需要在主函数中调用 `TWEEN.update` ,如下使用: ```js animate() function animate() { requestAnimationFrame(animate) // [...] TWEEN.update() // [...] } ``` 这样在更新每帧的时候都会运行补间动画;经过 1 秒(1000 毫秒)后 `position.x` 将会变成 `200`。 除非你在控制台中打印出 `x` 的值,不然你看不到它的变化。你可能想要使用 `onUpdate` 回调: ```js tween.onUpdate(function (object) { console.log(object.x) }) ``` 每次更新补间时都会调用此函数; 这种情况发生的频率取决于许多因素——例如,你的计算机或设备的速度有多快(以及有多繁忙!)。 到目前为止,我们只使用 tweens 将值打印到控制台,但你可以将它用于 three.js 对象的动画位置之类的事情: ```js const tween = new TWEEN.Tween(cube.position).to({x: 100, y: 100, z: 100}, 10000).start() animate() function animate() { requestAnimationFrame(animate) TWEEN.update() threeRenderer.render(scene, camera) } ``` 在这种情况下,因为 three.js 渲染器将在渲染之前查看对象的位置,所以不需要使用的 `onUpdate` 回调。 你可能也注意到了一些不同的地方:tween.js 可以链式调用! 每个 tween 函数都会返回 tween 实例,所以你可以重写下面的代码: ```js const tween = new TWEEN.Tween(position) tween.to({x: 200}, 1000) tween.start() ``` 改成这样: ```js const tween = new TWEEN.Tween(position).to({x: 200}, 1000).start() ``` 在将会看到很多例子,所以熟悉它是很好的!比如 [04-simplest](../examples/04_simplest.html) 这个例子。 ## tween.js 的动画 Tween.js 不会自行运行。你需要显式的调用 `update` 方法来告诉它何时运行。推荐的方法是在主动画循环中执行这个操作。使用 `requestAnimationFrame` 调用此循环以获得最佳的图形性能。 比如之前这个例子: ```js animate() function animate() { requestAnimationFrame(animate) // [...] TWEEN.update() // [...] } ``` 如果不带参数调用,`update` 将会判断当前时间点,以便找出自上次运行以来已经过了多长时间。 当然你也可以传递一个明确的时间参数给 `update` 来更新。因此: ```js TWEEN.update(100) ``` 意思是“更新时间 = 100 毫秒”。你可以使用它来确保代码中的所有时间相关函数都使用相同的时间值。例如,假设你有一个播放器,并希望同步运行补间。 你的 `animate` 函数可能看起来像这样: ```js const currentTime = player.currentTime TWEEN.update(currentTime) ``` 我们使用明确的时间值进行单元测试。你可以看下 [tests.ts](../src/tests.ts) 这个例子,看看我们如何用不同的值调用`TWEEN.update()` 来模拟时间传递。 ## 控制一个补间 ### `start` 和 `stop` 到目前为止,我们已经了解了 `Tween.start` 方法,但是还有更多的方法来控制单个补间。也许最重要的一个是 `start` 对应的方法:`stop` 。 如果你想取消一个补间,只要调用这个方法通过一个单独的补间: ```js tween.stop() ``` 停止一个从未开始或已经停止的补间没有任何效果。 没有错误被抛出。 `start` 方法还接受一个 `time` 参数。如果你使用它,补间直到那个特定的时刻才会开始;否则它将立即开始(即在下一次调用 `TWEEN.update` 时)。 `start` 方法接受第二个布尔参数:当为 `true` 时,我们之前使用的补间将从目标对象中的值开始,而不是从头开始。用于停止补间,然后启动另一个将从当前位置继续的补间。 ### `startFromCurrentValues` 这是 `tween.start(undefined, true)` 的别名,使以前使用的补间从目标对象的最后一个值开始,而不是从头开始。 ### `update` 个别补间有一个 `update` 方法。 这实际上是由 `TWEEN.update` 调用的,用于仅使用一个参数构建的补间。 在下面的示例中,第二个参数告诉新的 Tween 不要将自己添加到默认组(`TWEEN` 是 `TWEEN.Group` 的一个实例)。如果补间不与组关联(请注意,可以通过将组作为第二个参数传递给构造函数来关联组),则补间需要使用其 `update` 方法手动更新,如下所示: ```js const tween = new TWEEN.Tween(someObject, false).to(/*...*/).start() function animate(time) { tween.update(time) requestAnimationFrame(animate) } ``` > **Note** 如果你使用 `TWEEN.update()` 作为默认控制所有补间的方式,则无需直接调用 `tween.update()`,但是我们建议你直接如上例所示 [创建自己的补间组](#controlling-groups-of-tweens) 或手动更新补间。 使用组或单独控制的补间的概念很像避免在 JavaScript 代码中使用全局变量的做法:它可以防止一个组件意外破坏其他一些不相关组件的行为。 ### `chain` 当你按顺序排列不同的补间时,事情会变得更有趣,例如在上一个补间结束的时候立即启动另外一个补间。我们称此为链接补间,它是通过 `chain` 方法完成的。因此,要使 `tweenB` 在 `tweenA` 完成后开始: ```js tweenA.chain(tweenB) ``` 或者,可以创造一个无限的链式,`tweenA` 完成时开始 `tweenB`,`tweenB` 完成时开始 `tweenA`: ```js tweenA.chain(tweenB) tweenB.chain(tweenA) ``` 关于无限的链式查看案例 [Hello world](../examples/00_hello_world.html) 。 在其他情况下,你可能希望将多个补间链接到另一个补间,使它们(链接的补间)都同时开始动画: ```js tweenA.chain(tweenB, tweenC) ``` > **Warning** 调用 `tweenA.chain(tweenB)` 实际上修改了 tweenA,所以 tweenB 总是在 tweenA 完成时启动。 `chain` 的返回值只是 tweenA,不是一个新的 tween。 ### `repeat` 如果你想让一个补间永远重复,你可以链接到自己,但更好的方法是使用 `repeat` 方法。它接受一个参数,描述第一个补间完成后需要多少次重复: ```js tween.repeat(10) // 循环10次 tween.repeat(Infinity) // 无限循环 ``` 补间的总次数将是重复参数加上一个初始补间。查看 [Repeat](../examples/08_repeat.html) 。 ### `yoyo` 此功能仅在与 `repeat` 一起使用时才有效。 激活时,补间的行为将 _像溜溜球一样_,即它会在开始值和结束值之间来回跳动,而不是仅仅从头开始重复相同的顺序: ```js tween.yoyo(false) // 默认值,动画只会从开始到结束值 tween.yoyo(true) // tween 将在起始值和结束值之间“yoyo” ``` ### `delay` 更复杂的安排可能需要在实际开始运行之前延迟补间。 你可以使用 `delay` 方法来做到这一点: ```js tween.delay(1000) tween.start() ``` 将在调用 `start` 方法后的 1 秒钟后开始执行。 ### `repeatDelay` 通常,`delay` 时间应用于补间的重复之间,但如果向 `repeatDelay` 函数提供了一个值,则该值将确定补间重复之间经过的总时间。 参考这个例子: ```js tween.delay(1000) tween.repeatDelay(500) tween.start() ``` 补间的第一次迭代将在一秒后发生,第二次迭代将在第一次迭代结束后半秒发生,第三次迭代将在第二次迭代结束后半秒发生,依此类推。如果你想延迟初始迭代但不希望迭代之间有任何延迟,请确保调用 `tween.repeatDelay(0)` 。 ### `dynamic` 如果 `dynamic` 设置为 `true`(默认为 `false`),则传递给 `tween.to()` 的对象可以在补间动画的外部进行修改。这可用于在运行时动态修改补间的结果。 请参阅 [dynamic 示例](http://tweenjs.github.io/tween.js/examples/07_dynamic_to.html)。在那个例子中,在这两个场景中,兔子的位置都在动画期间更新。 兔子的位置恰好是传递给狐狸的 `tween.to()` 方法的对象。 随着兔子位置的更新,在第一个带有 `.dynamic(false)` 的场景中,狐狸向兔子的初始位置移动并且不跟随兔子,而在第二个带有 `.dynamic(true)` 的场景中,狐狸移动到兔子的最终目的地 狐狸因此也被更新,这使得狐狸跟随兔子。 > **Warning** 当 `dynamic` 设置为 `false` 时,Tween 复制传递给 `tween.to()` 的对象并且永远不会修改它(因此从外部更新原始对象不是动态的)。当 `dynamic` 为 `true` 时,Tween 在动画期间使用原始对象作为值的来源(每次更新都读取值,因此可以动态修改它们) **但请注意,在 dynamic 模式下,Tween 将修改传递给 `tween.to()` 的对象的任何插值数组,这可能会对也可能依赖于同一对象的任何外部代码造成副作用。** ## 控制*所有*补间 在 TWEEN 全局对象中可以找到以下方法,除了 `update` 之外,你通常不需要使用其中的大部分方法。 ### `TWEEN.update(time)` 我们已经讨论过这种方法。它用于更新所有活动的补间。 如果 `time` 不指定,它将使用当前时间。 ### `TWEEN.getAll` and `TWEEN.removeAll` 用于获取对活动 `tweens` 数组的引用,并分别通过一次调用将它们从数组中移除。 ### `TWEEN.add(tween)` and `TWEEN.remove(tween)` 分别用于将补间添加到活动补间列表,或从列表中删除特定补间。 这些方法通常只在内部使用,但如果你想做一些*有趣*的事情,我们会公开这些方法。 ## 控制补间集 使用 `TWEEN` 单例来管理补间可能会导致包含许多组件的大型应用程序出现问题。在这些情况下,你可能想要创建自己的更小的补间集。 ### 示例:交叉组件冲突 如果你有多个组件使用 `TWEEN`,并且每个组件都想管理自己的补间集,则可能会发生冲突。 如果一个组件调用 `TWEEN.update()` 或 `TWEEN.removeAll()`,其他组件的补间也将被更新或删除。 ### 创建你自己的补间集 为了解决这个问题,每个组件都可以创建自己的 `TWEEN.Group` 实例(这是全局 `TWEEN` 对象在内部使用的实例)。 在实例化新补间时,这些组可以作为第二个可选参数传入: ```js const groupA = new TWEEN.Group() const groupB = new TWEEN.Group() const tweenA = new TWEEN.Tween({x: 1}, groupA).to({x: 10}, 100).start() const tweenB = new TWEEN.Tween({x: 1}, groupB).to({x: 10}, 100).start() const tweenC = new TWEEN.Tween({x: 1}).to({x: 10}, 100).start() groupA.update() // 只更新tweenA groupB.update() // 只更新tweenB TWEEN.update() // 只更新tweenC groupA.removeAll() // 只移除tweenA groupB.removeAll() // 只移除tweenB TWEEN.removeAll() // 只移除tweenC ``` 这样,每个组件都可以处理创建、更新和销毁自己的补间集。 ## 改变缓动功能(别名:让它弹) Tween.js 将以线性方式执行值之间的插值(即缓动),因此变化将与经过的时间成正比。 这是可以预见的,但在视觉上也很无趣。 不用担心——可以使用缓动方法轻松更改此行为。 例如: ```js tween.easing(TWEEN.Easing.Quadratic.In) ``` 这将导致补间缓慢开始向最终值变化,向中间加速,然后快速达到其最终值。 相比之下,`TWEEN.Easing.Quadratic.Out` 将开始向该值快速变化,但在接近最终值时会减慢速度。 ### 可用的缓动函数:`TWEEN.Easing` tween.js 提供了一些现成的缓动功能。它们按照它们表示的方程类型进行分组:线性、二次、三次、四次、五次、正弦、指数、圆形、弹性、后退和反弹,然后按缓动类型:In、Out 和 InOut。 除非你已经熟悉这些概念,否则这些名称可能对你没有任何意义,所以现在可能是查看 [图表示例](../examples/17_generate_pow.html) 的时候了,该示例在一页中绘制了所有曲线,以便你可以比较它们的动画。 这些函数源自 Robert Penner 几年前慷慨地作为免费软件提供的原始方程,但已经过优化后,可以很好地与 JavaScript 配合使用。 ### 使用自定义缓动功能 你不仅可以使用任何现有的缓动函数,还可以提供自己的函数,只要它遵循一些约定即可: - 它必须接受一个参数: - `k`: 缓动过程,或我们的补间所处的时间有多长。允许的值在[0, 1]的范围内。 - 它必须根据输入参数返回一个值。 无论要更改多少属性,每次更新时每个补间只调用一次缓动函数。 然后将结果与初始值以及此值和最终值之间的差值(_deltas_)一起使用,如以下伪代码所示: ```js easedElapsed = easing(k); for each property: newPropertyValue = initialPropertyValue + propertyDelta * easedElapsed; ``` 对于更注重性能表现的人来说:只有在补间上调用 `start()` 时才会计算 deltas。 因此,假设你想要使用自定义缓动函数来缓动值但将 `Math.floor` 应用于输出,因此只会返回整数部分,从而产生一种阶梯式输出: ```js function tenStepEasing(k) { return Math.floor(k * 10) / 10 } ``` 你可以通过简单地调用它的缓动方法在补间中使用它,就像我们之前看到的那样: ```js tween.easing(tenStepEasing) ``` 查看 [graphs for custom easing functions](../examples/12_graphs_custom_functions.html) 示例,以查看这个动作(还有一些用于生成步进函数的 _metaprogramming_ )。 ## 回调函数 另一个强大的特性是能够在每个补间的生命周期的特定时间运行自己的功能。 当更改属性不够时,通常需要这样做。 例如,假设你正在试图给一些不能直接访问属性的对象设置动画,但是需要你调用 setter。 你可以使用 `update` 回调来读取新的更新值,然后手动调用 setters。 所有的回调函数都将补间对象作为唯一的参数。 ```js const trickyObjTween = new TWEEN.Tween({ propertyA: trickyObj.getPropertyA(), propertyB: trickyObj.getPropertyB(), }) .to({propertyA: 100, propertyB: 200}) .onUpdate(function (object) { object.setA(object.propertyA) object.setB(object.propertyB) }) ``` 或者假设你想在开始补间时播放声音。 你可以使用 `start` 回调: ```js const tween = new TWEEN.Tween(obj).to({x: 100}).onStart(function () { sound.play() }) ``` 每个回调的范围是补间对象——在本例中为 `obj`。 ### onStart 在补间开始动画之前执行,在 `delay` 方法指定的任何延迟时间之后。 每个补间只会执行一次,即当补间通过 `repeat()` 重复时不会运行。 `onStart` 非常适合与其他事件同步,或触发需要在补间开始时执行的操作。 补间对象作为第一个参数传入。 ### onEveryStart 和 `onStart` 类似,单 `onEveryStart` 在每次重复补间时也会运行。 补间对象作为第一个参数传入。 ### onStop 当补间通过 `stop()` 显式停止时执行,但不会在它正常完成时执行,并且在停止任何可能的链接补间之前执行。 补间对象作为第一个参数传入。 ### onUpdate 每次更新补间时执行,在实际更新值之后。 补间对象作为第一个参数传入。 ### onComplete 当补间正常完成(即未停止)时执行。 补间对象作为第一个参数传入。 ### onRepeat 每当补间刚刚完成一个重复并将开始另一个重复时执行。 补间对象作为第一个参数传入。 要阐明何时调用 `onStart`、`onEveryStart` 和 `onRepeat`,请参考: ```js const obj = {x: 0} const t = new TWEEN.Tween(obj) .to({x: 5}, 5) .repeat(Infinity) .onStart(() => { console.log('onStart') }) .onRepeat(() => { console.log('onRepeat') }) .onEveryStart(() => { console.log('onEveryStart') }) .start(0) for (let ticks = 0; ticks < 22; ticks += 1) { console.log('Tick', ticks) TWEEN.update(ticks) console.log(obj) console.log() } ``` 输出如下所示,左侧如上,右侧带有 `.delay(5)`: ```txt Tick 0 Tick 0 onStart { x: 0 } onEveryStart { x: 0 } Tick 1 Tick 1 { x: 1 } { x: 0 } Tick 2 Tick 2 { x: 2 } { x: 0 } Tick 3 Tick 3 { x: 3 } { x: 0 } Tick 4 Tick 4 { x: 4 } { x: 0 } Tick 5 Tick 5 onRepeat onStart { x: 5 } onEveryStart { x: 0 } Tick 6 Tick 6 onEveryStart { x: 1 } { x: 1 } Tick 7 Tick 7 { x: 2 } { x: 2 } Tick 8 Tick 8 { x: 3 } { x: 3 } Tick 9 Tick 9 { x: 4 } { x: 4 } Tick 10 Tick 10 onRepeat onRepeat { x: 5 } { x: 5 } Tick 11 Tick 11 onEveryStart { x: 5 } { x: 1 } Tick 12 Tick 12 { x: 2 } { x: 5 } Tick 13 Tick 13 { x: 3 } { x: 5 } Tick 14 Tick 14 { x: 4 } { x: 5 } Tick 15 Tick 15 onRepeat onEveryStart { x: 5 } { x: 0 } Tick 16 Tick 16 onEveryStart { x: 1 } { x: 1 } Tick 17 Tick 17 { x: 2 } { x: 2 } Tick 18 Tick 18 { x: 3 } { x: 3 } Tick 19 Tick 19 { x: 4 } { x: 4 } Tick 20 Tick 20 onRepeat onRepeat { x: 5 } { x: 5 } Tick 21 Tick 21 onEveryStart { x: 5 } { x: 1 } ``` ## 补间状态 ### `isPlaying` 开始时为 `true`(即使是暂停)。 当补间停止时,`isPlaying` 和 `isPaused` 都将为 `false`。 ### `isPaused` 暂停时为 `true`。 `isPlaying` 也将为 `true`。 如果补间已启动但未暂停,则 `isPlaying` 将为 `true` 而 `isPaused` 将为 `false`。 ## 高级补间 ### 相对值 使用 `to` 方法时,也可以使用相对值。 当 tween 启动时,Tween.js 将读取当前属性值并应用相对值来找出新的最终值。 **但是你需要使用引号**,否则这些值将被视为绝对的。 我们来看一个例子: ```js // 这将使 `x` 属性始终为 100 const absoluteTween = new TWEEN.Tween(absoluteObj).to({x: 100}) // 假设 absoluteObj.x 现在为 0 absoluteTween.start() // 使 x 变为 100 // 假设 absoluteObj.x 现在是 -100 absoluteTween.start() // 使 x 变为 100 // 相比之下... // 这将使 `x` 属性相对于开始时的实际值多 100 个单位 const relativeTween = new TWEEN.Tween(relativeObj).to({x: '+100'}) // 假设 relativeObj.x 现在是 0 relativeTween.start() // 使 x 变为 0 +100 = 100 // 假设 relativeObj.x 现在是 -100 relativeTween.start() // 使 x 变为 -100 +100 = 0 ``` 查看 [09_relative_values](../examples/09_relative_values.html) 示例。 ### 补间嵌套对象 Tween.js 还可以跨嵌套对象更改属性。 例如: ```js const nestedObject = {scale: {x: 0, y: 0}, alpha: 0} const tween = new TWEEN.Tween(nestedObject).to({scale: {x: 100, y: 100}, alpha: 1}) ``` ### 补间值的数组 除了补间到绝对值或相对值之外,你还可以让 Tween.js 更改一系列值的属性。 为此,你只需为属性指定一个值数组而不是单个值。 例如: ```js const tween = new TWEEN.Tween(relativeObj).to({x: [0, -100, 100]}) ``` 将使 `x` 从初始值变为 0,-100 和 100。 这些值的计算方法如下: - 首先,补间进度如常计算 - 进度(从 0 到 1)用作插值函数的输入 - 基于进度和值的数组,生成内插值 例如,当补间刚刚启动(进度为 0)时,插值函数将返回数组中的第一个值。 当补间到一半时,插值函数将返回一个大约在数组中间的值,当补间结束时,插值函数将返回最后一个值。 你可以使用插值方法更改插值函数。 例如: ```js tween.interpolation(TWEEN.Interpolation.Bezier) ``` 以下值可用: - TWEEN.Interpolation.Linear - TWEEN.Interpolation.Bezier - TWEEN.Interpolation.CatmullRom 默认是 `Linear`。 请注意,插值函数对于在同一补间中与数组补间的所有属性都是全局的。 你不能使用数组和线性函数更改属性 A,也不能使用数组和使用相同补间的贝塞尔函数更改属性 B; 你应该使用两个运行在同一对象上但修改不同属性并使用不同插值函数的补间对象。 查看 [06_array_interpolation](../examples/06_array_interpolation.html) 示例。 ## 获得最佳性能 虽然 Tween.js 试图靠自己发挥性能,但没有什么能阻止你以反性能的方式使用它。 以下是一些在使用 Tween.js 时(或通常在 Web 中制作动画时)可以避免拖慢项目速度的方法。 ### 使用高性能的 CSS 当你尝试为页面中元素的位置设置动画时,最简单的解决方案是为 `top` 和 `left` 样式属性设置动画,如下所示: ```js const element = document.getElementById('myElement') const tween = new TWEEN.Tween({top: 0, left: 0}).to({top: 100, left: 100}, 1000).onUpdate(function (object) { element.style.top = object.top + 'px' element.style.left = object.left + 'px' }) ``` 但这确实效率低下,因为更改这些属性会强制浏览器在每次更新时重新计算布局,这是一项非常消耗性能的操作。你应该使用 `transform`,它不会使布局无效,并且在可能的情况下也会进行硬件加速,如下所示: ```js const element = document.getElementById('myElement') const tween = new TWEEN.Tween({top: 0, left: 0}).to({top: 100, left: 100}, 1000).onUpdate(function (object) { element.style.transform = 'translate(' + object.left + 'px, ' + object.top + 'px);' }) ``` 如果你想了解更多关于高性能的 CSS,看看[这篇文章](https://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/)。 但是,如果你的动画需求就这么简单,最好只使用 CSS 动画或过渡(在适用的情况下),这样浏览器就可以尽可能地进行优化。 当你的动画需要涉及复杂的操作时,Tween.js 是非常有用,也就是说,你需要将多个补间同步在一起,在一个完成后开始,循环多次,具有不是使用 CSS 而是使用 Canvas 渲染的图形或 WebGL 等等。 ### 对垃圾收集器(别名 GC) 如果你使用 `onUpdate` 回调,你需要非常小心你放在它上面的东西。 这个函数每秒会被调用很多次,所以如果你在每次更新时都做代价高昂的操作,你可能会阻塞主线程并导致可怕的卡顿,或者——如果你的操作涉及内存分配,你最终会得到 垃圾收集器运行过于频繁,也会导致卡顿。 所以不要做这两件事。 保持你的 `onUpdate` 回调非常轻量级,并确保在开发时也使用内存分析器。 ### 疯狂的补间 这是你可能不经常使用的东西,但你可以在 Tween.js 之外使用补间方程式。 毕竟,它们只是函数。 因此,你可以使用它们来计算平滑曲线作为输入数据。 例如,它们用于在[此实验](<(http://5013.es/toys/tween.audio/)>)中生成音频数据。 ================================================ FILE: examples/00_hello_world.html ================================================ Tween.js / hello world!

tween.js

00 _ hello world

Simple example for illustrating the creation and chaining of tweens.

hello world!
================================================ FILE: examples/01_bars.html ================================================ Tween.js / bars

tween.js

01 _ Bars

1000 bars, moving horizontally, looped.

================================================ FILE: examples/02_black_and_red.html ================================================ Tween.js / Black and Red

tween.js

02 _ Black and red

4096 continuously changing color cells (in a 64x64 table)

================================================ FILE: examples/03_graphs.html ================================================ Tween.js / graphs

tween.js

03 _ graphs

The curves, visualised.

================================================ FILE: examples/04_simplest.html ================================================ Tween.js / simplest possible example!

tween.js

04 _ simplest possible example

Creating a tween and doing little else apart from that :)

================================================ FILE: examples/05_video_and_time.html ================================================ Tween.js / video and time

tween.js

05 _ video and time

Playing a video, synchronizing a tween to it.

================================================ FILE: examples/06_array_interpolation.html ================================================ Tween.js / array interpolation

tween.js

06 _ array interpolation

The different interpolations if arrays are used as values.

================================================ FILE: examples/07_dynamic_to.html ================================================ Tween.js / dynamic to object

tween.js

07 _ dynamic to object

tweening towards a moving target

.dynamic(false)

.dynamic(true)

================================================ FILE: examples/07a_dynamic_to_two_array_values.html ================================================ Tween.js / dynamic to interpolation array

tween.js

07a _ dynamic to interpolation array

tweening towards two moving targets

.dynamic(false)

.dynamic(true)

================================================ FILE: examples/07b_dynamic_to_an_array_of_values.html ================================================ Tween.js / dynamic to large interpolation array

tween.js

07b _ dynamic to large interpolation array

tweening towards many random moving targets

.dynamic(false)

.dynamic(true)

================================================ FILE: examples/08_repeat.html ================================================ Tween.js / repeat

tween.js

08 _ repeat

Demonstrating the repeat() feature.

no repeat
repeat twice
repeat forever
================================================ FILE: examples/09_relative_values.html ================================================ Tween.js / relative values

tween.js

09 _ relative values

Tweening to relative values, with repeat.

================================================ FILE: examples/09_relative_values.js ================================================ import {Tween, Group, Easing} from '@tweenjs/tween.js' const target1 = document.getElementById('target1') const tween = new Tween(target1.dataset) .to({top: '+20', left: '-20'}, 500) .repeat(5) .delay(500) .easing(Easing.Exponential.In) .onUpdate(function (object) { object.top = Math.round(object.top) object.left = Math.round(object.left) updateBox(target1, object) }) .start() updateBox(target1, target1.dataset) animate() function animate(time) { requestAnimationFrame(animate) tween.update(time) } function updateBox(box, params) { const s = box.style const transform = 'translate(' + params.left + 'px, ' + params.top + 'px)' s.transform = transform } ================================================ FILE: examples/10_yoyo.html ================================================ Tween.js / yoyo

tween.js

10 _ yoyo

Demonstrating the yoyo() feature.

yoyo with repeat once
yoyo with repeat forever
yoyo with repeat once, relative values
yoyo with repeat forever, relative values
================================================ FILE: examples/10_yoyo.js ================================================ // @ts-check import {Tween, Group, Easing} from 'tweenjs' const group = new Group() animate() const target1 = document.getElementById('target1') const tween1 = new Tween(target1.dataset, group) .to({rotation: 360, y: 300}, 750) .repeat(1) .delay(1000) .yoyo(true) .easing(Easing.Cubic.InOut) .onUpdate(function (object) { updateBox(target1, object) }) .start() const target2 = document.getElementById('target2') const tween2 = new Tween(target2.dataset, group) .to({rotation: 360, y: 300}, 750) .repeat(Infinity) .delay(1000) .yoyo(true) .easing(Easing.Cubic.InOut) .onUpdate(function (object) { updateBox(target2, object) }) .start() const target3 = document.getElementById('target3') const tween3 = new Tween(target3.dataset, group) .to({rotation: '+360', y: '+300'}, 750) .repeat(1) .delay(1000) .yoyo(true) .easing(Easing.Cubic.InOut) .onUpdate(function (object) { updateBox(target3, object) }) .start() const target4 = document.getElementById('target4') const tween4 = new Tween(target4.dataset, group) .to({rotation: '+360', y: '+300'}, 750) .repeat(Infinity) .delay(1000) .yoyo(true) .easing(Easing.Cubic.InOut) .onUpdate(function (object) { updateBox(target4, object) }) .start() // TODO perhaps add these methods to Group const restart = (window.restart = function () { tween1.stop().start() tween2.stop().start() tween3.stop().start() tween4.stop().start() }) const stop = (window.stop = function () { tween1.stop() tween2.stop() tween3.stop() tween4.stop() }) const start = (window.start = function () { tween1.start() tween2.start() tween3.start() tween4.start() }) const pause = (window.pause = function () { tween1.pause() tween2.pause() tween3.pause() tween4.pause() }) const resume = (window.resume = function () { tween1.resume() tween2.resume() tween3.resume() tween4.resume() }) function animate(time) { requestAnimationFrame(animate) group.update(time) } function updateBox(box, params) { const s = box.style, transform = 'translateY(' + Math.round(params.y) + 'px) rotate(' + Math.floor(params.rotation) + 'deg)' s.webkitTransform = transform s.mozTransform = transform s.transform = transform } ================================================ FILE: examples/11_stop_all_chained_tweens.html ================================================ Tween.js / stop chained

tween.js

11 _ stop all chained tweens.

Tween#stopChainedTweens

Box One
Box Two
================================================ FILE: examples/12_graphs_custom_functions.html ================================================ Tween.js / graphs for custom easing functions

tween.js

12 _ graphs for custom easing functions

A version of the graphs example, but using custom easing functions (not included in tween.js by default).

================================================ FILE: examples/13_relative_start_time.html ================================================ Tween.js / relative start time

tween.js

13 _ relative start time


One starts late, Two starts early.

One
Two
================================================ FILE: examples/14_pause_tween.html ================================================ Tween.js / pause tween

tween.js

14 _ pause tween


, then .

0%
================================================ FILE: examples/15_complex_properties.html ================================================ Tween.js / complex properties

tween.js

15 _ complex properties

Simple example for illustrating animation of nested properties.

hello world!
================================================ FILE: examples/16_animate_an_array_of_values.html ================================================ Tween.js / animate an array of values

tween.js

16 _ animate an array of values

Animate multiple objects with a single tween using an array of values.

one
two
three
================================================ FILE: examples/17_generate_pow.html ================================================ Tween.js / easing with a power of number

tween.js

17 _ Easing with Pow

TWEEN.Easing.generatePow() provides easing with a power of number.

================================================ FILE: examples/18_start_from_current_values.html ================================================ Tween.js / hello world!

tween.js

18 _ start from current values

Example for illustrating the startFromCurrentValues() functionality.

================================================ FILE: examples/19_end_tween.html ================================================
================================================ FILE: examples/css/style.css ================================================ body { background: #fff; font-family: Helvetica, Arial, sans; } a { color: #333; } h2 { font-weight: normal; } button { cursor: pointer; color: #333; background: #fff; border: 2px solid #333; padding: 10px; border-radius: 10px; font-weight: bold; font-size: 20px; transition: all 150ms ease-out; } button:hover { background: #333; color: #fff; } #info { position: absolute; top: 0; left: 0; padding: 1.5em 2em; } #info h1 { font-size: 3em; color: #333; margin-top: 0; letter-spacing: -0.05em; } #info h2 { font-size: 2.5em; text-transform: uppercase; color: #666; margin-top: 0; } #info p { font-size: 2em; line-height: 1em; color: #aaa; max-width: 10em; } ================================================ FILE: examples/example-projects/nodejs-commonjs/README.md ================================================ This example shows that Tween.js can be imported into a Node.js project in CommonJS format. ================================================ FILE: examples/example-projects/nodejs-commonjs/index.js ================================================ // Ensure we can import into Node CommonJS: const TWEEN = require('@tweenjs/tween.js') console.log(Object.keys(TWEEN)) const tween = new TWEEN.Tween() console.log(Object.keys(tween)) ================================================ FILE: examples/example-projects/nodejs-commonjs/package.json ================================================ { "description": "Node.js project in CommonJS format, importing Tween in CommonJS format.", "type": "commonjs", "scripts": { "start": "node ./index.js" }, "dependencies": { "@tweenjs/tween.js": "file:../../../" } } ================================================ FILE: examples/example-projects/nodejs-esmodules/README.md ================================================ This example shows that Tween.js can be imported into a Node.js project in ES Module format. ================================================ FILE: examples/example-projects/nodejs-esmodules/index.js ================================================ // Ensure we can import into Node ESM: import * as TWEEN from '@tweenjs/tween.js' console.log(Object.keys(TWEEN)) const tween = new TWEEN.Tween() console.log(Object.keys(tween)) ================================================ FILE: examples/example-projects/nodejs-esmodules/package.json ================================================ { "description": "Node.js project in CommonJS format, importing Tween in CommonJS format.", "type": "module", "scripts": { "start": "node ./index.js" }, "dependencies": { "@tweenjs/tween.js": "file:../../../" } } ================================================ FILE: examples/example-projects/plain-javascript-modules/README.md ================================================ This example is written in JavaScript without any build, using JavaScript modules to organize code into separate files and importing from one file to the other using native `import` syntax. To set up, run `npm install`. To run the project, run `npm start`. ================================================ FILE: examples/example-projects/plain-javascript-modules/animate.js ================================================ export function animate(group) { function loop(time) { group.update(time) const stopped = group.allStopped() if (!stopped) requestAnimationFrame(loop) } loop(performance.now()) } ================================================ FILE: examples/example-projects/plain-javascript-modules/index.html ================================================ Tween.js / hello world (JS modules)!

tween.js

00 _ hello world (using native JS modules)

Simple example for illustrating the creation and chaining of tweens.

hello world!
================================================ FILE: examples/example-projects/plain-javascript-modules/index.js ================================================ import * as TWEEN from '@tweenjs/tween.js' // For sake of example, we put animate() in a separate file, and import it into index.js. import {animate} from './animate.js' const group = new TWEEN.Group() const position = {x: 100, y: 100, rotation: 10} const target = document.getElementById('target') const tween = new TWEEN.Tween(position, group) .to({x: 700, y: 200, rotation: 359}, 2000) .delay(1000) .easing(TWEEN.Easing.Elastic.InOut) .onUpdate(update) const tweenBack = new TWEEN.Tween(position, group) .to({x: 100, y: 100, rotation: 10}, 3000) .easing(TWEEN.Easing.Elastic.InOut) .onUpdate(update) tween.chain(tweenBack) tweenBack.chain(tween) tween.start() animate(group) function update() { target.style.transform = `translate3d(${position.x}px, ${position.y}px, 0.0001px) rotateY(${Math.floor(position.rotation)}deg)` } ================================================ FILE: examples/example-projects/plain-javascript-modules/package.json ================================================ { "description": "Plain JS modules in a browser.", "type": "module", "scripts": { "start": "npm run serve", "serve": "five-server ." }, "dependencies": { "@tweenjs/tween.js": "file:../../../" }, "devDependencies": { "five-server": "^0.3.2" } } ================================================ FILE: examples/example-projects/plain-javascript-modules/style.css ================================================ html { transform-style: preserve-3d; perspective: 800px; } body { background: #fff; font-family: Helvetica, Arial, sans; transform-style: preserve-3d; } a { color: #333; } h2 { font-weight: normal; } button { cursor: pointer; color: #333; background: #fff; border: 2px solid #333; padding: 10px; border-radius: 10px; font-weight: bold; font-size: 20px; transition: all 150ms ease-out; } button:hover { background: #333; color: #fff; } #info { position: absolute; top: 0; left: 0; padding: 1.5em 2em; } #info h1 { font-size: 3em; color: #333; margin-top: 0; letter-spacing: -0.05em; } #info h2 { font-size: 2.5em; text-transform: uppercase; color: #666; margin-top: 0; } #info p { font-size: 2em; line-height: 1em; color: #aaa; max-width: 10em; } ================================================ FILE: examples/example-projects/plain-typescript-modules/README.md ================================================ This example is written in TypeScript and requires running a build step, using JavaScript module format to organize code into separate files and importing from one file to the other using native `import` syntax. This example uses the `tsc` (TypeScript compiler) to compile `.ts` files into `.js` files. The `tsconfig.json` file specifies the output format to be `esnext`, which means the output `.js` files will have `import` statements just as we've written them in the `.ts` files, and the output `.js` files will be executed as native JS modules in the browser. To set up, run `npm install`. To build and run the project in a single command, run `npm start`. To compile only, run `npm run build` to generate `.js` files from `.ts` files. To continuously build `.ts` files into `.js` files any time the `.ts` files change, run `npm run dev`. ================================================ FILE: examples/example-projects/plain-typescript-modules/animate.js ================================================ export function animate(group) { function loop(time) { group.update(time); const stopped = group.allStopped(); if (!stopped) requestAnimationFrame(loop); } loop(performance.now()); } ================================================ FILE: examples/example-projects/plain-typescript-modules/animate.ts ================================================ import type {Group} from '@tweenjs/tween.js' export function animate(group: Group) { function loop(time: number) { group.update(time) const stopped = group.allStopped() if (!stopped) requestAnimationFrame(loop) } loop(performance.now()) } ================================================ FILE: examples/example-projects/plain-typescript-modules/index.html ================================================ Tween.js / hello world (JS modules)!

tween.js

00 _ hello world (using native JS modules)

Simple example for illustrating the creation and chaining of tweens.

hello world!
================================================ FILE: examples/example-projects/plain-typescript-modules/index.js ================================================ import * as TWEEN from '@tweenjs/tween.js'; // For sake of example, we put animate() in a separate file, and import it into index.ts. // Note the extension needs to be .js, as TypeScript does modify the import specifiers, and .js is needed once the file is converted to plain JS. import { animate } from './animate.js'; const group = new TWEEN.Group(); const position = { x: 100, y: 100, rotation: 10 }; const target = document.getElementById('target'); const tween = new TWEEN.Tween(position, group) .to({ x: 700, y: 200, rotation: 359 }, 2000) .delay(1000) .easing(TWEEN.Easing.Elastic.InOut) .onUpdate(update); const tweenBack = new TWEEN.Tween(position, group) .to({ x: 100, y: 100, rotation: 10 }, 3000) .easing(TWEEN.Easing.Elastic.InOut) .onUpdate(update); tween.chain(tweenBack); tweenBack.chain(tween); tween.start(); animate(group); function update() { target.style.transform = `translate3d(${position.x}px, ${position.y}px, 0.0001px) rotateY(${Math.floor(position.rotation)}deg)`; } ================================================ FILE: examples/example-projects/plain-typescript-modules/index.ts ================================================ import * as TWEEN from '@tweenjs/tween.js' // For sake of example, we put animate() in a separate file, and import it into index.ts. // Note the extension needs to be .js, as TypeScript does modify the import specifiers, and .js is needed once the file is converted to plain JS. import {animate} from './animate.js' const group = new TWEEN.Group() const position = {x: 100, y: 100, rotation: 10} const target = document.getElementById('target') const tween = new TWEEN.Tween(position, group) .to({x: 700, y: 200, rotation: 359}, 2000) .delay(1000) .easing(TWEEN.Easing.Elastic.InOut) .onUpdate(update) const tweenBack = new TWEEN.Tween(position, group) .to({x: 100, y: 100, rotation: 10}, 3000) .easing(TWEEN.Easing.Elastic.InOut) .onUpdate(update) tween.chain(tweenBack) tweenBack.chain(tween) tween.start() animate(group) function update() { target.style.transform = `translate3d(${position.x}px, ${position.y}px, 0.0001px) rotateY(${Math.floor(position.rotation)}deg)` } ================================================ FILE: examples/example-projects/plain-typescript-modules/package.json ================================================ { "description": "TS compiled to plain JS modules for running in browser.", "type": "module", "scripts": { "start": "npm run build && npm run serve", "build": "tsc", "dev": "tsc --watch", "serve": "five-server ." }, "dependencies": { "@tweenjs/tween.js": "file:../../../" }, "devDependencies": { "five-server": "^0.3.2", "typescript": "^5.3.3" } } ================================================ FILE: examples/example-projects/plain-typescript-modules/style.css ================================================ html { transform-style: preserve-3d; perspective: 800px; } body { background: #fff; font-family: Helvetica, Arial, sans; transform-style: preserve-3d; } a { color: #333; } h2 { font-weight: normal; } button { cursor: pointer; color: #333; background: #fff; border: 2px solid #333; padding: 10px; border-radius: 10px; font-weight: bold; font-size: 20px; transition: all 150ms ease-out; } button:hover { background: #333; color: #fff; } #info { position: absolute; top: 0; left: 0; padding: 1.5em 2em; } #info h1 { font-size: 3em; color: #333; margin-top: 0; letter-spacing: -0.05em; } #info h2 { font-size: 2.5em; text-transform: uppercase; color: #666; margin-top: 0; } #info p { font-size: 2em; line-height: 1em; color: #aaa; max-width: 10em; } ================================================ FILE: examples/example-projects/plain-typescript-modules/tsconfig.json ================================================ { "compilerOptions": { "module": "ESNext", "moduleResolution": "node", "target": "ESNext" }, "include": ["./**/*.ts"] } ================================================ FILE: examples/js/createGraph.js ================================================ // @ts-check import {Tween, Easing} from '../../dist/tween.esm.js' import {toPhysicalPx} from './toPhysicalPx.js' export function createGraph(group, text, easingFn, width = 180, height = 100) { const div = document.createElement('div') div.style.display = 'inline-block' // +20 for padding div.style.width = width + 20 + 'px' div.style.height = height + 20 + 'px' const canvas = document.createElement('canvas') canvas.style.width = width + 'px' canvas.style.height = height + 'px' canvas.width = toPhysicalPx(width) canvas.height = toPhysicalPx(height) const context = canvas.getContext('2d') if (!context) throw 'impossible' context.fillStyle = 'rgb(250,250,250)' context.fillRect(0, 0, toPhysicalPx(width), toPhysicalPx(height)) context.lineWidth = toPhysicalPx(1) context.strokeStyle = 'rgb(230,230,230)' context.beginPath() context.moveTo(0, toPhysicalPx(20)) context.lineTo(toPhysicalPx(width), toPhysicalPx(20)) context.moveTo(0, toPhysicalPx(80)) context.lineTo(toPhysicalPx(width), toPhysicalPx(80)) context.closePath() context.stroke() context.lineWidth = toPhysicalPx(2) context.strokeStyle = 'rgba(255,127,127,0.9)' context.beginPath() context.moveTo(toPhysicalPx(5), toPhysicalPx(80)) context.lineCap = 'round' const position = {x: toPhysicalPx(5), y: toPhysicalPx(80)} new Tween(position, group) .to({x: toPhysicalPx(175)}, 2000) .easing(Easing.Linear.None) .start() new Tween(position, group) .to({y: toPhysicalPx(20)}, 2000) .easing(easingFn) .onUpdate(function () { context.lineTo(position.x, position.y) context.stroke() }) .start() div.appendChild(document.createTextNode(text)) div.appendChild(document.createElement('br')) div.appendChild(canvas) return div } ================================================ FILE: examples/js/createPath.js ================================================ // @ts-check import {Tween, Easing} from '../../dist/tween.esm.js' import {toPhysicalPx} from './toPhysicalPx.js' const width = 240, height = 160 // random points /* export const x0 = Math.random() * (width - 40) + 20, y0 = Math.random() * (height - 40) + 20, xA = [], yA = [] for (const i = 0; i < 10; i++) { xA.push(Math.random() * (width - 40) + 20) yA.push(Math.random() * (height - 40) + 20) } */ // fixed points export const min = 1 / 6, max = 5 / 6 export const x0 = width * min, y0 = height / 2, xA = [width * max, width / 2], yA = [height * min, height * max] export function createPath(group, text, interpolation, width = 240, height = 160) { const div = document.createElement('div') div.style.display = 'inline-block' // +20 for padding div.style.width = width + 20 + 'px' div.style.height = height + 20 + 'px' const canvas = document.createElement('canvas') canvas.style.width = width + 'px' canvas.style.height = height + 'px' canvas.width = toPhysicalPx(width) canvas.height = toPhysicalPx(height) const context = canvas.getContext('2d') if (!context) throw 'doh' context.fillStyle = 'rgb(250,250,250)' context.fillRect(0, 0, toPhysicalPx(width), toPhysicalPx(height)) context.lineWidth = toPhysicalPx(1) context.strokeStyle = 'rgb(230,230,230)' // points context.fillStyle = 'rgb(200,200,200)' context.fillRect( toPhysicalPx(x0) - toPhysicalPx(3), toPhysicalPx(y0) - toPhysicalPx(3), toPhysicalPx(6), toPhysicalPx(6), ) context.fillRect( toPhysicalPx(xA[xA.length - 1]) - toPhysicalPx(3), toPhysicalPx(yA[yA.length - 1]) - toPhysicalPx(3), toPhysicalPx(6), toPhysicalPx(6), ) for (let i = 0; i < xA.length; i++) { context.fillRect( toPhysicalPx(xA[i]) - toPhysicalPx(2), toPhysicalPx(yA[i]) - toPhysicalPx(2), toPhysicalPx(4), toPhysicalPx(4), ) } // context.lineWidth = toPhysicalPx(2) context.strokeStyle = 'rgba(255,127,127,0.9)' context.beginPath() context.moveTo(toPhysicalPx(x0), toPhysicalPx(y0)) context.lineCap = 'round' const position = {x: x0, y: y0} new Tween(position, group) .to({x: xA, y: yA}, 3000) .easing(Easing.Linear.None) .interpolation(interpolation) .onUpdate(function () { context.lineTo(toPhysicalPx(position.x), toPhysicalPx(position.y)) context.stroke() }) .start() div.appendChild(document.createTextNode(text)) div.appendChild(document.createElement('br')) div.appendChild(canvas) return div } ================================================ FILE: examples/js/drawings.js ================================================ export function drawRabbit(context, x, y, color, opacity = 1) { context.save() context.fillStyle = color context.globalAlpha = opacity context.translate(x, y) context.beginPath() context.moveTo(0, 0) context.bezierCurveTo(15, 0, 15, -40, 5, -30) context.lineTo(0, 0) context.bezierCurveTo(-15, 0, -15, -40, -5, -30) context.lineTo(0, 0) context.closePath() context.fill() context.beginPath() context.arc(0, 0, 15, 0, Math.PI * 2, true) context.closePath() context.fill() context.restore() } export function drawFox(context, x, y, color) { context.save() context.fillStyle = color context.translate(x, y - 13) context.scale(1.2, 1.2) context.beginPath() context.moveTo(4, 24) context.lineTo(8, 16) context.lineTo(14, 10) context.lineTo(15, 0) context.lineTo(9, -10) context.lineTo(2, 0) context.lineTo(-2, 0) context.lineTo(-9, -10) context.lineTo(-15, 0) context.lineTo(-14, 10) context.lineTo(-8, 16) context.lineTo(-4, 24) context.closePath() context.fill() context.restore() } ================================================ FILE: examples/js/toPhysicalPx.js ================================================ export const toPhysicalPx = cssPx => cssPx * devicePixelRatio ================================================ FILE: package.json ================================================ { "name": "@tweenjs/tween.js", "description": "Simple and fast tweening engine with optimised Robert Penner's equations.", "version": "25.0.0", "type": "module", "main": "dist/tween.cjs", "types": "dist/tween.d.ts", "module": "dist/tween.esm.js", "exports": { ".": { "import": "./dist/tween.esm.js", "require": "./dist/tween.cjs", "types": "./dist/tween.d.ts" } }, "files": [ "dist", "README.md", "LICENSE" ], "homepage": "https://github.com/tweenjs/tween.js", "repository": { "type": "git", "url": "https://github.com/tweenjs/tween.js.git" }, "bugs": { "url": "https://github.com/tweenjs/tween.js/issues" }, "license": "MIT", "keywords": [ "tween", "interpolation" ], "dependencies": {}, "scripts": { "dev": "(npm run tsc-watch -- --preserveWatchOutput & p1=$!; npm run rollup-build -- --watch & p2=$!; wait $p1 $p2)", "build": "rimraf dist .tmp && node scripts/write-version.js && npm run tsc && npm run rollup-build", "rollup-build": "rollup -c ./rollup.config.js", "tsc": "tsc", "tsc-watch": "tsc --watch", "examples": "npx serve .", "test": "npm run build && npm run test-lint && npm run test-unit", "test-unit": "nodeunit test/unit/nodeunitheadless.cjs", "test-lint": "npm run prettier -- --check", "lint": "npm run prettier -- --write", "prettier": "prettier .", "prepare": "npm run build", "version": "npm test && git add .", "release:patch": "npm version patch --message 'v%s' && npm publish && npm run _release:push-branch", "release:minor": "npm version minor --message 'v%s' && npm publish && npm run _release:push-branch", "release:major": "npm version major --message 'v%s' && npm publish && npm run _release:push-branch", "_release:push-branch": "git push --follow-tags --set-upstream origin `git rev-parse --abbrev-ref HEAD`" }, "author": "tween.js contributors (https://github.com/tweenjs/tween.js/graphs/contributors)", "devDependencies": { "nodeunit": "^0.11.3", "prettier": "^3.0.0", "rimraf": "^3.0.0", "rollup": "3.20.7", "rollup-plugin-dts": "5.3.0", "tslib": "^1.10.0", "typescript": "5.0.4" } } ================================================ FILE: rollup.config.js ================================================ import dts from 'rollup-plugin-dts' export default [ { input: '.tmp/Index.js', // https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined context: 'this', watch: {clearScreen: false}, output: [ { file: 'dist/tween.umd.js', name: 'TWEEN', format: 'umd', exports: 'named', }, { file: 'dist/tween.amd.js', format: 'amd', exports: 'named', }, { file: 'dist/tween.cjs', format: 'cjs', exports: 'named', }, { file: 'dist/tween.esm.js', format: 'es', exports: 'named', }, ], }, { input: '.tmp/tests.js', context: 'this', watch: {clearScreen: false}, output: [ {file: '.tmp/tests.cjs', format: 'cjs', exports: 'named'}, // For tests running in Node.js {file: '.tmp/tests.umd.js', format: 'umd', exports: 'named', name: 'TWEEN'}, // For the nodeunit.html tests in browser ], }, { input: './.tmp/Index.d.ts', watch: {clearScreen: false}, output: [{file: 'dist/tween.d.ts', format: 'es'}], plugins: [dts()], }, ] ================================================ FILE: scripts/write-version.js ================================================ import fs from 'fs' import pkg from '../package.json' with {type: 'json'} const {version} = pkg function handleError(error) { if (error) { console.error(error) process.exit(1) } } fs.open('./src/Version.ts', 'w', (error, fd) => { handleError(error) fs.write(fd, [`const VERSION = '${version}'`, 'export default VERSION', ''].join('\n'), error => { handleError(error) }) }) ================================================ FILE: src/Easing.ts ================================================ export type EasingFunction = (amount: number) => number export type EasingFunctionGroup = { In: EasingFunction Out: EasingFunction InOut: EasingFunction } /** * The Ease class provides a collection of easing functions for use with tween.js. */ export const Easing = Object.freeze({ Linear: Object.freeze({ None(amount: number): number { return amount }, In(amount: number): number { return amount }, Out(amount: number): number { return amount }, InOut(amount: number): number { return amount }, }), Quadratic: Object.freeze({ In(amount: number): number { return amount * amount }, Out(amount: number): number { return amount * (2 - amount) }, InOut(amount: number): number { if ((amount *= 2) < 1) { return 0.5 * amount * amount } return -0.5 * (--amount * (amount - 2) - 1) }, }), Cubic: Object.freeze({ In(amount: number): number { return amount * amount * amount }, Out(amount: number): number { return --amount * amount * amount + 1 }, InOut(amount: number): number { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount } return 0.5 * ((amount -= 2) * amount * amount + 2) }, }), Quartic: Object.freeze({ In(amount: number): number { return amount * amount * amount * amount }, Out(amount: number): number { return 1 - --amount * amount * amount * amount }, InOut(amount: number): number { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount } return -0.5 * ((amount -= 2) * amount * amount * amount - 2) }, }), Quintic: Object.freeze({ In(amount: number): number { return amount * amount * amount * amount * amount }, Out(amount: number): number { return --amount * amount * amount * amount * amount + 1 }, InOut(amount: number): number { if ((amount *= 2) < 1) { return 0.5 * amount * amount * amount * amount * amount } return 0.5 * ((amount -= 2) * amount * amount * amount * amount + 2) }, }), Sinusoidal: Object.freeze({ In(amount: number): number { return 1 - Math.sin(((1.0 - amount) * Math.PI) / 2) }, Out(amount: number): number { return Math.sin((amount * Math.PI) / 2) }, InOut(amount: number): number { return 0.5 * (1 - Math.sin(Math.PI * (0.5 - amount))) }, }), Exponential: Object.freeze({ In(amount: number): number { return amount === 0 ? 0 : Math.pow(1024, amount - 1) }, Out(amount: number): number { return amount === 1 ? 1 : 1 - Math.pow(2, -10 * amount) }, InOut(amount: number): number { if (amount === 0) { return 0 } if (amount === 1) { return 1 } if ((amount *= 2) < 1) { return 0.5 * Math.pow(1024, amount - 1) } return 0.5 * (-Math.pow(2, -10 * (amount - 1)) + 2) }, }), Circular: Object.freeze({ In(amount: number): number { return 1 - Math.sqrt(1 - amount * amount) }, Out(amount: number): number { return Math.sqrt(1 - --amount * amount) }, InOut(amount: number): number { if ((amount *= 2) < 1) { return -0.5 * (Math.sqrt(1 - amount * amount) - 1) } return 0.5 * (Math.sqrt(1 - (amount -= 2) * amount) + 1) }, }), Elastic: Object.freeze({ In(amount: number): number { if (amount === 0) { return 0 } if (amount === 1) { return 1 } return -Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI) }, Out(amount: number): number { if (amount === 0) { return 0 } if (amount === 1) { return 1 } return Math.pow(2, -10 * amount) * Math.sin((amount - 0.1) * 5 * Math.PI) + 1 }, InOut(amount: number): number { if (amount === 0) { return 0 } if (amount === 1) { return 1 } amount *= 2 if (amount < 1) { return -0.5 * Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI) } return 0.5 * Math.pow(2, -10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI) + 1 }, }), Back: Object.freeze({ In(amount: number): number { const s = 1.70158 return amount === 1 ? 1 : amount * amount * ((s + 1) * amount - s) }, Out(amount: number): number { const s = 1.70158 return amount === 0 ? 0 : --amount * amount * ((s + 1) * amount + s) + 1 }, InOut(amount: number): number { const s = 1.70158 * 1.525 if ((amount *= 2) < 1) { return 0.5 * (amount * amount * ((s + 1) * amount - s)) } return 0.5 * ((amount -= 2) * amount * ((s + 1) * amount + s) + 2) }, }), Bounce: Object.freeze({ In(amount: number): number { return 1 - Easing.Bounce.Out(1 - amount) }, Out(amount: number): number { if (amount < 1 / 2.75) { return 7.5625 * amount * amount } else if (amount < 2 / 2.75) { return 7.5625 * (amount -= 1.5 / 2.75) * amount + 0.75 } else if (amount < 2.5 / 2.75) { return 7.5625 * (amount -= 2.25 / 2.75) * amount + 0.9375 } else { return 7.5625 * (amount -= 2.625 / 2.75) * amount + 0.984375 } }, InOut(amount: number): number { if (amount < 0.5) { return Easing.Bounce.In(amount * 2) * 0.5 } return Easing.Bounce.Out(amount * 2 - 1) * 0.5 + 0.5 }, }), generatePow(power = 4): EasingFunctionGroup { power = power < Number.EPSILON ? Number.EPSILON : power power = power > 10000 ? 10000 : power return { In(amount: number): number { return amount ** power }, Out(amount: number): number { return 1 - (1 - amount) ** power }, InOut(amount: number): number { if (amount < 0.5) { return (amount * 2) ** power / 2 } return (1 - (2 - amount * 2) ** power) / 2 + 0.5 }, } }, }) export default Easing ================================================ FILE: src/Group.ts ================================================ import now from './Now' import type {Tween} from './Tween' /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tween */ export default class Group { private _tweens: Record = {} private _tweensAddedDuringUpdate: Record = {} constructor(...tweens: Tween[]) { this.add(...tweens) } getAll(): Array { return Object.keys(this._tweens).map(tweenId => this._tweens[tweenId]) } removeAll(): void { this._tweens = {} } add(...tweens: Tween[]): void { for (const tween of tweens) { // Remove from any other group first, a tween can only be in one group at a time. // @ts-expect-error library internal access tween._group?.remove(tween) // @ts-expect-error library internal access tween._group = this this._tweens[tween.getId()] = tween this._tweensAddedDuringUpdate[tween.getId()] = tween } } remove(...tweens: Tween[]): void { for (const tween of tweens) { // @ts-expect-error library internal access tween._group = undefined delete this._tweens[tween.getId()] delete this._tweensAddedDuringUpdate[tween.getId()] } } /** Return true if all tweens in the group are not paused or playing. */ allStopped() { return this.getAll().every(tween => !tween.isPlaying()) } update(time?: number): void /** * @deprecated The `preserve` parameter is now defaulted to `true` and will * be removed in a future major release, at which point all tweens of a * group will always be preserved when calling update. To migrate, always * use `group.add(tween)` or `group.remove(tween)` to manually add or remove * tweens, and do not rely on tweens being automatically added or removed. */ update(time?: number, preserve?: boolean): void update(time: number = now(), preserve = true): void { let tweenIds = Object.keys(this._tweens) if (tweenIds.length === 0) return // Tweens are updated in "batches". If you add a new tween during an // update, then the new tween will be updated in the next batch. // If you remove a tween during an update, it may or may not be updated. // However, if the removed tween was added during the current batch, // then it will not be updated. while (tweenIds.length > 0) { this._tweensAddedDuringUpdate = {} for (let i = 0; i < tweenIds.length; i++) { const tween = this._tweens[tweenIds[i]] const autoStart = !preserve if (tween && tween.update(time, autoStart) === false && !preserve) this.remove(tween) } tweenIds = Object.keys(this._tweensAddedDuringUpdate) } } onComplete(callback: (object: Tween[]) => void) { const group = this.getAll() group.forEach(tween => { const prevCallback = tween.getCompleteCallback() tween.onComplete(() => { prevCallback?.(tween) // After the onComplete callback completes, _isPlaying is updated to false, so if the total number of completed tweens is -1, then they are all complete. const completedGroup = group.filter(tween => !tween.isPlaying()) if (completedGroup.length === group.length - 1) callback(group) }) }) } } ================================================ FILE: src/Index.ts ================================================ /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ import Easing from './Easing' import Group from './Group' import Interpolation from './Interpolation' import now, {setNow} from './Now' import Sequence from './Sequence' import Tween from './Tween' import VERSION from './Version' import {mainGroup} from './mainGroup' const nextId = Sequence.nextId /** * Controlling groups of tweens * * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components. * In these cases, you may want to create your own smaller groups of tweens. */ const TWEEN = mainGroup // This is the best way to export things in a way that's compatible with both ES // Modules and CommonJS, without build hacks, and so as not to break the // existing API. // https://github.com/rollup/rollup/issues/1961#issuecomment-423037881 /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ const getAll = TWEEN.getAll.bind(TWEEN) /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ const removeAll = TWEEN.removeAll.bind(TWEEN) /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ const add = TWEEN.add.bind(TWEEN) /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ const remove = TWEEN.remove.bind(TWEEN) /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ const update = TWEEN.update.bind(TWEEN) // NOTE! Make sure both lists of exports below are kept in sync: export { Easing, Group, Interpolation, now, setNow, Sequence, nextId, Tween, VERSION, getAll, removeAll, add, remove, update, } const exports = { Easing, Group, Interpolation, now, setNow, Sequence, nextId, Tween, VERSION, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ getAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ removeAll, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ add, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ remove, /** * @deprecated The global TWEEN Group will be removed in a following major * release. To migrate, create a `new Group()` instead of using `TWEEN` as a * group. * * Old code: * * ```js * import * as TWEEN from '@tweenjs/tween.js' * * //... * * const tween = new TWEEN.Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * requestAnimationFrame(function loop(time) { * TWEEN.update(time) * requestAnimationFrame(loop) * }) * ``` * * New code: * * ```js * import {Tween, Group} from '@tweenjs/tween.js' * * //... * * const tween = new Tween(obj) * const tween2 = new TWEEN.Tween(obj2) * * //... * * const group = new Group() * group.add(tween) * group.add(tween2) * * //... * * requestAnimationFrame(function loop(time) { * group.update(time) * requestAnimationFrame(loop) * }) * ``` */ update, } export default exports ================================================ FILE: src/Interpolation.ts ================================================ /** * */ export type InterpolationFunction = (v: number[], k: number) => number /** * */ const Interpolation = { Linear: function (v: number[], k: number): number { const m = v.length - 1 const f = m * k const i = Math.floor(f) const fn = Interpolation.Utils.Linear if (k < 0) { return fn(v[0], v[1], f) } if (k > 1) { return fn(v[m], v[m - 1], m - f) } return fn(v[i], v[i + 1 > m ? m : i + 1], f - i) }, Bezier: function (v: number[], k: number): number { let b = 0 const n = v.length - 1 const pw = Math.pow const bn = Interpolation.Utils.Bernstein for (let i = 0; i <= n; i++) { b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i) } return b }, CatmullRom: function (v: number[], k: number): number { const m = v.length - 1 let f = m * k let i = Math.floor(f) const fn = Interpolation.Utils.CatmullRom if (v[0] === v[m]) { if (k < 0) { i = Math.floor((f = m * (1 + k))) } return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i) } else { if (k < 0) { return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]) } if (k > 1) { return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]) } return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i) } }, Utils: { Linear: function (p0: number, p1: number, t: number): number { return (p1 - p0) * t + p0 }, Bernstein: function (n: number, i: number): number { const fc = Interpolation.Utils.Factorial return fc(n) / fc(i) / fc(n - i) }, Factorial: (function () { const a = [1] return function (n: number): number { let s = 1 if (a[n]) { return a[n] } for (let i = n; i > 1; i--) { s *= i } a[n] = s return s } })(), CatmullRom: function (p0: number, p1: number, p2: number, p3: number, t: number): number { const v0 = (p2 - p0) * 0.5 const v1 = (p3 - p1) * 0.5 const t2 = t * t const t3 = t * t2 return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1 }, }, } export default Interpolation ================================================ FILE: src/Now.ts ================================================ let _nowFunc: Function = () => performance.now() const now = (): number => { return _nowFunc() } export function setNow(nowFunction: Function) { _nowFunc = nowFunction } export default now ================================================ FILE: src/Sequence.ts ================================================ /** * Utils */ export default class Sequence { private static _nextId = 0 static nextId(): number { return Sequence._nextId++ } } ================================================ FILE: src/Tween.ts ================================================ /** * Tween.js - Licensed under the MIT license * https://github.com/tweenjs/tween.js * ---------------------------------------------- * * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. * Thank you all, you're awesome! */ import Easing from './Easing' import Interpolation from './Interpolation' import {mainGroup} from './mainGroup' import Sequence from './Sequence' import now from './Now' import type {EasingFunction} from './Easing' import type {InterpolationFunction} from './Interpolation' import type Group from './Group' export class Tween { static autoStartOnUpdate = false private _isPaused = false private _pauseStart = 0 private _valuesStart: UnknownProps = {} private _valuesEnd: Record = {} private _valuesStartRepeat: UnknownProps = {} private _duration = 1000 private _isDynamic = false private _initialRepeat = 0 private _repeat = 0 private _repeatDelayTime?: number private _yoyo = false private _isPlaying = false private _reversed = false private _delayTime = 0 private _startTime = 0 private _easingFunction: EasingFunction = Easing.Linear.None private _interpolationFunction: InterpolationFunction = Interpolation.Linear // eslint-disable-next-line private _chainedTweens: Array> = [] private _onStartCallback?: (object: T) => void private _onStartCallbackFired = false private _onEveryStartCallback?: (object: T) => void private _onEveryStartCallbackFired = false private _onUpdateCallback?: (object: T, elapsed: number) => void private _onRepeatCallback?: (object: T) => void private _onCompleteCallback?: (object: T) => void private _onStopCallback?: (object: T) => void private _id = Sequence.nextId() private _isChainStopped = false private _propertiesAreSetUp = false private _object: T private _group?: Group /** * @param object - The object whose properties this Tween will animate. * @param group - The object whose properties this Tween will animate. */ constructor(object: T, group?: Group) /** * @deprecated The group parameter is now deprecated, instead use `new * Tween(object)` then `group.add(tween)` to add a tween to a group. Use * `new Tween(object, true)` to restore the old behavior for now, but this * will be removed in the future. */ constructor(object: T, group: true) constructor(object: T, group?: Group | true) { this._object = object if (typeof group === 'object') { this._group = group group.add(this) } // Use "true" to restore old behavior (will be removed in future release). else if (group === true) { this._group = mainGroup mainGroup.add(this) } } getId(): number { return this._id } getCompleteCallback(): ((object: T) => void) | undefined { return this._onCompleteCallback } isPlaying(): boolean { return this._isPlaying } isPaused(): boolean { return this._isPaused } getDuration(): number { return this._duration } to(target: UnknownProps, duration = 1000): this { if (this._isPlaying) throw new Error('Can not call Tween.to() while Tween is already started or paused. Stop the Tween first.') this._valuesEnd = target this._propertiesAreSetUp = false this._duration = duration < 0 ? 0 : duration return this } duration(duration = 1000): this { this._duration = duration < 0 ? 0 : duration return this } dynamic(dynamic = false): this { this._isDynamic = dynamic return this } start(time: number = now(), overrideStartingValues = false): this { if (this._isPlaying) { return this } this._repeat = this._initialRepeat if (this._reversed) { // If we were reversed (f.e. using the yoyo feature) then we need to // flip the tween direction back to forward. this._reversed = false for (const property in this._valuesStartRepeat) { this._swapEndStartRepeatValues(property) this._valuesStart[property] = this._valuesStartRepeat[property] } } this._isPlaying = true this._isPaused = false this._onStartCallbackFired = false this._onEveryStartCallbackFired = false this._isChainStopped = false this._startTime = time this._startTime += this._delayTime if (!this._propertiesAreSetUp || overrideStartingValues) { this._propertiesAreSetUp = true // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { const tmp: Record = {} for (const prop in this._valuesEnd) tmp[prop] = this._valuesEnd[prop] this._valuesEnd = tmp } this._setupProperties( this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues, ) } return this } startFromCurrentValues(time?: number): this { return this.start(time, true) } private _setupProperties( _object: UnknownProps, _valuesStart: UnknownProps, _valuesEnd: UnknownProps, _valuesStartRepeat: UnknownProps, overrideStartingValues: boolean, ): void { for (const property in _valuesEnd) { const startValue = _object[property] as number | Record const startValueIsArray = Array.isArray(startValue) const propType = startValueIsArray ? 'array' : typeof startValue let isInterpolationList = !startValueIsArray && Array.isArray(_valuesEnd[property]) // If `to()` specifies a property that doesn't exist in the source object, // we should not set that property in the object if (propType === 'undefined' || propType === 'function') { continue } // Check if an Array was provided as property value if (isInterpolationList) { const endValues = _valuesEnd[property] as Array if (endValues.length === 0) { continue } // Handle an array of relative values. // Creates a local copy of the Array with the start value at the front const temp = [startValue as number] for (let i = 0, l = endValues.length; i < l; i += 1) { const value = this._handleRelativeValue(startValue as number, endValues[i]) if (isNaN(value)) { isInterpolationList = false console.warn('Found invalid interpolation list. Skipping.') break } temp.push(value) } if (isInterpolationList) { // if (_valuesStart[property] === undefined) { // handle end values only the first time. NOT NEEDED? setupProperties is now guarded by _propertiesAreSetUp. _valuesEnd[property] = temp // } } } // handle the deepness of the values if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) { _valuesStart[property] = startValueIsArray ? [] : {} const nestedObject = startValue as Record for (const prop in nestedObject) { _valuesStart[property][prop] = nestedObject[prop] } // TODO? repeat nested values? And yoyo? And array values? _valuesStartRepeat[property] = startValueIsArray ? [] : {} let endValues = _valuesEnd[property] // If dynamic is not enabled, clone the end values instead of using the passed-in end values. if (!this._isDynamic) { const tmp: Record = {} for (const prop in endValues) tmp[prop] = endValues[prop] _valuesEnd[property] = endValues = tmp } this._setupProperties( nestedObject, _valuesStart[property], endValues, _valuesStartRepeat[property], overrideStartingValues, ) } else { // Save the starting value, but only once unless override is requested. if (typeof _valuesStart[property] === 'undefined' || overrideStartingValues) { _valuesStart[property] = startValue } if (!startValueIsArray) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStart[property] *= 1.0 // Ensures we're using numbers, not strings } if (isInterpolationList) { // eslint-disable-next-line // @ts-ignore FIXME? _valuesStartRepeat[property] = _valuesEnd[property].slice().reverse() } else { _valuesStartRepeat[property] = _valuesStart[property] || 0 } } } } stop(): this { if (!this._isChainStopped) { this._isChainStopped = true this.stopChainedTweens() } if (!this._isPlaying) { return this } this._isPlaying = false this._isPaused = false if (this._onStopCallback) { this._onStopCallback(this._object) } return this } end(): this { this._goToEnd = true this.update(this._startTime + this._duration) return this } pause(time: number = now()): this { if (this._isPaused || !this._isPlaying) { return this } this._isPaused = true this._pauseStart = time return this } resume(time: number = now()): this { if (!this._isPaused || !this._isPlaying) { return this } this._isPaused = false this._startTime += time - this._pauseStart this._pauseStart = 0 return this } stopChainedTweens(): this { for (let i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { this._chainedTweens[i].stop() } return this } /** * Removes the tween from the current group it is in, if any, then adds the * tween to the specified `group`. */ group(group: Group): this /** * @deprecated The argless call signature has been removed. Use * `tween.group(group)` or `group.add(tween)`, instead. */ group(): this group(group?: Group): this { if (!group) { console.warn('tween.group() without args has been removed, use group.add(tween) instead.') return this } group.add(this) return this } /** * Removes the tween from whichever group it is in. */ remove() { this._group?.remove(this) return this } delay(amount = 0): this { this._delayTime = amount return this } repeat(times = 0): this { this._initialRepeat = times this._repeat = times return this } repeatDelay(amount?: number): this { this._repeatDelayTime = amount return this } yoyo(yoyo = false): this { this._yoyo = yoyo return this } easing(easingFunction: EasingFunction = Easing.Linear.None): this { this._easingFunction = easingFunction return this } interpolation(interpolationFunction: InterpolationFunction = Interpolation.Linear): this { this._interpolationFunction = interpolationFunction return this } // eslint-disable-next-line chain(...tweens: Array>): this { this._chainedTweens = tweens return this } onStart(callback?: (object: T) => void): this { this._onStartCallback = callback return this } onEveryStart(callback?: (object: T) => void): this { this._onEveryStartCallback = callback return this } onUpdate(callback?: (object: T, elapsed: number) => void): this { this._onUpdateCallback = callback return this } onRepeat(callback?: (object: T) => void): this { this._onRepeatCallback = callback return this } onComplete(callback?: (object: T) => void): this { this._onCompleteCallback = callback return this } onStop(callback?: (object: T) => void): this { this._onStopCallback = callback return this } private _goToEnd = false /** * @returns true if the tween is still playing after the update, false * otherwise (calling update on a paused tween still returns true because * it is still playing, just paused). * * @param autoStart - When true, calling update will implicitly call start() * as well. Note, if you stop() or end() the tween, but are still calling * update(), it will start again! */ update(time = now(), autoStart = Tween.autoStartOnUpdate): boolean { if (this._isPaused) return true let property if (!this._goToEnd && !this._isPlaying) { if (autoStart) this.start(time, true) else return false } this._goToEnd = false if (time < this._startTime) { return true } if (this._onStartCallbackFired === false) { if (this._onStartCallback) { this._onStartCallback(this._object) } this._onStartCallbackFired = true } if (this._onEveryStartCallbackFired === false) { if (this._onEveryStartCallback) { this._onEveryStartCallback(this._object) } this._onEveryStartCallbackFired = true } const elapsedTime = time - this._startTime const durationAndDelay = this._duration + (this._repeatDelayTime ?? this._delayTime) const totalTime = this._duration + this._repeat * durationAndDelay const calculateElapsedPortion = () => { if (this._duration === 0) return 1 if (elapsedTime > totalTime) { return 1 } const timesRepeated = Math.trunc(elapsedTime / durationAndDelay) const timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay // TODO use %? // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay const portion = Math.min(timeIntoCurrentRepeat / this._duration, 1) if (portion === 0 && elapsedTime === this._duration) { return 1 } return portion } const elapsed = calculateElapsedPortion() const value = this._easingFunction(elapsed) // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value) if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed) } if (this._duration === 0 || elapsedTime >= this._duration) { if (this._repeat > 0) { const completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat) if (isFinite(this._repeat)) { this._repeat -= completeCount } // Reassign starting values, restart by making startTime = now for (property in this._valuesStartRepeat) { if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { this._valuesStartRepeat[property] = // eslint-disable-next-line // @ts-ignore FIXME? this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]) } if (this._yoyo) { this._swapEndStartRepeatValues(property) } this._valuesStart[property] = this._valuesStartRepeat[property] } if (this._yoyo) { this._reversed = !this._reversed } this._startTime += durationAndDelay * completeCount if (this._onRepeatCallback) { this._onRepeatCallback(this._object) } this._onEveryStartCallbackFired = false return true } else { if (this._onCompleteCallback) { this._onCompleteCallback(this._object) } for (let i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { // Make the chained tweens start exactly at the time they should, // even if the `update()` method was called way past the duration of the tween this._chainedTweens[i].start(this._startTime + this._duration, false) } this._isPlaying = false return false } } return true } private _updateProperties( _object: UnknownProps, _valuesStart: UnknownProps, _valuesEnd: UnknownProps, value: number, ): void { for (const property in _valuesEnd) { // Don't update properties that do not exist in the source object if (_valuesStart[property] === undefined) { continue } const start = _valuesStart[property] || 0 let end = _valuesEnd[property] const startIsArray = Array.isArray(_object[property]) const endIsArray = Array.isArray(end) const isInterpolationList = !startIsArray && endIsArray if (isInterpolationList) { _object[property] = this._interpolationFunction(end as Array, value) } else if (typeof end === 'object' && end) { // eslint-disable-next-line // @ts-ignore FIXME? this._updateProperties(_object[property], start, end, value) } else { // Parses relative end values with start as base (e.g.: +10, -3) end = this._handleRelativeValue(start as number, end as number | string) // Protect against non numeric properties. if (typeof end === 'number') { // eslint-disable-next-line // @ts-ignore FIXME? _object[property] = start + (end - start) * value } } } } private _handleRelativeValue(start: number, end: number | string): number { if (typeof end !== 'string') { return end } if (end.charAt(0) === '+' || end.charAt(0) === '-') { return start + parseFloat(end) } return parseFloat(end) } private _swapEndStartRepeatValues(property: string): void { const tmp = this._valuesStartRepeat[property] const endValue = this._valuesEnd[property] if (typeof endValue === 'string') { this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(endValue) } else { this._valuesStartRepeat[property] = this._valuesEnd[property] } this._valuesEnd[property] = tmp } } export type UnknownProps = Record export default Tween ================================================ FILE: src/Version.ts ================================================ const VERSION = '25.0.0' export default VERSION ================================================ FILE: src/mainGroup.ts ================================================ import Group from './Group' export const mainGroup = new Group() ================================================ FILE: src/test-performance-now-fake.ts ================================================ /** * @fileoverview Tool for patching performance.now so we can fake it in the * tests. */ let time = 0 export function tickTime(t: number): void { time = t } export function patchPerformanceNow(): void { Object.defineProperties(performance, { now: { value: () => { return time }, configurable: true, }, }) } export function restorePerformanceNow(): void { // The original is on the prototype, simply delete ours to expose that one again. delete (performance as any).now } ================================================ FILE: src/tests.ts ================================================ import {tickTime, patchPerformanceNow, restorePerformanceNow} from './test-performance-now-fake' import * as TWEEN from './Index' import type {EasingFunctionGroup} from './Easing' export const tests = { hello(test: Test): void { test.ok(TWEEN !== null) test.done() }, // TWEEN tests 'TWEEN.getAll'(test: Test): void { test.ok(TWEEN.getAll() instanceof Array) test.done() }, 'TWEEN object stores tweens automatically on start'(test: Test): void { const numTweensBefore = TWEEN.getAll().length, t = new TWEEN.Tween({}, true) t.start() const numTweensAfter = TWEEN.getAll().length test.equal(numTweensBefore + 1, numTweensAfter) test.done() }, 'TWEEN.removeAll()'(test: Test): void { const t = new TWEEN.Tween({}, true) TWEEN.removeAll() test.equal(TWEEN.getAll().length, 0, 'No tweens left') t.start() test.equal(TWEEN.getAll().length, 0, 'Tweens are not automatically added/removed') TWEEN.add(t) test.equal(TWEEN.getAll().length, 1, 'A tween has to be manually added') TWEEN.removeAll() test.equal(TWEEN.getAll().length, 0, 'No tweens left') test.done() }, 'TWEEN.add()'(test: Test): void { const all = TWEEN.getAll(), numTweens = all.length, t = new TWEEN.Tween({}, true) TWEEN.add(t) test.equal(numTweens + 1, TWEEN.getAll().length) test.done() }, 'TWEEN.remove()'(test: Test): void { const all = TWEEN.getAll(), numTweens = all.length, t = new TWEEN.Tween({}, true) TWEEN.add(t) test.ok(TWEEN.getAll().indexOf(t) != -1) TWEEN.remove(t) test.equal(numTweens, TWEEN.getAll().length) test.equal(TWEEN.getAll().indexOf(t), -1) test.done() }, 'TWEEN.update() removes tweens when they are finished only if preserve is set to false'(test: Test): void { TWEEN.removeAll() const t1 = new TWEEN.Tween({}, true).to({}, 1000), t2 = new TWEEN.Tween({}, true).to({}, 2000) test.equal(TWEEN.getAll().length, 2) t1.start(0) t2.start(0) test.equal(TWEEN.getAll().length, 2) TWEEN.update(0, false) test.equal(TWEEN.getAll().length, 2) TWEEN.update(999, false) test.equal(TWEEN.getAll().length, 2) TWEEN.update(1000, false) test.equal(TWEEN.getAll().length, 1) test.equal(TWEEN.getAll().indexOf(t1), -1) test.ok(TWEEN.getAll().indexOf(t2) != -1) test.done() }, 'TWEEN.update() does not remove tweens when they are finished with preserve true (default)'(test: Test): void { TWEEN.removeAll() const t1 = new TWEEN.Tween({}, true).to({}, 1000), t2 = new TWEEN.Tween({}, true).to({}, 2000) test.equal(TWEEN.getAll().length, 2) t1.start(0) t2.start(0) test.equal(TWEEN.getAll().length, 2) TWEEN.update(0) test.equal(TWEEN.getAll().length, 2) TWEEN.update(999) test.equal(TWEEN.getAll().length, 2) TWEEN.update(1000) test.equal(TWEEN.getAll().length, 2) TWEEN.update(1001) test.equal(TWEEN.getAll().length, 2) test.ok(TWEEN.getAll().indexOf(t1) != -1) test.ok(TWEEN.getAll().indexOf(t2) != -1) test.done() }, 'Unremoved tweens which have been updated past their finish time may go backward in time'(test: Test): void { TWEEN.removeAll() const target1 = {a: 0} const target2 = {b: 0} const t1 = new TWEEN.Tween(target1, true).to({a: 1}, 1000), t2 = new TWEEN.Tween(target2, true).to({b: 1}, 2000) t1.start(0) t2.start(0) // To be able to make a tween go backward in time, it must be // updated with preserve set to true (default). Otherwise, the // backward-in-time feature does not apply. TWEEN.update(200, true) TWEEN.update(2500, true) test.equal(TWEEN.getAll().length, 2) // If you want to go backward in time, start back at the beginning // first, then go to any time between the start and end time. t1.start(0) t2.start(0) TWEEN.update(750, true) test.equal(target1.a, 0.75) test.equal(target2.b, 0.375) TWEEN.update(500) test.equal(target1.a, 0.5) test.equal(target2.b, 0.25) test.done() }, // TWEEN.Tween tests constructor(test: Test): void { const t = new TWEEN.Tween({}) test.ok(t instanceof TWEEN.Tween, 'Pass') test.done() }, 'Return the same tween instance for method chaining'(test: Test): void { const t = new TWEEN.Tween({}) test.ok(t.to({}, 0) instanceof TWEEN.Tween) test.equal(t.to({}, 0), t) test.ok(t.start() instanceof TWEEN.Tween) test.equal(t.start(), t) test.ok(t.stop() instanceof TWEEN.Tween) test.equal(t.stop(), t) test.ok(t.delay() instanceof TWEEN.Tween) test.equal(t.delay(), t) test.ok(t.easing() instanceof TWEEN.Tween) test.equal(t.easing(), t) test.ok(t.interpolation() instanceof TWEEN.Tween) test.equal(t.interpolation(), t) test.ok(t.chain() instanceof TWEEN.Tween) test.equal(t.chain(), t) test.ok(t.onStart() instanceof TWEEN.Tween) test.equal(t.onStart(), t) test.ok(t.onEveryStart() instanceof TWEEN.Tween) test.equal(t.onEveryStart(), t) test.ok(t.onStop() instanceof TWEEN.Tween) test.equal(t.onStop(), t) test.ok(t.onUpdate() instanceof TWEEN.Tween) test.equal(t.onUpdate(), t) test.ok(t.onComplete() instanceof TWEEN.Tween) test.equal(t.onComplete(), t) test.ok(t.duration() instanceof TWEEN.Tween) test.equal(t.duration(), t) test.ok(t.group() instanceof TWEEN.Tween) test.equal(t.group(), t) test.done() }, 'Tween existing property'(test: Test): void { const obj = {x: 1}, t = new TWEEN.Tween(obj) t.to({x: 2}, 1000) t.start(0) t.update(1000) test.deepEqual(obj.x, 2) test.done() }, 'Tween non-existing property'(test: Test): void { const obj = {x: 1}, t = new TWEEN.Tween(obj) t.to({y: 0}, 1000) t.start(0) t.update(1000) test.deepEqual(obj.x, 1) // eslint-disable-next-line // @ts-ignore test.equal(obj.y, undefined) test.done() }, 'Tween non-null property'(test: Test): void { const obj = {x: 1}, t = new TWEEN.Tween(obj) t.to({x: 2}, 1000) t.start(0) t.update(1000) test.deepEqual(obj.x, 2) test.ok(obj.x !== null) test.done() }, 'Tween function property'(test: Test): void { const my_function = new Function() const obj = {x: my_function}, t = new TWEEN.Tween(obj) t.to({x: my_function}) t.start(0) t.update(1000) test.ok(obj.x === my_function) test.done() }, 'Tween boolean property'(test: Test): void { const obj = {x: true}, t = new TWEEN.Tween(obj) t.to({x: new Function()}) t.start(0) t.update(1000) test.ok(typeof obj.x === 'boolean') test.ok(obj.x === true) test.done() }, 'Tween null property'(test: Test): void { const obj = {x: null}, t = new TWEEN.Tween(obj) t.to({x: 2}, 1000) t.start(0) t.update(1000) test.deepEqual(obj.x, 2) test.done() }, 'Tween undefined property'(test: Test): void { const obj = {}, t = new TWEEN.Tween(obj) t.to({x: 2}, 1000) t.start(0) t.update(1000) // eslint-disable-next-line // @ts-ignore test.equal(obj.x, undefined) test.done() }, 'Tween relative positive value'(test: Test): void { const obj = {x: 0}, t = new TWEEN.Tween(obj) t.to({x: '+100'}, 1000) t.start(0) t.update(1000) test.equal(obj.x, 100) test.done() }, 'Tween relative negative value'(test: Test): void { const obj = {x: 0}, t = new TWEEN.Tween(obj) t.to({x: '-100'}, 1000) t.start(0) t.update(1000) test.equal(obj.x, -100) test.done() }, 'String values without a + or - sign should not be interpreted as relative'(test: Test): void { const obj = {x: 100}, t = new TWEEN.Tween(obj) t.to({x: '100'}, 1000) t.start(0) t.update(1000) test.equal(obj.x, 100) test.done() }, 'Tween relative positive value, with yoyo'(test: Test): void { const obj = {x: 0}, t = new TWEEN.Tween(obj) t.to({x: '+100'}, 1000) t.repeat(1) t.yoyo(true) t.start(0) t.update(500) test.equal(obj.x, 50) t.update(1000) test.equal(obj.x, 100) t.update(1500) test.equal(obj.x, 50) t.update(2000) test.equal(obj.x, 0) test.done() }, 'Tween relative negative value, with yoyo'(test: Test): void { const obj = {x: 0}, t = new TWEEN.Tween(obj) t.to({x: '-100'}, 1000) t.repeat(1) t.yoyo(true) t.start(0) t.update(500) test.equal(obj.x, -50) t.update(1000) test.equal(obj.x, -100) t.update(1500) test.equal(obj.x, -50) t.update(2000) test.equal(obj.x, -0) test.done() }, 'Tween relative positive array interpolation values'(test: Test): void { const obj = {x: 0}, t = new TWEEN.Tween(obj) t.to({x: ['+100', '+0', '-100', '+0']}, 2000) t.start(0) t.update(250) test.equal(obj.x, 50) t.update(500) test.equal(obj.x, 100) t.update(750) test.equal(obj.x, 50) t.update(1000) test.equal(obj.x, 0) t.update(1250) test.equal(obj.x, -50) t.update(1500) test.equal(obj.x, -100) t.update(1750) test.equal(obj.x, -50) t.update(2000) test.equal(obj.x, 0) test.done() }, 'String values without a + or - sign should not be interpreted as relative with array interpolation values'( test: Test, ): void { const obj = {x: 0}, t = new TWEEN.Tween(obj) t.to({x: ['100', '0', '100', '0']}, 2000) t.start(0) t.update(250) test.equal(obj.x, 50) t.update(500) test.equal(obj.x, 100) t.update(750) test.equal(obj.x, 50) t.update(1000) test.equal(obj.x, 0) t.update(1250) test.equal(obj.x, 50) t.update(1500) test.equal(obj.x, 100) t.update(1750) test.equal(obj.x, 50) t.update(2000) test.equal(obj.x, 0) test.done() }, 'animate values in an array'(test: Test): void { const obj = [0, 0, 0], t = new TWEEN.Tween(obj) t.to([1000, '-2000', '+2000'], 1000) t.start(0) t.update(250) test.equal(obj[0], 250) test.equal(obj[1], -500) test.equal(obj[2], 500) t.update(500) test.equal(obj[0], 500) test.equal(obj[1], -1000) test.equal(obj[2], 1000) t.update(750) test.equal(obj[0], 750) test.equal(obj[1], -1500) test.equal(obj[2], 1500) t.update(1000) test.equal(obj[0], 1000) test.equal(obj[1], -2000) test.equal(obj[2], 2000) test.done() }, 'animate values in a nested array'(test: Test): void { const obj = {a: [0, 0, 0]}, t = new TWEEN.Tween(obj) t.to({a: [1000, '-2000', '+2000']}, 1000) t.start(0) t.update(250) test.equal(obj.a[0], 250) test.equal(obj.a[1], -500) test.equal(obj.a[2], 500) t.update(500) test.equal(obj.a[0], 500) test.equal(obj.a[1], -1000) test.equal(obj.a[2], 1000) t.update(750) test.equal(obj.a[0], 750) test.equal(obj.a[1], -1500) test.equal(obj.a[2], 1500) t.update(1000) test.equal(obj.a[0], 1000) test.equal(obj.a[1], -2000) test.equal(obj.a[2], 2000) test.done() }, 'Test TWEEN.Tween.start()'(test: Test): void { TWEEN.removeAll() const obj = {}, t = new TWEEN.Tween(obj, true) t.to({}, 1000) test.equal(TWEEN.getAll().length, 1) t.start(0) test.equal(TWEEN.getAll().length, 1) test.done() }, 'Ensure tweens start without calling start() method.'(test: Test): void { TWEEN.Tween.autoStartOnUpdate = true const obj = {x: 0}, t = new TWEEN.Tween(obj) t.to({x: 1000}, 1000) let started = false t.onStart(() => (started = true)) t.onComplete(() => (started = false)) t.update(0) test.deepEqual(started, true) test.deepEqual(obj.x, 0) t.update(500) test.deepEqual(started, true) test.deepEqual(obj.x, 500) t.update(1000) test.deepEqual(obj.x, 1000) test.deepEqual(started, false) TWEEN.Tween.autoStartOnUpdate = false test.done() }, 'Test Tween.to() tweening towards a dynamic object'(test: Test): void { const rabbit = {x: 1000, y: 0} const tr = new TWEEN.Tween(rabbit) tr.to({y: 1000}, 1000) tr.start(0) const fox = {x: 0, y: 0} const tf = new TWEEN.Tween(fox) tf.to(rabbit, 1000) // fox chase rabbit! tf.dynamic(true) tf.start(0) tr.update(200) tf.update(200) test.equal(rabbit.x, 1000) test.equal(rabbit.y, 200) test.equal(fox.x, 200) test.equal(fox.y, 40) tr.update(500) tf.update(500) test.equal(rabbit.x, 1000) test.equal(rabbit.y, 500) test.equal(fox.x, 500) test.equal(fox.y, 250) tr.update(800) tf.update(800) test.equal(rabbit.x, 1000) test.equal(rabbit.y, 800) test.equal(fox.x, 800) test.equal(fox.y, 640) tr.update(1000) tf.update(1000) test.equal(rabbit.x, 1000) test.equal(rabbit.y, 1000) test.equal(fox.x, 1000) test.equal(fox.y, 1000) test.done() }, 'Test TWEEN.Tween.stop()'(test: Test): void { TWEEN.removeAll() const obj = {}, t = new TWEEN.Tween(obj, true) t.to({x: 2}, 1000) t.start() test.equal(TWEEN.getAll().length, 1) t.stop() test.equal(TWEEN.getAll().length, 1) test.done() }, 'Test TWEEN.Tween.delay()'(test: Test): void { const obj = {x: 1}, t = new TWEEN.Tween(obj) t.to({x: 2}, 1000) t.delay(500) t.start(0) t.update(100) test.deepEqual(obj.x, 1, "Tween hasn't started yet") t.update(1000) test.ok(obj.x !== 1 && obj.x !== 2, "Tween has started but hasn't finished yet") t.update(1500) test.equal(obj.x, 2, 'Tween finishes when expected') test.done() }, // TODO: not really sure how to test this. Advice appreciated! 'Test TWEEN.Tween.easing()'(test: Test): void { const obj = {x: 0}, t = new TWEEN.Tween(obj) t.to({x: 1}, 1000) t.easing(TWEEN.Easing.Quadratic.In) t.start(0) t.update(500) test.equal(obj.x, TWEEN.Easing.Quadratic.In(0.5)) test.done() }, 'Test TWEEN.Tween.EasingFunctionGroup should be frozen'(test: Test): void { const replaceEasingFunction = (easingGroup: EasingFunctionGroup) => { const throwsWithReassigned = () => { easingGroup.In = (amount: number) => { return 1.0 + amount } easingGroup.Out = (amount: number) => { return 1.0 + amount } easingGroup.InOut = (amount: number) => { return 1.0 + amount } } test.throws(throwsWithReassigned) test.equal(easingGroup.In(0.0), 0.0) test.equal(easingGroup.Out(0.0), 0.0) test.equal(easingGroup.InOut(0.0), 0.0) test.equal(easingGroup.In(1.0), 1.0) test.equal(easingGroup.Out(1.0), 1.0) test.equal(easingGroup.InOut(1.0), 1.0) } const Easing = TWEEN.Easing const easingGroups = [ Easing.Quadratic, Easing.Cubic, Easing.Quartic, Easing.Quintic, Easing.Sinusoidal, Easing.Exponential, Easing.Circular, Easing.Elastic, Easing.Back, Easing.Bounce, ] easingGroups.forEach(replaceEasingFunction) test.done() }, 'Test TWEEN.Easing should starts at 0.0, ends at 1.0. TWEEN.Easing.InOut() should be 0.5 at midpoint'( test: Test, ): void { const checkEdgeValue = (ease: EasingFunctionGroup) => { test.equal(ease.In(0.0), 0.0) test.equal(ease.Out(0.0), 0.0) test.equal(ease.InOut(0.0), 0.0) test.equal(ease.In(1.0), 1.0) test.equal(ease.Out(1.0), 1.0) test.equal(ease.InOut(1.0), 1.0) test.equal(ease.InOut(0.5), 0.5) } checkEdgeValue(TWEEN.Easing.Quadratic) checkEdgeValue(TWEEN.Easing.Cubic) checkEdgeValue(TWEEN.Easing.Quartic) checkEdgeValue(TWEEN.Easing.Quintic) checkEdgeValue(TWEEN.Easing.Sinusoidal) checkEdgeValue(TWEEN.Easing.Exponential) checkEdgeValue(TWEEN.Easing.Circular) checkEdgeValue(TWEEN.Easing.Elastic) checkEdgeValue(TWEEN.Easing.Back) checkEdgeValue(TWEEN.Easing.Bounce) test.done() }, 'Test TWEEN.Easing should pass a specific value'(test: Test): void { const checkEasingGroupPassPoints = ( easingGroup: EasingFunctionGroup, expects: {In: number; Out: number; InOut: number}, ) => { checkPassPoint(easingGroup.In, expects.In) checkPassPoint(easingGroup.Out, expects.Out) checkPassPoint(easingGroup.InOut, expects.InOut) } const checkPassPoint = ( easeFunc: (amount: number) => number, expect: number, numDigits = 14, amount = Math.LOG10E, ) => { toBeCloseTo(test, easeFunc(amount), expect, numDigits) } checkEasingGroupPassPoints(TWEEN.Easing.Quadratic, { In: 0.18861169701161393, Out: 0.6799772667948897, InOut: 0.37722339402322785, }) checkEasingGroupPassPoints(TWEEN.Easing.Cubic, { In: 0.08191301923455198, Out: 0.8189613739094657, InOut: 0.3276520769382079, }) checkEasingGroupPassPoints(TWEEN.Easing.Quartic, { In: 0.035574372249600854, Out: 0.8975854502319308, InOut: 0.28459497799680683, }) checkEasingGroupPassPoints(TWEEN.Easing.Quintic, { In: 0.015449753565173821, Out: 0.9420635240628092, InOut: 0.24719605704278114, }) checkEasingGroupPassPoints(TWEEN.Easing.Sinusoidal, { In: 0.22380505208857682, Out: 0.630492983971101, InOut: 0.397521402836783, }) checkEasingGroupPassPoints(TWEEN.Easing.Exponential, { In: 0.01981785759600918, Out: 0.9507231043886069, InOut: 0.2010867096041978, }) checkEasingGroupPassPoints(TWEEN.Easing.Circular, { In: 0.09922905076352173, Out: 0.8246073409780499, InOut: 0.2522333699054974, }) checkEasingGroupPassPoints(TWEEN.Easing.Elastic, { In: -0.01701121590548648, Out: 0.9577017895937282, InOut: -0.09523991217687242, }) checkEasingGroupPassPoints(TWEEN.Easing.Back, { In: -0.09964331689734113, Out: 1.055453950893486, InOut: 0.19901899530677744, }) checkEasingGroupPassPoints(TWEEN.Easing.Bounce, { In: 0.24689860443452594, Out: 0.8434464829485027, InOut: 0.43470212148602316, }) test.done() }, 'Test TWEEN.interpolation should starts at values[0], ends at values[values.length-1].'(test: Test): void { const generateArray = (): number[] => { return [0, Math.PI, Math.SQRT2, Math.E] } const checkStartAndEnd = (interpolation: (v: number[], k: number) => number, values: number[]) => { const originalValue = values.concat() test.equal(interpolation(values, 0.0), originalValue[0]) test.equal(interpolation(values, 1.0), originalValue[originalValue.length - 1]) test.deepEqual(originalValue, values) } const Interpolations = [TWEEN.Interpolation.Linear, TWEEN.Interpolation.Bezier, TWEEN.Interpolation.CatmullRom] Interpolations.forEach(func => { checkStartAndEnd(func, generateArray()) }) test.done() }, 'Test TWEEN.interpolation.Bezier should return a value equal to Linear if there are two values.'(test: Test): void { const compareToLinear = (k: number) => { const Interpolation = TWEEN.Interpolation const values = [0, Math.E] test.equal(Interpolation.Bezier(values, k), Interpolation.Linear(values, k)) } compareToLinear(0.0) compareToLinear(0.5) compareToLinear(1.0) compareToLinear(Math.LOG10E) compareToLinear(Math.LN2) test.done() }, 'Test TWEEN.interpolation should pass a specific value.'(test: Test): void { const generateArray = (): number[] => { return [0, Math.PI, Math.SQRT2, Math.E] } const testInterpolationPath = ( interpolation: (v: number[], k: number) => number, values: number[], result: number, ) => { toBeCloseTo(test, interpolation(values, Math.LOG10E), result, 14) } testInterpolationPath(TWEEN.Interpolation.Linear, generateArray(), 2.618398122395094) testInterpolationPath(TWEEN.Interpolation.Bezier, generateArray(), 1.985241172928958) testInterpolationPath(TWEEN.Interpolation.CatmullRom, generateArray(), 2.879802635590904) test.done() }, 'Test TWEEN.Tween.chain --with one tween'(test: Test): void { TWEEN.removeAll() const t = new TWEEN.Tween({}, true), t2 = new TWEEN.Tween({}, true) let tStarted = false, tCompleted = false, t2Started = false t.to({}, 1000) t2.to({}, 1000) t.chain(t2) t.onStart(function (): void { tStarted = true }) t.onComplete(function (): void { tCompleted = true }) t2.onStart(function (): void { test.equal(tStarted, true) test.equal(tCompleted, true) test.equal(t2Started, false) t2Started = true }) test.equal(tStarted, false) test.equal(t2Started, false) t.start(0) TWEEN.update(0) test.equal(tStarted, true) test.equal(t2Started, false) TWEEN.update(1000) test.equal(tCompleted, true) TWEEN.update(1001) test.equal(t2Started, true, 't2 is automatically started by t') test.done() }, 'Test TWEEN.Tween.chain --with several tweens in an array'(test: Test): void { TWEEN.removeAll() const t = new TWEEN.Tween({}, true), chainedTweens: TWEEN.Tween[] = [], numChained = 3 let numChainedStarted = 0 t.to({}, 1000) function onChainedStart(): void { numChainedStarted++ } for (let i = 0; i < numChained; i++) { const chained = new TWEEN.Tween({}, true) chained.to({}, 1000) chainedTweens.push(chained) chained.onStart(onChainedStart) } t.chain(...chainedTweens) test.equal(numChainedStarted, 0) t.start(0) TWEEN.update(0) TWEEN.update(1000) TWEEN.update(1001) test.equal(numChainedStarted, numChained, 'All chained tweens have been started') test.done() }, 'Test TWEEN.Tween.chain allows endless loops'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t1 = new TWEEN.Tween(obj, true).to({x: 100}, 1000), t2 = new TWEEN.Tween(obj, true).to({x: 0}, 1000) t1.chain(t2) t2.chain(t1) test.equal(obj.x, 0) // x == 0 t1.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(500) test.equal(obj.x, 50) // there... (x == 100) TWEEN.update(1000) test.equal(obj.x, 100) TWEEN.update(1500) test.equal(obj.x, 50) // ... and back again (x == 0) TWEEN.update(2000) test.equal(obj.x, 0) TWEEN.update(2500) test.equal(obj.x, 50) TWEEN.update(3000) test.equal(obj.x, 100) // and x == 100 again // Repeat the same test but with the tweens added in the // opposite order. const obj2 = {x: 0} const t3 = new TWEEN.Tween(obj2, true).to({x: 200}, 1000) const t4 = new TWEEN.Tween(obj2, true).to({x: 100}, 1000) t4.chain(t3) t3.chain(t4) test.equal(obj2.x, 0) t4.start(0) TWEEN.update(0) test.equal(obj2.x, 0) TWEEN.update(500) test.equal(obj2.x, 50) TWEEN.update(1000) test.equal(obj2.x, 100) TWEEN.update(1500) test.equal(obj2.x, 150) TWEEN.update(2000) test.equal(obj2.x, 0) TWEEN.update(2500) test.equal(obj2.x, 50) TWEEN.update(3000) test.equal(obj2.x, 100) TWEEN.update(3500) test.equal(obj2.x, 150) TWEEN.update(4000) test.equal(obj2.x, 0) TWEEN.update(4500) test.equal(obj2.x, 50) test.done() }, 'Test TWEEN.Tween.startFromCurrentValues'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}) test.equal(obj.x, 0) // x == 0 t.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(1500) test.equal(obj.x, 100) obj.x = 200 t.startFromCurrentValues(0) TWEEN.update(0) test.equal(obj.x, 200) TWEEN.update(500) test.equal(obj.x, 150) TWEEN.update(1000) test.equal(obj.x, 100) test.done() }, 'Test TWEEN.Tween.onStart'(test: Test): void { TWEEN.removeAll() const obj = {}, t = new TWEEN.Tween(obj, true) let counter = 0 t.to({x: 2}, 1000) t.onStart(function (): void { test.ok(true, 'onStart callback is called') counter++ }) test.deepEqual(counter, 0) t.start(0) TWEEN.update(0) test.deepEqual(counter, 1) TWEEN.update(500) test.deepEqual(counter, 1, 'onStart callback is not called again') test.done() }, 'Test TWEEN.Tween.onEveryStart'(test: Test): void { TWEEN.removeAll() const obj = {}, t = new TWEEN.Tween(obj, true) let counter = 0 t.to({x: 2}, 500) t.delay(500) t.repeat(Infinity) t.onEveryStart(function (): void { counter++ }) test.deepEqual(counter, 0) t.start(0) TWEEN.update(0) test.deepEqual(counter, 0, 'onEveryStart callback not called before delayed start') TWEEN.update(500) test.deepEqual(counter, 1, 'onEveryStart callback called at delayed start') TWEEN.update(1000) test.deepEqual(counter, 1, 'onEveryStart callback not called before delayed repeat start') TWEEN.update(1500) test.deepEqual(counter, 2, 'onEveryStart callback called at delayed repeat start') test.done() }, 'Test TWEEN.Tween.onStop'(test: Test): void { TWEEN.removeAll() const obj = {}, t = new TWEEN.Tween(obj, true) let counter = 0 t.to({x: 2}, 1000) t.onStop(function (): void { test.ok(true, 'onStop callback is called') counter++ }) test.deepEqual(counter, 0) t.stop() TWEEN.update(0) test.deepEqual(counter, 0, "onStop callback not called when the tween hasn't started yet") t.start(0) TWEEN.update(0) t.stop() test.deepEqual(counter, 1, 'onStop callback is called if the tween has been started already and stop is invoked') TWEEN.update(500) t.stop() test.deepEqual(counter, 1, 'onStop callback is not called again once the tween is stopped') test.done() }, 'Test TWEEN.Tween.onUpdate'(test: Test): void { TWEEN.removeAll() const obj = {}, t = new TWEEN.Tween(obj, true) let counter = 0 t.to({x: 2}, 1000) t.onUpdate(function (): void { counter++ }) test.deepEqual(counter, 0) t.start(0) TWEEN.update(0) test.deepEqual(counter, 1) TWEEN.update(500) test.deepEqual(counter, 2) TWEEN.update(600) test.deepEqual(counter, 3) TWEEN.update(1000) test.deepEqual(counter, 4) TWEEN.update(1500) test.deepEqual(counter, 4, 'onUpdate callback should not be called after the tween has finished') test.done() }, 'Test TWEEN.Tween.onComplete'(test: Test): void { TWEEN.removeAll() const obj = {}, t = new TWEEN.Tween(obj, true) let counter = 0 t.to({x: 2}, 1000) t.onComplete(function (): void { counter++ }) test.deepEqual(counter, 0) t.start(0) TWEEN.update(0) test.deepEqual(counter, 0) TWEEN.update(500) test.deepEqual(counter, 0) TWEEN.update(600) test.deepEqual(counter, 0) TWEEN.update(1000) test.deepEqual(counter, 1) TWEEN.update(1500) test.deepEqual(counter, 1, 'onComplete callback must be called only once') test.done() }, 'TWEEN.Tween does not repeat by default'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100) t.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(50) test.equal(obj.x, 50) TWEEN.update(100) test.equal(obj.x, 100) TWEEN.update(150) test.equal(obj.x, 100) test.done() }, 'Test single repeat happens only once'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(1) t.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(50) test.equal(obj.x, 50) TWEEN.update(100) test.equal(obj.x, 100) TWEEN.update(150) test.equal(obj.x, 50) TWEEN.update(200) test.equal(obj.x, 100) test.done() }, 'Test Infinity repeat happens forever'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(Infinity) t.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(50) test.equal(obj.x, 50) TWEEN.update(100) test.equal(obj.x, 100) TWEEN.update(150) test.equal(obj.x, 50) TWEEN.update(200) test.equal(obj.x, 100) TWEEN.update(250) test.equal(obj.x, 50) test.done() }, 'Test tweening relatively with repeat'(test: Test): void { TWEEN.removeAll() const obj = {x: 0, y: 0}, t = new TWEEN.Tween(obj, true).to({x: '+100', y: '-100'}, 100).repeat(1) t.start(0) TWEEN.update(0) test.equal(obj.x, 0) test.equal(obj.y, 0) TWEEN.update(50) test.equal(obj.x, 50) test.equal(obj.y, -50) TWEEN.update(100) test.equal(obj.x, 100) test.equal(obj.y, -100) TWEEN.update(150) test.equal(obj.x, 150) test.equal(obj.y, -150) TWEEN.update(200) test.equal(obj.x, 200) test.equal(obj.y, -200) test.done() }, 'Test yoyo with repeat Infinity happens forever'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(Infinity).yoyo(true) t.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(25) test.equal(obj.x, 25) TWEEN.update(100) test.equal(obj.x, 100) TWEEN.update(125) test.equal(obj.x, 75) TWEEN.update(200) test.equal(obj.x, 0) TWEEN.update(225) test.equal(obj.x, 25) test.done() }, 'Test yoyo with repeat 1 happens once'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(1).yoyo(true) t.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(25) test.equal(obj.x, 25) TWEEN.update(100) test.equal(obj.x, 100) TWEEN.update(125) test.equal(obj.x, 75) TWEEN.update(200) test.equal(obj.x, 0) TWEEN.update(225) test.equal(obj.x, 0) test.done() }, 'Test yoyo works with arrays'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true) .to({x: [100, 200]}, 100) .repeat(1) .yoyo(true) t.start(0) TWEEN.update(50) test.equal(obj.x, 100) TWEEN.update(100) test.equal(obj.x, 200) TWEEN.update(150) test.equal(obj.x, 100) TWEEN.update(200) test.equal(obj.x, 0) test.done() }, 'Test yoyo can be stopped and restarted properly'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(1).yoyo(true) t.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(25) test.equal(obj.x, 25) TWEEN.update(100) test.equal(obj.x, 100) TWEEN.update(125) test.equal(obj.x, 75) t.stop() t.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(25) test.equal(obj.x, 25) TWEEN.update(100) test.equal(obj.x, 100) TWEEN.update(125) test.equal(obj.x, 75) TWEEN.update(200) test.equal(obj.x, 0) TWEEN.update(225) test.equal(obj.x, 0) test.done() }, 'Test TWEEN.Tween.stopChainedTweens()'(test: Test): void { TWEEN.removeAll() const t = new TWEEN.Tween({}, true), t2 = new TWEEN.Tween({}, true) let tStarted = false, tCompleted = false, t2Started = false t.to({}, 1000) t2.delay(500).to({}, 1000) t.chain(t2) t2.chain(t) t.onStart(function (): void { tStarted = true }) t.onComplete(function (): void { tCompleted = true }) t2.onStart(function (): void { test.equal(tStarted, true) test.equal(tCompleted, true) test.equal(t2Started, false) t2Started = true }) test.equal(tStarted, false) test.equal(t2Started, false) t.start(0) TWEEN.update(1001) t.stop() test.equal(tStarted, true) test.equal(t2Started, false) test.equal(TWEEN.getAll().length, 2) TWEEN.update(1501) test.equal(t2Started, false) test.done() }, 'Test TWEEN.Tween.chain progressess into chained tweens'(test: Test): void { TWEEN.removeAll() const obj = {t: 1000} // 1000 of nothing const blank = new TWEEN.Tween({}, true).to({}, 1000) // tween obj.t from 1000 -> 2000 (in time with update time) const next = new TWEEN.Tween(obj, true).to({t: 2000}, 1000) blank.chain(next).start(0) TWEEN.update(1500) test.equal(obj.t, 1500) TWEEN.update(2000) test.equal(obj.t, 2000) test.done() }, 'Test that TWEEN.Tween.end sets the final values.'(test: Test): void { const object1 = {x: 0, y: -50, z: 1000} const target1 = {x: 50, y: 123, z: '+234'} const tween1 = new TWEEN.Tween(object1).to(target1, 1000) tween1.start() tween1.end() test.equal(object1.x, 50) test.equal(object1.y, 123) test.equal(object1.z, 1234) const object2 = {x: 0, y: -50, z: 1000} const target2 = {x: 50, y: 123, z: '+234'} const tween2 = new TWEEN.Tween(object2).to(target2, 1000) tween2.start(300) tween2.update(500) tween2.end() test.equal(object2.x, 50) test.equal(object2.y, 123) test.equal(object2.z, 1234) test.done() }, 'Test that TWEEN.Tween.end calls the onComplete callback of the tween.'(test: Test): void { test.expect(1) const tween1 = new TWEEN.Tween({}).to({}, 1000).onComplete(function (): void { test.ok(true) }) tween1.start() tween1.end() test.done() }, 'Ensure Tween.end() works after stopping a tween.'(test: Test): void { const object = {x: 0, y: -50, z: 1000} const target = {x: 50, y: 123, z: '+234'} const tween = new TWEEN.Tween(object).to(target, 1000) tween.start(300) tween.update(500) tween.stop() tween.end() test.equal(object.x, 50) test.equal(object.y, 123) test.equal(object.z, 1234) test.done() }, 'Test delay adds delay before each repeat'(test: Test): void { // If repeatDelay isn't specified then delay is used since // that's the way it worked before repeatDelay was added. TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(1).delay(100) t.start(0) TWEEN.update(100) test.equal(obj.x, 0) TWEEN.update(150) test.equal(obj.x, 50) TWEEN.update(200) test.equal(obj.x, 100) TWEEN.update(250) test.equal(obj.x, 100) TWEEN.update(300) test.equal(obj.x, 0) TWEEN.update(350) test.equal(obj.x, 50) TWEEN.update(400) test.equal(obj.x, 100) test.done() }, 'Test repeatDelay adds delay before each repeat'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(1).repeatDelay(200) t.start(0) TWEEN.update(0) test.equal(obj.x, 0) TWEEN.update(50) test.equal(obj.x, 50) TWEEN.update(100) test.equal(obj.x, 100) TWEEN.update(200) test.equal(obj.x, 100) TWEEN.update(300) test.equal(obj.x, 0) TWEEN.update(350) test.equal(obj.x, 50) TWEEN.update(400) test.equal(obj.x, 100) test.done() }, 'Test repeatDelay and delay can be used together'(test: Test): void { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).delay(100).repeat(1).repeatDelay(200) t.start(0) TWEEN.update(100) test.equal(obj.x, 0) TWEEN.update(150) test.equal(obj.x, 50) TWEEN.update(200) test.equal(obj.x, 100) TWEEN.update(300) test.equal(obj.x, 100) TWEEN.update(400) test.equal(obj.x, 0) TWEEN.update(450) test.equal(obj.x, 50) TWEEN.update(500) test.equal(obj.x, 100) test.done() }, 'TWEEN.Tween.onRepeat should not be called if repeat = 0 or default'(test: Test): void { TWEEN.removeAll() const obj = {x: 0} let callbackCounter = 0 const t = new TWEEN.Tween(obj, true).to({x: 100}, 100).start(0) t.onRepeat(() => { callbackCounter++ }) TWEEN.update(0) test.equal(callbackCounter, 0) TWEEN.update(50) test.equal(callbackCounter, 0) TWEEN.update(100) test.equal(callbackCounter, 0) TWEEN.update(150) test.equal(callbackCounter, 0) test.ok(!t.isPlaying()) test.done() }, 'TWEEN.Tween.onRepeat should be called once if repeat = 1'(test: Test): void { TWEEN.removeAll() const obj = {x: 0} let callbackCounter = 0 const t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(1).start(0) t.onRepeat(() => { callbackCounter++ }) TWEEN.update(0) test.equal(callbackCounter, 0) TWEEN.update(50) test.equal(callbackCounter, 0) TWEEN.update(99.99999999) test.equal(callbackCounter, 0) TWEEN.update(100) test.equal(callbackCounter, 1) test.ok(t.isPlaying()) TWEEN.update(150) test.equal(callbackCounter, 1) TWEEN.update(200) test.equal(callbackCounter, 1) test.ok(!t.isPlaying()) test.done() }, 'TWEEN.Tween.onRepeat should be called every time if repeat = Infinity'(test: Test): void { TWEEN.removeAll() const obj = {x: 0} let callbackCounter = 0 const t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(Infinity).start(0) t.onRepeat(() => { callbackCounter++ }) const repeatTween = (repeatCount: number): void => { TWEEN.update(repeatCount * 100) test.equal(callbackCounter, repeatCount) TWEEN.update(50 + repeatCount * 100) test.equal(callbackCounter, repeatCount) TWEEN.update(99.99999999 + repeatCount * 100) test.equal(callbackCounter, repeatCount) TWEEN.update(100 + repeatCount * 100) test.equal(callbackCounter, repeatCount + 1) test.ok(t.isPlaying()) } for (let i = 0; i < 10; i++) { repeatTween(i) } test.done() }, 'TWEEN.Tween.onRepeat should not be called if Tween.pause() or Tween.stop(), and should be called after Tween.resume() or restart'( test: Test, ): void { TWEEN.removeAll() const generateTween = () => { const obj = {x: 0} const counter = {count: 0} const tween = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(Infinity).start(0) tween.onRepeat(() => { counter.count++ }) return { tween, counter, } } const tweenPause = generateTween() const tweenStop = generateTween() TWEEN.update(100) test.equal(tweenPause.counter.count, 1, 'repeats after first completion') test.equal(tweenStop.counter.count, 1, 'repeats after first completion') TWEEN.update(200) test.equal(tweenPause.counter.count, 2, 'repeats after second completion') test.equal(tweenStop.counter.count, 2, 'repeats after second completion') tweenPause.tween.pause(200) TWEEN.update(300) test.equal(tweenPause.counter.count, 2, 'tween is paused hence it did not repeat') test.equal(tweenStop.counter.count, 3, 'tween is not stopped hence it repeated') tweenPause.tween.resume(300) tweenStop.tween.stop() TWEEN.update(400) test.equal(tweenPause.counter.count, 3, 'tween is resumed so it repeated') test.equal(tweenStop.counter.count, 3, 'tween was stopped hence it did not repeat') tweenStop.tween.start(400) TWEEN.update(500) test.equal(tweenPause.counter.count, 4, 'tween is not paused hence it repeated') test.equal(tweenStop.counter.count, 4, 'tween is not stopped hence it repeated') test.done() }, 'If Tween.delay is set, TWEEN.Tween.onRepeat should be called when repeat section finished'(test: Test): void { TWEEN.removeAll() const obj = {x: 0} let callbackCounter = 0 const t = new TWEEN.Tween(obj, true).to({x: 100}, 100).delay(50).repeat(1).start(0) t.onRepeat(() => { callbackCounter++ }) TWEEN.update(0) test.equal(callbackCounter, 0) TWEEN.update(50) //start first section test.equal(obj.x, 0) test.equal(callbackCounter, 0) TWEEN.update(100) test.equal(obj.x, 50) test.equal(callbackCounter, 0) TWEEN.update(150) //first section is finished test.equal(obj.x, 100) test.equal(callbackCounter, 1) TWEEN.update(200) //restart test.equal(obj.x, 0) test.equal(callbackCounter, 1) TWEEN.update(250) test.equal(obj.x, 50) test.equal(callbackCounter, 1) TWEEN.update(300) //second section is finished test.equal(obj.x, 100) test.equal(callbackCounter, 1) test.ok(!t.isPlaying()) TWEEN.update(400) test.equal(obj.x, 100) test.equal(callbackCounter, 1) test.done() }, 'If Tween.repeatDelay is set, TWEEN.Tween.onRepeat should be called when repeat section finished'(test: Test): void { TWEEN.removeAll() const obj = {x: 0} let callbackCounter = 0 const t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeatDelay(100).repeat(1).start(0) t.onRepeat(() => { callbackCounter++ }) TWEEN.update(0) test.equal(callbackCounter, 0) TWEEN.update(50) test.equal(callbackCounter, 0) TWEEN.update(99.99999999) test.equal(callbackCounter, 0) TWEEN.update(100) //first section is finished test.equal(callbackCounter, 1) TWEEN.update(150) //delay test.equal(callbackCounter, 1) TWEEN.update(200) //restart test.equal(callbackCounter, 1) TWEEN.update(300) //second section is finished test.equal(callbackCounter, 1) test.ok(!t.isPlaying()) TWEEN.update(400) test.equal(callbackCounter, 1) test.done() }, 'Test TWEEN.update() should reduce the repeat count'(test: Test) { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(5).delay(100) t.start(0) TWEEN.update(100) // @ts-expect-error test.equal(t._repeat, 5) TWEEN.update(150) // @ts-expect-error test.equal(t._repeat, 5) TWEEN.update(200) // @ts-expect-error test.equal(t._repeat, 4) TWEEN.update(250) // @ts-expect-error test.equal(t._repeat, 4) TWEEN.update(300) // @ts-expect-error test.equal(t._repeat, 4) TWEEN.update(350) // @ts-expect-error test.equal(t._repeat, 4) TWEEN.update(400) // @ts-expect-error test.equal(t._repeat, 3) test.done() }, 'Test TWEEN.update() should reduce the repeat count multiple times'(test: Test) { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(5).delay(100) t.start(0) TWEEN.update(400) // @ts-expect-error test.equal(t._repeat, 3) test.done() }, 'Test browser tab sleep with delay'(test: Test) { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(Infinity).delay(100) t.start(0) TWEEN.update(350) test.equal(obj.x, 50) TWEEN.update(750) test.equal(obj.x, 50) test.done() }, 'Test browser tab sleep with repeatDelay'(test: Test) { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).repeat(1).repeatDelay(200) t.start(0) TWEEN.update(350) test.equal(obj.x, 50) TWEEN.update(600) test.equal(obj.x, 100) TWEEN.update(Infinity) test.equal(obj.x, 100) test.done() }, 'Test browser tab sleep with repeatDelay and delay'(test: Test) { TWEEN.removeAll() const obj = {x: 0}, t = new TWEEN.Tween(obj, true).to({x: 100}, 100).delay(100).repeat(1).repeatDelay(200) t.start(0) TWEEN.update(450) test.equal(obj.x, 50) TWEEN.update(500) test.equal(obj.x, 100) TWEEN.update(Infinity) test.equal(obj.x, 100) test.done() }, 'Tween.js compatible with Object.defineProperty getter / setters'(test: Test): void { TWEEN.removeAll() const obj = {_x: 0, x: 0} Object.defineProperty(obj, 'x', { get(): void { return this._x }, set(x): void { this._x = x }, }) test.equal(obj.x, 0) const t = new TWEEN.Tween(obj, true).to({x: 100}, 100) t.start(0) test.equal(obj.x, 0) TWEEN.update(37) test.equal(obj.x, 37) TWEEN.update(100) test.equal(obj.x, 100) TWEEN.update(115) test.equal(obj.x, 100) test.done() }, 'tween.isPlaying() is false before the tween starts'(test: Test): void { const t = new TWEEN.Tween({x: 0}).to({x: 1}, 100) test.equal(t.isPlaying(), false) test.done() }, 'tween.isPlaying() is true when a tween is started and before it ends'(test: Test): void { const t = new TWEEN.Tween({x: 0}).to({x: 1}, 100) t.start(0) test.equal(t.isPlaying(), true) test.done() }, 'tween.isPlaying() is false after a tween ends'(test: Test): void { TWEEN.removeAll() const t = new TWEEN.Tween({x: 0}, true).to({x: 1}, 100) t.start(0) TWEEN.update(150) test.equal(t.isPlaying(), false) test.done() }, 'A zero-duration tween finishes at its starting time without an error.'(test: Test): void { TWEEN.removeAll() const object = {x: 0} const t = new TWEEN.Tween(object, true).to({x: 1}, 0) t.start(0) TWEEN.update(0) test.equal(t.isPlaying(), false) test.equal(object.x, 1) test.done() }, // Custom TWEEN.Group tests 'Custom group.getAll()'(test: Test): void { const group = new TWEEN.Group() test.ok(group.getAll() instanceof Array) test.done() }, 'Custom group.onComplete() should be triggered when all Tweens in the group have reached their completion, and the child Tween.onComplete() should also be fired'( test: Test, ): void { TWEEN.removeAll() const t = new TWEEN.Tween({x: 1}), t2 = new TWEEN.Tween({x: 1}), t3 = new TWEEN.Tween({x: 1}), group = new TWEEN.Group() let groupCounter = 0, childCounter = 0, childCounter2 = 0, childCounter3 = 0 group.add(t) group.add(t2) group.add(t3) t.to({x: 2}, 1000) t2.to({x: 2}, 2000) t3.to({x: 2}, 3000) t.onComplete(function (): void { childCounter++ }) t2.onComplete(function (): void { childCounter2++ }) t3.onComplete(function (): void { childCounter3++ }) group.onComplete(function (): void { groupCounter++ }) t.start(0) t2.start(0) t3.start(0) group.update(0) test.deepEqual(groupCounter, 0) test.deepEqual(childCounter, 0) test.deepEqual(childCounter2, 0) test.deepEqual(childCounter3, 0) group.update(1000) test.deepEqual(groupCounter, 0) test.deepEqual(childCounter, 1) test.deepEqual(childCounter2, 0) test.deepEqual(childCounter3, 0) group.update(2000) test.deepEqual(childCounter, 1) test.deepEqual(groupCounter, 0) test.deepEqual(childCounter2, 1) test.deepEqual(childCounter3, 0) group.update(3000) test.deepEqual(groupCounter, 1) test.deepEqual(childCounter, 1) test.deepEqual(childCounter2, 1) test.deepEqual(childCounter3, 1) test.done() }, 'Custom group stores tweens instead of global TWEEN group'(test: Test): void { const group = new TWEEN.Group() const numGlobalTweensBefore = TWEEN.getAll().length const numGroupTweensBefore = group.getAll().length const globalTween = new TWEEN.Tween({}, true) const groupTweenA = new TWEEN.Tween({}, group) const groupTweenB = new TWEEN.Tween({}, group) globalTween.start() groupTweenA.start() groupTweenB.start() test.equal(TWEEN.getAll().length, numGlobalTweensBefore + 1) test.equal(group.getAll().length, numGroupTweensBefore + 2) test.done() }, "Custom group.removeAll() doesn't conflict with global TWEEN group"(test: Test): void { const group = new TWEEN.Group() TWEEN.removeAll() group.removeAll() test.equal(TWEEN.getAll().length, 0, 'No global tweens left') test.equal(group.getAll().length, 0, 'No group tweens left') const globalTween = new TWEEN.Tween({}, true) const groupTweenA = new TWEEN.Tween({}, group) const groupTweenB = new TWEEN.Tween({}, group) globalTween.start() groupTweenA.start() groupTweenB.start() test.equal(TWEEN.getAll().length, 1, 'One global tween has been added') test.equal(group.getAll().length, 2, 'Two group tweens have been added') group.removeAll() test.equal(TWEEN.getAll().length, 1, 'One global tween left') test.equal(group.getAll().length, 0, 'No group tweens left') TWEEN.removeAll() test.equal(TWEEN.getAll().length, 0, 'No global tweens left') test.done() }, "Global TWEEN.removeAll() doesn't conflict with custom group"(test: Test): void { const group = new TWEEN.Group() TWEEN.removeAll() group.removeAll() test.equal(TWEEN.getAll().length, 0, 'No global tweens left') test.equal(group.getAll().length, 0, 'No group tweens left') const globalTween = new TWEEN.Tween({}, true) const groupTweenA = new TWEEN.Tween({}, group) const groupTweenB = new TWEEN.Tween({}, group) globalTween.start() groupTweenA.start() groupTweenB.start() test.equal(TWEEN.getAll().length, 1, 'One global tween has been added') test.equal(group.getAll().length, 2, 'Two group tweens have been added') TWEEN.removeAll() test.equal(TWEEN.getAll().length, 0, 'No global tweens left') test.equal(group.getAll().length, 2, 'Two group tweens left') group.removeAll() test.equal(group.getAll().length, 0, 'No group tweens left') test.done() }, "Custom group.add() doesn't conflict with global TWEEN group, or vice versa"(test: Test): void { const group = new TWEEN.Group() const globalTween = new TWEEN.Tween({}, true) const groupTweenA = new TWEEN.Tween({}, group) const groupTweenB = new TWEEN.Tween({}, group) TWEEN.add(globalTween) group.add(groupTweenA) group.add(groupTweenB) test.equal(TWEEN.getAll().length, 1) test.equal(group.getAll().length, 2) test.done() }, "Custom group.update() doesn't conflict with global TWEEN group"(test: Test): void { const group = new TWEEN.Group() const startObj = {x: 1} const endObj = {x: 2} const duration = 1000 const globalObj = {x: 1} new TWEEN.Tween(globalObj, true).to(endObj, duration).start(0) const groupObj = {x: 1} new TWEEN.Tween(groupObj, group).to(endObj, duration).start(0) group.update(duration) test.deepEqual(globalObj, startObj) test.deepEqual(groupObj, endObj) test.done() }, "Global TWEEN.update() doesn't conflict with custom group"(test: Test): void { const group = new TWEEN.Group() const startObj = {x: 1} const endObj = {x: 2} const duration = 1000 const globalObj = {x: 1} new TWEEN.Tween(globalObj, true).to(endObj, duration).start(0) const groupObj = {x: 1} new TWEEN.Tween(groupObj, group).to(endObj, duration).start(0) TWEEN.update(duration) test.deepEqual(globalObj, endObj) test.deepEqual(groupObj, startObj) test.done() }, 'Ensure tweens work without any group'(test: Test): void { const obj = {x: 0}, t = new TWEEN.Tween(obj) t.to({x: 1000}, 1000) t.start(0) test.equal(obj.x, 0) t.update(500) test.equal(obj.x, 500) t.pause(600) test.equal(obj.x, 500) t.update(750) test.equal(obj.x, 500) t.resume(800) test.equal(obj.x, 500) t.update(1000) test.equal(obj.x, 800) t.update(1001) test.equal(obj.x, 801) t.stop().end() test.equal(obj.x, 1000) test.done() }, 'Stopping a tween within an update callback will not cause an error.'(test: Test): void { TWEEN.removeAll() const tweenA = new TWEEN.Tween({x: 1, y: 2}, true) .to({x: 3, y: 4}, 1000) .onUpdate(function (): void { tweenB.stop() }) .start(0) const tweenB = new TWEEN.Tween({x: 5, y: 6}, true) .to({x: 7, y: 8}) .onUpdate(function (): void { tweenA.stop() }) .start(0) let success = true try { TWEEN.update(500) } catch (exception) { success = false } finally { test.ok(success) test.done() } }, 'Set the duration with .duration'(test: Test): void { const obj = {x: 1} const t = new TWEEN.Tween(obj).to({x: 2}).duration(1000).start(0) t.update(1000) test.deepEqual(obj.x, 2) test.done() }, 'Get the duration with .getDuration'(test: Test): void { const obj = {x: 1} const t = new TWEEN.Tween(obj).to({x: 2}).duration(100) test.deepEqual(t.getDuration(), 100) test.done() }, "Tween.group sets the tween's group."(test: Test): void { const group = new TWEEN.Group() const groupTweenA = new TWEEN.Tween({}).group(group) groupTweenA.start() test.equal(group.getAll().length, 1) test.done() }, 'Test TWEEN.Tween.pause() and TWEEN.Tween.resume()'(test: Test): void { TWEEN.removeAll() const obj = {x: 0.0}, t = new TWEEN.Tween(obj, true) t.to({x: 1.0}, 1000) test.equal(TWEEN.getAll().length, 1) t.start(0) test.equal(t.isPaused(), false) TWEEN.update(400) test.equal(obj.x, 0.4) t.pause(450) test.equal(t.isPaused(), true) test.equal(TWEEN.getAll().length, 1) test.equal(obj.x, 0.4) TWEEN.update(900) test.equal(obj.x, 0.4) TWEEN.update(3000) test.equal(obj.x, 0.4) t.resume(3200) // values do not change until an update test.equal(obj.x, 0.4) test.equal(TWEEN.getAll().length, 1) test.equal(t.isPaused(), false) TWEEN.update(3500) test.equal(obj.x, 0.75) TWEEN.update(5000) test.equal(obj.x, 1.0) test.done() }, 'Test TWEEN.Tween.pause() and TWEEN.Tween.resume(), without groups'(test: Test): void { const obj = {x: 0.0}, t = new TWEEN.Tween(obj) t.to({x: 1.0}, 1000) t.start(0) test.equal(t.isPaused(), false) t.update(400) test.equal(obj.x, 0.4) t.pause(450) test.equal(t.isPaused(), true) test.equal(obj.x, 0.4) t.update(900) test.equal(obj.x, 0.4) t.update(3000) test.equal(obj.x, 0.4) t.resume(3200) // values do not change until an update test.equal(obj.x, 0.4) test.equal(t.isPaused(), false) t.update(3500) test.equal(obj.x, 0.75) t.update(5000) test.equal(obj.x, 1.0) test.done() }, 'Arrays in the object passed to to() are not modified by start() if dynamic is false.'(test: Test): void { const start = {x: 10, y: 20, z: 30} const end = {x: 100, y: 200, z: ['+10', '-10']} const valuesArray = end.z new TWEEN.Tween(start).to(end).start() test.equal(valuesArray, end.z) test.equal(end.z.length, 2) test.equal(end.z[0], '+10') test.equal(end.z[1], '-10') test.done() }, 'Arrays in the object passed to to() are modified by start() if dynamic is true.'(test: Test): void { const start = {x: 10, y: 20, z: 30} const end = {x: 100, y: 200, z: ['+10', '-10']} const valuesArray = end.z test.equal(end.z.length, 2) new TWEEN.Tween(start).to(end).dynamic(true).start() test.notEqual(valuesArray, end.z) test.equal(end.z.length, 3) test.equal(end.z[0], 30) test.equal(end.z[1], 40) test.equal(end.z[2], 20) test.done() }, 'Arrays in the object passed to to() are not modified by start() if they are not interpolation arrays, regardless of dynamic.'( test: Test, ): void { // eslint-disable-next-line function testWithDynamic(start: any, end: any, dynamic: boolean): void { // const start = {x: 10, y: 20, z: [1, 2]} // const end = {x: 100, y: 200, z: ['a', 'b']} const valuesArray = end.z new TWEEN.Tween(start).to(end).dynamic(dynamic).start() test.equal(valuesArray, end.z) test.equal(end.z.length, 2) test.equal(end.z[0], 'a') test.equal(end.z[1], 'b') } testWithDynamic({x: 10, y: 20, z: [1, 2]}, {x: 100, y: 200, z: ['a', 'b']}, true) testWithDynamic({x: 10, y: 20, z: [1, 2]}, {x: 100, y: 200, z: ['a', 'b']}, false) testWithDynamic({x: 10, y: 20, z: 30}, {x: 100, y: 200, z: ['a', 'b']}, true) testWithDynamic({x: 10, y: 20, z: 30}, {x: 100, y: 200, z: ['a', 'b']}, false) test.done() }, 'Tween.js animate nested object'(test: Test): void { TWEEN.removeAll() const obj = {scale: {x: 0}, alpha: 0} const t = new TWEEN.Tween(obj, true).to({scale: {x: 100}, alpha: 100}, 100) t.start(0) test.equal(obj.scale.x, 0) TWEEN.update(37) test.equal(obj.scale.x, 37) test.equal(obj.alpha, 37) TWEEN.update(100) test.equal(obj.scale.x, 100) test.equal(obj.alpha, 100) TWEEN.update(115) test.equal(obj.scale.x, 100) test.equal(obj.alpha, 100) test.done() }, 'Tween.js animate nested object including relative value'(test: Test): void { TWEEN.removeAll() const obj = {world: {hero: {scale: {x: 0}, x: 100}}, time: 0} const t = new TWEEN.Tween(obj, true).to({world: {hero: {scale: {x: 100}, x: '+100'}}, time: 100}, 100) t.start(0) test.equal(obj.world.hero.scale.x, 0) TWEEN.update(37) test.equal(obj.world.hero.scale.x, 37) test.equal(obj.world.hero.x, 137) test.equal(obj.time, 37) TWEEN.update(100) test.equal(obj.world.hero.scale.x, 100) test.equal(obj.world.hero.x, 200) test.equal(obj.time, 100) TWEEN.update(115) test.equal(obj.world.hero.scale.x, 100) test.equal(obj.world.hero.x, 200) test.equal(obj.time, 100) test.done() }, 'Test TWEEN.Tween with nested objects'(test: Test): void { TWEEN.removeAll() const obj = {x: 0.0, y: 100, some: {value: 0.0, style: {opacity: 1.0}}}, t = new TWEEN.Tween(obj, true) t.to({x: 1.0, y: 200, some: {value: 1.0, style: {opacity: 0.5}}}, 1000) test.equal(TWEEN.getAll().length, 1) t.start(0) test.equal(TWEEN.getAll().length, 1) test.equal(t.isPaused(), false) TWEEN.update(400) test.equal(obj.x, 0.4) test.equal(obj.y, 140) test.equal(obj.some.style.opacity, 0.8) test.equal(obj.some.value, 0.4) TWEEN.update(750) test.equal(obj.x, 0.75) test.equal(obj.y, 175) test.equal(obj.some.style.opacity, 0.625) test.equal(obj.some.value, 0.75) TWEEN.update(1000) test.equal(obj.x, 1.0) test.equal(obj.y, 200) test.equal(obj.some.style.opacity, 0.5) test.equal(obj.some.value, 1.0) test.done() }, 'Test TWEEN.Tween.pause() and .resume() with nested objects'(test: Test): void { TWEEN.removeAll() const obj = {x: 0.0, y: 100, some: {value: 0.0}}, t = new TWEEN.Tween(obj, true) t.to({x: 1.0, y: 200, some: {value: 1.0}}, 1000) test.equal(TWEEN.getAll().length, 1) t.start(0) test.equal(TWEEN.getAll().length, 1) test.equal(t.isPaused(), false) TWEEN.update(400) test.equal(obj.x, 0.4) test.equal(obj.y, 140) test.equal(obj.some.value, 0.4) t.pause(450) test.equal(t.isPaused(), true) test.equal(TWEEN.getAll().length, 1) test.equal(obj.x, 0.4) test.equal(obj.y, 140) test.equal(obj.some.value, 0.4) TWEEN.update(900) test.equal(obj.x, 0.4) test.equal(obj.y, 140) test.equal(obj.some.value, 0.4) TWEEN.update(3000) test.equal(obj.x, 0.4) test.equal(obj.y, 140) test.equal(obj.some.value, 0.4) t.resume(3200) // values do not change until an update test.equal(obj.x, 0.4) test.equal(obj.y, 140) test.equal(obj.some.value, 0.4) test.equal(TWEEN.getAll().length, 1) test.equal(t.isPaused(), false) TWEEN.update(3500) test.equal(obj.x, 0.75) test.equal(obj.y, 175) test.equal(obj.some.value, 0.75) TWEEN.update(5000) test.equal(obj.x, 1.0) test.equal(obj.y, 200) test.equal(obj.some.value, 1.0) test.done() }, 'Test TWEEN.Easing.generatePow(1) equals Linear'(test: Test): void { const ease1 = TWEEN.Easing.generatePow(1) const compareWithLinear = (ease: EasingFunctionGroup, amount: number) => { const linearResult = TWEEN.Easing.Linear.None(amount) test.equal(linearResult, ease.In(amount)) test.equal(linearResult, ease.Out(amount)) test.equal(linearResult, ease.InOut(amount)) } compareWithLinear(ease1, 0) compareWithLinear(ease1, 0.25) compareWithLinear(ease1, 0.5) compareWithLinear(ease1, 0.75) compareWithLinear(ease1, 1) compareWithLinear(ease1, -1) compareWithLinear(ease1, Infinity) test.done() }, 'Test TWEEN.Easing.generatePow(n) should pass 0.0, 0.5, 1.0'(test: Test): void { const checkEdgeValue = (ease: EasingFunctionGroup) => { test.equal(ease.InOut(0.0), 0.0) test.equal(ease.In(0.0), 0.0) test.equal(ease.Out(0.0), 0.0) test.equal(ease.InOut(0.5), 0.5) test.equal(ease.InOut(1.0), 1.0) test.equal(ease.In(1.0), 1.0) test.equal(ease.Out(1.0), 1.0) } checkEdgeValue(TWEEN.Easing.generatePow(Number.NEGATIVE_INFINITY)) checkEdgeValue(TWEEN.Easing.generatePow(-1.0)) checkEdgeValue(TWEEN.Easing.generatePow(1)) checkEdgeValue(TWEEN.Easing.generatePow(Math.LOG2E)) checkEdgeValue(TWEEN.Easing.generatePow(Math.PI)) checkEdgeValue(TWEEN.Easing.generatePow()) checkEdgeValue(TWEEN.Easing.generatePow(6)) checkEdgeValue(TWEEN.Easing.generatePow(Number.POSITIVE_INFINITY)) test.done() }, "Test TWEEN.to(ends) shouldn't grow endless on ends value"(test: Test): void { TWEEN.removeAll() const target = {y: 0} const ends = {y: [100, 200]} const tween = new TWEEN.Tween(target, true).to(ends, 1000) tween.stop().start(0) tween.stop().start(0) TWEEN.update(250) test.equal(target.y, 50) test.done() }, 'Test TWEEN.Tween.to() with a dynamic target provided as object'(test: Test): void { TWEEN.removeAll() const dynamicTargetValue = {x: 5} const chasingValue = {x: 0} const duration = 1000 // must be even const t1 = new TWEEN.Tween(dynamicTargetValue, true).to({x: 10}, duration), t2 = new TWEEN.Tween(chasingValue, true).to(dynamicTargetValue, duration).dynamic(true) test.equal(TWEEN.getAll().length, 2) t1.start(0) t2.start(0) test.notDeepEqual(chasingValue, dynamicTargetValue) TWEEN.update(duration / 2, true) test.notDeepEqual(chasingValue, dynamicTargetValue) TWEEN.update(duration, true) test.deepEqual(chasingValue, dynamicTargetValue) test.done() }, 'Test TWEEN.Tween.to() with a dynamic target provided as array': function (test: Test): void { TWEEN.removeAll() const dynamicTargetValue = [5] const chasingValue = [0] const duration = 1000 // must be even const t1 = new TWEEN.Tween(dynamicTargetValue, true).to([10], duration), t2 = new TWEEN.Tween(chasingValue, true).to(dynamicTargetValue, duration).dynamic(true) test.equal(TWEEN.getAll().length, 2) t1.start(0) t2.start(0) test.notDeepEqual(chasingValue, dynamicTargetValue) TWEEN.update(duration / 2, true) test.notDeepEqual(chasingValue, dynamicTargetValue) TWEEN.update(duration, true) test.deepEqual(chasingValue, dynamicTargetValue) test.done() }, 'Test TWEEN.Tween.to() with multiple dynamic targets provided as array': function (test: Test): void { TWEEN.removeAll() const dynamicTargetValues = {x: [4, 10, 12, 20]} const chasingValue = {x: 0} const duration = 1000 // must be even const tweens = [] const observedValues = [] for (let i = 0; i < dynamicTargetValues.x.length; i++) { const initialValue = {x: 0} observedValues.push(initialValue) tweens.push( new TWEEN.Tween(initialValue, true).to({x: dynamicTargetValues.x[i]}, duration).onUpdate(function (object) { // TODO the fact that we need `index + 1` instead of just // `index` here is confusing. It is because Tween adds an // axtra start value at the beginning of the array. Update // Tween so it does not add the start value to the array, // and instead reads it from _valuesStart. dynamicTargetValues.x[i + 1] = object.x }), ) } const t = new TWEEN.Tween(chasingValue, true).to(dynamicTargetValues, duration).dynamic(true) test.equal(TWEEN.getAll().length, 5) tweens.forEach(tween => tween.start(0)) t.start(0) test.equal(TWEEN.getAll().length, tweens.length + 1) for (let i = 0; i < tweens.length; i++) { const progress = ((i + 1) * duration) / tweens.length TWEEN.update(progress, true) test.equal(chasingValue.x, observedValues[i].x) } test.done() }, 'Test TWEEN.Tween.update() with no arguments'(test: Test): void { patchPerformanceNow() const targetNow = {x: 0.0} const targetTime = {x: 0.0} const tweenNow = new TWEEN.Tween(targetNow).to({x: 1.0}).start() const tweenTime = new TWEEN.Tween(targetTime).to({x: 1.0}).start(0) const tick = (time: number) => { tickTime(time) tweenNow.update() tweenTime.update(time) test.equal(targetNow.x, targetTime.x) } tick(0) tick(16) tick(16.66) tick(100) tick(20000) restorePerformanceNow() test.done() }, } type Test = { ok(a: unknown, failMessage?: string): void equal(a: unknown, b: unknown, failMessage?: string): void notEqual(a: unknown, b: unknown, failMessage?: string): void deepEqual(a: unknown, b: unknown, failMessage?: string): void notDeepEqual(a: unknown, b: unknown, failMessage?: string): void expect(n: number): void throws(block: unknown, error?: unknown, message?: string): void done(): void } function toBeCloseTo(test: Test, numberA: number, numberB: number, numDigits = 2): void { const diff = Math.abs(numberA - numberB) test.ok( diff < 10 ** -numDigits / 2, ` actual : ${numberA} expect : ${numberB} diff : ${diff}`, ) } // TODO test that starting and stopping a tween multiple times doesn't cause // interpolation arrays to modified yet again (and similar with other // initialization items). Initialization should happen only once, on first // start. // TODO test onRepeat ================================================ FILE: test/unit/nodeunit.html ================================================ nodeunit based tests ================================================ FILE: test/unit/nodeunit.js ================================================ /*! * Nodeunit * https://github.com/caolan/nodeunit * Copyright (c) 2010 Caolan McMahon * MIT Licensed * * json2.js * http://www.JSON.org/json2.js * Public Domain. * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. */ nodeunit = (function () { /* http://www.JSON.org/json2.js 2010-11-17 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See http://www.JSON.org/js.html This code should be minified before deployment. See http://javascript.crockford.com/jsmin.html USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO NOT CONTROL. This file creates a global JSON object containing two methods: stringify and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object values are stringified for objects. It can be a function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each level. If it is a string (such as '\t' or ' '), it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, or undefined if nothing should be serialized. The toJSON method will be passed the key associated with the value, and this will be bound to the value For example, this would serialize Dates as ISO strings. Date.prototype.toJSON = function (key) { function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing object. The value that is returned from your method will be serialized. If your method returns undefined, then the member will be excluded from the serialization. If the replacer parameter is an array of strings, then it will be used to select the members to be serialized. It filters the results such that only members with keys listed in the replacer array are stringified. Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. The optional space parameter produces a stringification of the value that is filled with line breaks and indentation to make it easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then the indentation will be that many spaces. Example: text = JSON.stringify(['e', {pluribus: 'unum'}]); // text is '["e",{"pluribus":"unum"}]' text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' text = JSON.stringify([new Date()], function (key, value) { return this[key] instanceof Date ? 'Date(' + this[key] + ')' : value; }); // text is '["Date(---current time---)"]' JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and transform the results. It receives each of the keys and values, and its return value is used instead of the original value. If it returns what it received, then the structure is not modified. If it returns undefined then the member is deleted. Example: // Parse the text. Values that look like ISO date strings will // be converted to Date objects. myData = JSON.parse(text, function (key, value) { var a; if (typeof value === 'string') { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); if (a) { return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); } } return value; }); myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { var d; if (typeof value === 'string' && value.slice(0, 5) === 'Date(' && value.slice(-1) === ')') { d = new Date(value.slice(5, -1)); if (d) { return d; } } return value; }); This is a reference implementation. You are free to copy, modify, or redistribute. */ /*jslint evil: true, strict: false, regexp: false */ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, prototype, push, replace, slice, stringify, test, toJSON, toString, valueOf */ // Create a JSON object only if one does not already exist. We create the // methods in a closure to avoid creating global variables. var JSON = {} ;(function () { 'use strict' function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function (key) { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null } String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { return this.valueOf() } } var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\', }, rep function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0 return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a] return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) }) + '"' : '"' + string + '"' } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key] // If the value has a toJSON method, call it to obtain a replacement value. if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key) } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value) } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value) case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null' case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value) // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null' } // Make an array to hold the partial results of stringifying this object value. gap += indent partial = [] // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null' } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']' gap = mind return v } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length for (i = 0; i < length; i += 1) { k = rep[i] if (typeof k === 'string') { v = str(k, value) if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v) } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = str(k, value) if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v) } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}' gap = mind return v } } // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i gap = '' indent = '' // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' ' } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer if ( replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number') ) { throw new Error('JSON.stringify') } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}) } } // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key] if (value && typeof value === 'object') { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = walk(value, k) if (v !== undefined) { value[k] = v } else { delete value[k] } } } } return reviver.call(holder, key, value) } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. text = String(text) cx.lastIndex = 0 if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) }) } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if ( /^[\],:{}\s]*$/.test( text .replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') .replace(/(?:^|:|,)(?:\s*\[)+/g, ''), ) ) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')') // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse') } } })() var assert = (this.assert = {}) var types = {} var core = {} var nodeunit = {} var reporter = {} /*global setTimeout: false, console: false */ ;(function () { var async = {} // global on the server, window in the browser var root = this, previous_async = root.async if (typeof module !== 'undefined' && module.exports) { module.exports = async } else { root.async = async } async.noConflict = function () { root.async = previous_async return async } //// cross-browser compatiblity functions //// var _forEach = function (arr, iterator) { if (arr.forEach) { return arr.forEach(iterator) } for (var i = 0; i < arr.length; i += 1) { iterator(arr[i], i, arr) } } var _map = function (arr, iterator) { if (arr.map) { return arr.map(iterator) } var results = [] _forEach(arr, function (x, i, a) { results.push(iterator(x, i, a)) }) return results } var _reduce = function (arr, iterator, memo) { if (arr.reduce) { return arr.reduce(iterator, memo) } _forEach(arr, function (x, i, a) { memo = iterator(memo, x, i, a) }) return memo } var _keys = function (obj) { if (Object.keys) { return Object.keys(obj) } var keys = [] for (var k in obj) { if (obj.hasOwnProperty(k)) { keys.push(k) } } return keys } var _indexOf = function (arr, item) { if (arr.indexOf) { return arr.indexOf(item) } for (var i = 0; i < arr.length; i += 1) { if (arr[i] === item) { return i } } return -1 } //// exported async module functions //// //// nextTick implementation with browser-compatible fallback //// if (typeof setImmediate === 'function') { async.nextTick = function (fn) { setImmediate(fn) } } else if (typeof process !== 'undefined' && process.nextTick) { async.nextTick = process.nextTick } else { async.nextTick = function (fn) { setTimeout(fn, 0) } } async.forEach = function (arr, iterator, callback) { if (!arr.length) { return callback() } var completed = 0 _forEach(arr, function (x) { iterator(x, function (err) { if (err) { callback(err) callback = function () {} } else { completed += 1 if (completed === arr.length) { callback() } } }) }) } async.forEachSeries = function (arr, iterator, callback) { if (!arr.length) { return callback() } var completed = 0 var iterate = function () { iterator(arr[completed], function (err) { if (err) { callback(err) callback = function () {} } else { completed += 1 if (completed === arr.length) { callback() } else { iterate() } } }) } iterate() } var doParallel = function (fn) { return function () { var args = Array.prototype.slice.call(arguments) return fn.apply(null, [async.forEach].concat(args)) } } var doSeries = function (fn) { return function () { var args = Array.prototype.slice.call(arguments) return fn.apply(null, [async.forEachSeries].concat(args)) } } var _asyncMap = function (eachfn, arr, iterator, callback) { var results = [] arr = _map(arr, function (x, i) { return {index: i, value: x} }) eachfn( arr, function (x, callback) { iterator(x.value, function (err, v) { results[x.index] = v callback(err) }) }, function (err) { callback(err, results) }, ) } async.map = doParallel(_asyncMap) async.mapSeries = doSeries(_asyncMap) // reduce only has a series version, as doing reduce in parallel won't // work in many situations. async.reduce = function (arr, memo, iterator, callback) { async.forEachSeries( arr, function (x, callback) { iterator(memo, x, function (err, v) { memo = v callback(err) }) }, function (err) { callback(err, memo) }, ) } // inject alias async.inject = async.reduce // foldl alias async.foldl = async.reduce async.reduceRight = function (arr, memo, iterator, callback) { var reversed = _map(arr, function (x) { return x }).reverse() async.reduce(reversed, memo, iterator, callback) } // foldr alias async.foldr = async.reduceRight var _filter = function (eachfn, arr, iterator, callback) { var results = [] arr = _map(arr, function (x, i) { return {index: i, value: x} }) eachfn( arr, function (x, callback) { iterator(x.value, function (v) { if (v) { results.push(x) } callback() }) }, function (err) { callback( _map( results.sort(function (a, b) { return a.index - b.index }), function (x) { return x.value }, ), ) }, ) } async.filter = doParallel(_filter) async.filterSeries = doSeries(_filter) // select alias async.select = async.filter async.selectSeries = async.filterSeries var _reject = function (eachfn, arr, iterator, callback) { var results = [] arr = _map(arr, function (x, i) { return {index: i, value: x} }) eachfn( arr, function (x, callback) { iterator(x.value, function (v) { if (!v) { results.push(x) } callback() }) }, function (err) { callback( _map( results.sort(function (a, b) { return a.index - b.index }), function (x) { return x.value }, ), ) }, ) } async.reject = doParallel(_reject) async.rejectSeries = doSeries(_reject) var _detect = function (eachfn, arr, iterator, main_callback) { eachfn( arr, function (x, callback) { iterator(x, function (result) { if (result) { main_callback(x) } else { callback() } }) }, function (err) { main_callback() }, ) } async.detect = doParallel(_detect) async.detectSeries = doSeries(_detect) async.some = function (arr, iterator, main_callback) { async.forEach( arr, function (x, callback) { iterator(x, function (v) { if (v) { main_callback(true) main_callback = function () {} } callback() }) }, function (err) { main_callback(false) }, ) } // any alias async.any = async.some async.every = function (arr, iterator, main_callback) { async.forEach( arr, function (x, callback) { iterator(x, function (v) { if (!v) { main_callback(false) main_callback = function () {} } callback() }) }, function (err) { main_callback(true) }, ) } // all alias async.all = async.every async.sortBy = function (arr, iterator, callback) { async.map( arr, function (x, callback) { iterator(x, function (err, criteria) { if (err) { callback(err) } else { callback(null, {value: x, criteria: criteria}) } }) }, function (err, results) { if (err) { return callback(err) } else { var fn = function (left, right) { var a = left.criteria, b = right.criteria return a < b ? -1 : a > b ? 1 : 0 } callback( null, _map(results.sort(fn), function (x) { return x.value }), ) } }, ) } async.auto = function (tasks, callback) { callback = callback || function () {} var keys = _keys(tasks) if (!keys.length) { return callback(null) } var completed = [] var listeners = [] var addListener = function (fn) { listeners.unshift(fn) } var removeListener = function (fn) { for (var i = 0; i < listeners.length; i += 1) { if (listeners[i] === fn) { listeners.splice(i, 1) return } } } var taskComplete = function () { _forEach(listeners, function (fn) { fn() }) } addListener(function () { if (completed.length === keys.length) { callback(null) } }) _forEach(keys, function (k) { var task = tasks[k] instanceof Function ? [tasks[k]] : tasks[k] var taskCallback = function (err) { if (err) { callback(err) // stop subsequent errors hitting callback multiple times callback = function () {} } else { completed.push(k) taskComplete() } } var requires = task.slice(0, Math.abs(task.length - 1)) || [] var ready = function () { return _reduce( requires, function (a, x) { return a && _indexOf(completed, x) !== -1 }, true, ) } if (ready()) { task[task.length - 1](taskCallback) } else { var listener = function () { if (ready()) { removeListener(listener) task[task.length - 1](taskCallback) } } addListener(listener) } }) } async.waterfall = function (tasks, callback) { if (!tasks.length) { return callback() } callback = callback || function () {} var wrapIterator = function (iterator) { return function (err) { if (err) { callback(err) callback = function () {} } else { var args = Array.prototype.slice.call(arguments, 1) var next = iterator.next() if (next) { args.push(wrapIterator(next)) } else { args.push(callback) } async.nextTick(function () { iterator.apply(null, args) }) } } } wrapIterator(async.iterator(tasks))() } async.parallel = function (tasks, callback) { callback = callback || function () {} if (tasks.constructor === Array) { async.map( tasks, function (fn, callback) { if (fn) { fn(function (err) { var args = Array.prototype.slice.call(arguments, 1) if (args.length <= 1) { args = args[0] } callback.call(null, err, args || null) }) } }, callback, ) } else { var results = {} async.forEach( _keys(tasks), function (k, callback) { tasks[k](function (err) { var args = Array.prototype.slice.call(arguments, 1) if (args.length <= 1) { args = args[0] } results[k] = args callback(err) }) }, function (err) { callback(err, results) }, ) } } async.series = function (tasks, callback) { callback = callback || function () {} if (tasks.constructor === Array) { async.mapSeries( tasks, function (fn, callback) { if (fn) { fn(function (err) { var args = Array.prototype.slice.call(arguments, 1) if (args.length <= 1) { args = args[0] } callback.call(null, err, args || null) }) } }, callback, ) } else { var results = {} async.forEachSeries( _keys(tasks), function (k, callback) { tasks[k](function (err) { var args = Array.prototype.slice.call(arguments, 1) if (args.length <= 1) { args = args[0] } results[k] = args callback(err) }) }, function (err) { callback(err, results) }, ) } } async.iterator = function (tasks) { var makeCallback = function (index) { var fn = function () { if (tasks.length) { tasks[index].apply(null, arguments) } return fn.next() } fn.next = function () { return index < tasks.length - 1 ? makeCallback(index + 1) : null } return fn } return makeCallback(0) } async.apply = function (fn) { var args = Array.prototype.slice.call(arguments, 1) return function () { return fn.apply(null, args.concat(Array.prototype.slice.call(arguments))) } } var _concat = function (eachfn, arr, fn, callback) { var r = [] eachfn( arr, function (x, cb) { fn(x, function (err, y) { r = r.concat(y || []) cb(err) }) }, function (err) { callback(err, r) }, ) } async.concat = doParallel(_concat) async.concatSeries = doSeries(_concat) async.whilst = function (test, iterator, callback) { if (test()) { iterator(function (err) { if (err) { return callback(err) } async.whilst(test, iterator, callback) }) } else { callback() } } async.until = function (test, iterator, callback) { if (!test()) { iterator(function (err) { if (err) { return callback(err) } async.until(test, iterator, callback) }) } else { callback() } } async.queue = function (worker, concurrency) { var workers = 0 var tasks = [] var q = { concurrency: concurrency, push: function (data, callback) { tasks.push({data: data, callback: callback}) async.nextTick(q.process) }, process: function () { if (workers < q.concurrency && tasks.length) { var task = tasks.splice(0, 1)[0] workers += 1 worker(task.data, function () { workers -= 1 if (task.callback) { task.callback.apply(task, arguments) } q.process() }) } }, length: function () { return tasks.length }, } return q } var _console_fn = function (name) { return function (fn) { var args = Array.prototype.slice.call(arguments, 1) fn.apply( null, args.concat([ function (err) { var args = Array.prototype.slice.call(arguments, 1) if (typeof console !== 'undefined') { if (err) { if (console.error) { console.error(err) } } else if (console[name]) { _forEach(args, function (x) { console[name](x) }) } } }, ]), ) } } async.log = _console_fn('log') async.dir = _console_fn('dir') /*async.info = _console_fn('info'); async.warn = _console_fn('warn'); async.error = _console_fn('error');*/ async.memoize = function (fn, hasher) { var memo = {} hasher = hasher || function (x) { return x } return function () { var args = Array.prototype.slice.call(arguments) var callback = args.pop() var key = hasher.apply(null, args) if (key in memo) { callback.apply(null, memo[key]) } else { fn.apply( null, args.concat([ function () { memo[key] = arguments callback.apply(null, arguments) }, ]), ) } } } }).call(this) ;(function (exports) { /** * This file is based on the node.js assert module, but with some small * changes for browser-compatibility * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! */ /** * Added for browser compatibility */ var _keys = function (obj) { if (Object.keys) return Object.keys(obj) if (typeof obj != 'object' && typeof obj != 'function') { throw new TypeError('-') } var keys = [] for (var k in obj) { if (obj.hasOwnProperty(k)) keys.push(k) } return keys } // http://wiki.commonjs.org/wiki/Unit_Testing/1.0 // // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! // // Originally from narwhal.js (http://narwhaljs.org) // Copyright (c) 2009 Thomas Robinson <280north.com> // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the 'Software'), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. var pSlice = Array.prototype.slice // 1. The assert module provides functions that throw // AssertionError's when particular conditions are not met. The // assert module must conform to the following interface. var assert = exports // 2. The AssertionError is defined in assert. // new assert.AssertionError({message: message, actual: actual, expected: expected}) assert.AssertionError = function AssertionError(options) { this.name = 'AssertionError' this.message = options.message this.actual = options.actual this.expected = options.expected this.operator = options.operator var stackStartFunction = options.stackStartFunction || fail if (Error.captureStackTrace) { Error.captureStackTrace(this, stackStartFunction) } } // code from util.inherits in node assert.AssertionError.super_ = Error // EDITED FOR BROWSER COMPATIBILITY: replaced Object.create call // TODO: test what effect this may have var ctor = function () { this.constructor = assert.AssertionError } ctor.prototype = Error.prototype assert.AssertionError.prototype = new ctor() assert.AssertionError.prototype.toString = function () { if (this.message) { return [this.name + ':', this.message].join(' ') } else { return [ this.name + ':', typeof this.expected !== 'undefined' ? JSON.stringify(this.expected) : 'undefined', this.operator, typeof this.actual !== 'undefined' ? JSON.stringify(this.actual) : 'undefined', ].join(' ') } } // assert.AssertionError instanceof Error assert.AssertionError.__proto__ = Error.prototype // At present only the three keys mentioned above are used and // understood by the spec. Implementations or sub modules can pass // other keys to the AssertionError's constructor - they will be // ignored. // 3. All of the following functions must throw an AssertionError // when a corresponding condition is not met, with a message that // may be undefined if not provided. All assertion methods provide // both the actual and expected values to the assertion error for // display purposes. function fail(actual, expected, message, operator, stackStartFunction) { throw new assert.AssertionError({ message: message, actual: actual, expected: expected, operator: operator, stackStartFunction: stackStartFunction, }) } // EXTENSION! allows for well behaved errors defined elsewhere. assert.fail = fail // 4. Pure assertion tests whether a value is truthy, as determined // by !!guard. // assert.ok(guard, message_opt); // This statement is equivalent to assert.equal(true, guard, // message_opt);. To test strictly for the value true, use // assert.strictEqual(true, guard, message_opt);. assert.ok = function ok(value, message) { if (!!!value) fail(value, true, message, '==', assert.ok) } // 5. The equality assertion tests shallow, coercive equality with // ==. // assert.equal(actual, expected, message_opt); assert.equal = function equal(actual, expected, message) { if (actual != expected) fail(actual, expected, message, '==', assert.equal) } // 6. The non-equality assertion tests for whether two objects are not equal // with != assert.notEqual(actual, expected, message_opt); assert.notEqual = function notEqual(actual, expected, message) { if (actual == expected) { fail(actual, expected, message, '!=', assert.notEqual) } } // 7. The equivalence assertion tests a deep equality relation. // assert.deepEqual(actual, expected, message_opt); assert.deepEqual = function deepEqual(actual, expected, message) { if (!_deepEqual(actual, expected)) { fail(actual, expected, message, 'deepEqual', assert.deepEqual) } } var Buffer = null if (typeof require !== 'undefined' && typeof process !== 'undefined') { try { Buffer = require('buffer').Buffer } catch (e) { // May be a CommonJS environment other than Node.js Buffer = null } } function _deepEqual(actual, expected) { // 7.1. All identical values are equivalent, as determined by ===. if (actual === expected) return true // Convert to primitives, if supported actual = actual.valueOf ? actual.valueOf() : actual expected = expected.valueOf ? expected.valueOf() : expected // 7.2. If the expected value is a Date object, the actual value is // equivalent if it is also a Date object that refers to the same time. if (actual instanceof Date && expected instanceof Date) { return actual.getTime() === expected.getTime() // 7.2.1 If the expcted value is a RegExp object, the actual value is // equivalent if it is also a RegExp object that refers to the same source and options } else if (actual instanceof RegExp && expected instanceof RegExp) { return ( actual.source === expected.source && actual.global === expected.global && actual.ignoreCase === expected.ignoreCase && actual.multiline === expected.multiline ) } else if (Buffer && actual instanceof Buffer && expected instanceof Buffer) { return (function () { var i, len for (i = 0, len = expected.length; i < len; i++) { if (actual[i] !== expected[i]) { return false } } return actual.length === expected.length })() // 7.3. Other pairs that do not both pass typeof value == "object", // equivalence is determined by ==. } else if (typeof actual != 'object' && typeof expected != 'object') { return actual == expected // 7.4. For all other Object pairs, including Array objects, equivalence is // determined by having the same number of owned properties (as verified // with Object.prototype.hasOwnProperty.call), the same set of keys // (although not necessarily the same order), equivalent values for every // corresponding key, and an identical "prototype" property. Note: this // accounts for both named and indexed properties on Arrays. } else { return objEquiv(actual, expected) } } function isUndefinedOrNull(value) { return value === null || value === undefined } function isArguments(object) { return Object.prototype.toString.call(object) == '[object Arguments]' } function objEquiv(a, b) { if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) return false // an identical "prototype" property. if (a.prototype !== b.prototype) return false //~~~I've managed to break Object.keys through screwy arguments passing. // Converting to array solves the problem. if (isArguments(a)) { if (!isArguments(b)) { return false } a = pSlice.call(a) b = pSlice.call(b) return _deepEqual(a, b) } try { var ka = _keys(a), kb = _keys(b), key, i } catch (e) { //happens when one is a string literal and the other isn't return false } // having the same number of owned properties (keys incorporates hasOwnProperty) if (ka.length != kb.length) return false //the same set of keys (although not necessarily the same order), ka.sort() kb.sort() //~~~cheap key test for (i = ka.length - 1; i >= 0; i--) { if (ka[i] != kb[i]) return false } //equivalent values for every corresponding key, and //~~~possibly expensive deep test for (i = ka.length - 1; i >= 0; i--) { key = ka[i] if (!_deepEqual(a[key], b[key])) return false } return true } // 8. The non-equivalence assertion tests for any deep inequality. // assert.notDeepEqual(actual, expected, message_opt); assert.notDeepEqual = function notDeepEqual(actual, expected, message) { if (_deepEqual(actual, expected)) { fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual) } } // 9. The strict equality assertion tests strict equality, as determined by ===. // assert.strictEqual(actual, expected, message_opt); assert.strictEqual = function strictEqual(actual, expected, message) { if (actual !== expected) { fail(actual, expected, message, '===', assert.strictEqual) } } // 10. The strict non-equality assertion tests for strict inequality, as determined by !==. // assert.notStrictEqual(actual, expected, message_opt); assert.notStrictEqual = function notStrictEqual(actual, expected, message) { if (actual === expected) { fail(actual, expected, message, '!==', assert.notStrictEqual) } } function expectedException(actual, expected) { if (!actual || !expected) { return false } if (expected instanceof RegExp) { return expected.test(actual.message || actual) } else if (actual instanceof expected) { return true } else if (expected.call({}, actual) === true) { return true } return false } function _throws(shouldThrow, block, expected, message) { var actual if (typeof expected === 'string') { message = expected expected = null } try { block() } catch (e) { actual = e } message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + (message ? ' ' + message : '.') if (shouldThrow && !actual) { fail('Missing expected exception' + message) } if (!shouldThrow && expectedException(actual, expected)) { fail('Got unwanted exception' + message) } if ((shouldThrow && actual && expected && !expectedException(actual, expected)) || (!shouldThrow && actual)) { throw actual } } // 11. Expected to throw an error: // assert.throws(block, Error_opt, message_opt); assert.throws = function (block, /*optional*/ error, /*optional*/ message) { _throws.apply(this, [true].concat(pSlice.call(arguments))) } // EXTENSION! This is annoying to write outside this module. assert.doesNotThrow = function (block, /*optional*/ error, /*optional*/ message) { _throws.apply(this, [false].concat(pSlice.call(arguments))) } assert.ifError = function (err) { if (err) { throw err } } })(assert) ;(function (exports) { /*! * Nodeunit * Copyright (c) 2010 Caolan McMahon * MIT Licensed * * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! * Only code on that line will be removed, it's mostly to avoid requiring code * that is node specific */ /** * Module dependencies */ /** * Creates assertion objects representing the result of an assert call. * Accepts an object or AssertionError as its argument. * * @param {object} obj * @api public */ exports.assertion = function (obj) { return { method: obj.method || '', message: obj.message || (obj.error && obj.error.message) || '', error: obj.error, passed: function () { return !this.error }, failed: function () { return Boolean(this.error) }, } } /** * Creates an assertion list object representing a group of assertions. * Accepts an array of assertion objects. * * @param {Array} arr * @param {Number} duration * @api public */ exports.assertionList = function (arr, duration) { var that = arr || [] that.failures = function () { var failures = 0 for (var i = 0; i < this.length; i += 1) { if (this[i].failed()) { failures += 1 } } return failures } that.passes = function () { return that.length - that.failures() } that.duration = duration || 0 return that } /** * Create a wrapper function for assert module methods. Executes a callback * after it's complete with an assertion object representing the result. * * @param {Function} callback * @api private */ var assertWrapper = function (callback) { return function (new_method, assert_method, arity) { return function () { var message = arguments[arity - 1] var a = exports.assertion({method: new_method, message: message}) try { assert[assert_method].apply(null, arguments) } catch (e) { a.error = e } callback(a) } } } /** * Creates the 'test' object that gets passed to every test function. * Accepts the name of the test function as its first argument, followed by * the start time in ms, the options object and a callback function. * * @param {String} name * @param {Number} start * @param {Object} options * @param {Function} callback * @api public */ exports.test = function (name, start, options, callback) { var expecting var a_list = [] var wrapAssert = assertWrapper(function (a) { a_list.push(a) if (options.log) { async.nextTick(function () { options.log(a) }) } }) var test = { done: function (err) { if (expecting !== undefined && expecting !== a_list.length) { var e = new Error('Expected ' + expecting + ' assertions, ' + a_list.length + ' ran') var a1 = exports.assertion({method: 'expect', error: e}) a_list.push(a1) if (options.log) { async.nextTick(function () { options.log(a1) }) } } if (err) { var a2 = exports.assertion({error: err}) a_list.push(a2) if (options.log) { async.nextTick(function () { options.log(a2) }) } } var end = new Date().getTime() async.nextTick(function () { var assertion_list = exports.assertionList(a_list, end - start) options.testDone(name, assertion_list) callback(null, a_list) }) }, ok: wrapAssert('ok', 'ok', 2), same: wrapAssert('same', 'deepEqual', 3), equals: wrapAssert('equals', 'equal', 3), expect: function (num) { expecting = num }, _assertion_list: a_list, } // add all functions from the assert module for (var k in assert) { if (assert.hasOwnProperty(k)) { test[k] = wrapAssert(k, k, assert[k].length) } } return test } /** * Ensures an options object has all callbacks, adding empty callback functions * if any are missing. * * @param {Object} opt * @return {Object} * @api public */ exports.options = function (opt) { var optionalCallback = function (name) { opt[name] = opt[name] || function () {} } optionalCallback('moduleStart') optionalCallback('moduleDone') optionalCallback('testStart') optionalCallback('testReady') optionalCallback('testDone') //optionalCallback('log'); // 'done' callback is not optional. return opt } })(types) ;(function (exports) { /*! * Nodeunit * Copyright (c) 2010 Caolan McMahon * MIT Licensed * * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! * Only code on that line will be removed, it's mostly to avoid requiring code * that is node specific */ /** * Module dependencies */ /** * Added for browser compatibility */ var _keys = function (obj) { if (Object.keys) { return Object.keys(obj) } var keys = [] for (var k in obj) { if (obj.hasOwnProperty(k)) { keys.push(k) } } return keys } var _copy = function (obj) { var nobj = {} var keys = _keys(obj) for (var i = 0; i < keys.length; i += 1) { nobj[keys[i]] = obj[keys[i]] } return nobj } /** * Runs a test function (fn) from a loaded module. After the test function * calls test.done(), the callback is executed with an assertionList as its * second argument. * * @param {String} name * @param {Function} fn * @param {Object} opt * @param {Function} callback * @api public */ exports.runTest = function (name, fn, opt, callback) { var options = types.options(opt) options.testStart(name) var start = new Date().getTime() var test = types.test(name, start, options, callback) options.testReady(test) try { fn(test) } catch (e) { test.done(e) } } /** * Takes an object containing test functions or other test suites as properties * and runs each in series. After all tests have completed, the callback is * called with a list of all assertions as the second argument. * * If a name is passed to this function it is prepended to all test and suite * names that run within it. * * @param {String} name * @param {Object} suite * @param {Object} opt * @param {Function} callback * @api public */ exports.runSuite = function (name, suite, opt, callback) { suite = wrapGroup(suite) var keys = _keys(suite) async.concatSeries( keys, function (k, cb) { var prop = suite[k], _name _name = name ? [].concat(name, k) : [k] _name.toString = function () { // fallback for old one return this.join(' - ') } if (typeof prop === 'function') { var in_name = false, in_specific_test = _name.toString() === opt.testFullSpec ? true : false for (var i = 0; i < _name.length; i += 1) { if (_name[i] === opt.testspec) { in_name = true } } if ((!opt.testFullSpec || in_specific_test) && (!opt.testspec || in_name)) { if (opt.moduleStart) { opt.moduleStart() } exports.runTest(_name, suite[k], opt, cb) } else { return cb() } } else { exports.runSuite(_name, suite[k], opt, cb) } }, callback, ) } /** * Run each exported test function or test suite from a loaded module. * * @param {String} name * @param {Object} mod * @param {Object} opt * @param {Function} callback * @api public */ exports.runModule = function (name, mod, opt, callback) { var options = _copy(types.options(opt)) var _run = false var _moduleStart = options.moduleStart mod = wrapGroup(mod) function run_once() { if (!_run) { _run = true _moduleStart(name) } } options.moduleStart = run_once var start = new Date().getTime() exports.runSuite(null, mod, options, function (err, a_list) { var end = new Date().getTime() var assertion_list = types.assertionList(a_list, end - start) options.moduleDone(name, assertion_list) if (nodeunit.complete) { nodeunit.complete(name, assertion_list) } callback(null, a_list) }) } /** * Treats an object literal as a list of modules keyed by name. Runs each * module and finished with calling 'done'. You can think of this as a browser * safe alternative to runFiles in the nodeunit module. * * @param {Object} modules * @param {Object} opt * @api public */ // TODO: add proper unit tests for this function exports.runModules = function (modules, opt) { var all_assertions = [] var options = types.options(opt) var start = new Date().getTime() async.concatSeries( _keys(modules), function (k, cb) { exports.runModule(k, modules[k], options, cb) }, function (err, all_assertions) { var end = new Date().getTime() options.done(types.assertionList(all_assertions, end - start)) }, ) } /** * Wraps a test function with setUp and tearDown functions. * Used by testCase. * * @param {Function} setUp * @param {Function} tearDown * @param {Function} fn * @api private */ var wrapTest = function (setUp, tearDown, fn) { return function (test) { var context = {} if (tearDown) { var done = test.done test.done = function (err) { try { tearDown.call(context, function (err2) { if (err && err2) { test._assertion_list.push(types.assertion({error: err})) return done(err2) } done(err || err2) }) } catch (e) { done(e) } } } if (setUp) { setUp.call(context, function (err) { if (err) { return test.done(err) } fn.call(context, test) }) } else { fn.call(context, test) } } } /** * Returns a serial callback from two functions. * * @param {Function} funcFirst * @param {Function} funcSecond * @api private */ var getSerialCallback = function (fns) { if (!fns.length) { return null } return function (callback) { var that = this var bound_fns = [] for (var i = 0, len = fns.length; i < len; i++) { ;(function (j) { bound_fns.push(function () { return fns[j].apply(that, arguments) }) })(i) } return async.series(bound_fns, callback) } } /** * Wraps a group of tests with setUp and tearDown functions. * Used by testCase. * * @param {Object} group * @param {Array} setUps - parent setUp functions * @param {Array} tearDowns - parent tearDown functions * @api private */ var wrapGroup = function (group, setUps, tearDowns) { var tests = {} var setUps = setUps ? setUps.slice() : [] var tearDowns = tearDowns ? tearDowns.slice() : [] if (group.setUp) { setUps.push(group.setUp) delete group.setUp } if (group.tearDown) { tearDowns.unshift(group.tearDown) delete group.tearDown } var keys = _keys(group) for (var i = 0; i < keys.length; i += 1) { var k = keys[i] if (typeof group[k] === 'function') { tests[k] = wrapTest(getSerialCallback(setUps), getSerialCallback(tearDowns), group[k]) } else if (typeof group[k] === 'object') { tests[k] = wrapGroup(group[k], setUps, tearDowns) } } return tests } /** * Backwards compatibility for test suites using old testCase API */ exports.testCase = function (suite) { return suite } })(core) ;(function (exports) { /*! * Nodeunit * Copyright (c) 2010 Caolan McMahon * MIT Licensed * * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! * Only code on that line will be removed, its mostly to avoid requiring code * that is node specific */ /** * NOTE: this test runner is not listed in index.js because it cannot be * used with the command-line tool, only inside the browser. */ /** * Reporter info string */ exports.info = 'Browser-based test reporter' /** * Run all tests within each module, reporting the results * * @param {Array} files * @api public */ exports.run = function (modules, options, callback) { var start = new Date().getTime(), div, textareas, displayErrorsByDefault options = options || {} div = options.div || document.body textareas = options.textareas displayErrorsByDefault = options.displayErrorsByDefault function setText(el, txt) { if ('innerText' in el) { el.innerText = txt } else if ('textContent' in el) { el.textContent = txt } } function getOrCreate(tag, id) { var el = document.getElementById(id) if (!el) { el = document.createElement(tag) el.id = id div.appendChild(el) } return el } var header = getOrCreate('h1', 'nodeunit-header') var banner = getOrCreate('h2', 'nodeunit-banner') var userAgent = getOrCreate('h2', 'nodeunit-userAgent') var tests = getOrCreate('ol', 'nodeunit-tests') var result = getOrCreate('p', 'nodeunit-testresult') setText(userAgent, navigator.userAgent) nodeunit.runModules(modules, { moduleStart: function (name) { /*var mheading = document.createElement('h2'); mheading.innerText = name; results.appendChild(mheading); module = document.createElement('ol'); results.appendChild(module);*/ }, testDone: function (name, assertions) { var test = document.createElement('li') var strong = document.createElement('strong') strong.innerHTML = name + ' (' + '' + assertions.failures() + ', ' + '' + assertions.passes() + ', ' + assertions.length + ')' test.className = assertions.failures() ? 'fail' : 'pass' test.appendChild(strong) var aList = document.createElement('ol') aList.style.display = displayErrorsByDefault ? 'block' : 'none' ;(displayErrorsByDefault ? strong : test).onclick = function () { var d = aList.style.display aList.style.display = d == 'none' ? 'block' : 'none' } for (var i = 0; i < assertions.length; i++) { var li = document.createElement('li') var a = assertions[i] if (a.failed()) { li.innerHTML = (a.message || a.method || 'no message') + (textareas ? '' : '
' + (a.error.stack || a.error) + '
') li.className = 'fail' } else { li.innerHTML = a.message || a.method || 'no message' li.className = 'pass' } aList.appendChild(li) } test.appendChild(aList) tests.appendChild(test) }, done: function (assertions) { var end = new Date().getTime() var duration = end - start var failures = assertions.failures() banner.className = failures ? 'fail' : 'pass' result.innerHTML = 'Tests completed in ' + duration + ' milliseconds.
' + assertions.passes() + ' assertions of ' + '' + assertions.length + ' passed, ' + assertions.failures() + ' failed.' if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined) }, }) } })(reporter) nodeunit = core nodeunit.assert = assert nodeunit.reporter = reporter nodeunit.run = reporter.run return nodeunit })() ================================================ FILE: test/unit/nodeunitheadless.cjs ================================================ const {tests} = require('../../.tmp/tests.cjs') module.exports = {tween: tests} // CONTINUE: got html tests running, had to convert to CJS files, ammend the infrastructure update commit with these changes ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "outDir": "./.tmp", "declaration": true, "strict": true, "sourceMap": true, "module": "es2015", "target": "es5", "moduleResolution": "node", "lib": ["dom", "es2015"], // TODO will be removed in TS 5.5 "ignoreDeprecations": "5.0", "importsNotUsedAsValues": "error" }, "exclude": ["node_modules", "examples"] }