├── .browserslistrc ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── babel.config.json ├── config ├── banner.js ├── copy-to-examples.js ├── rollup.config.js ├── shared.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js ├── example ├── app.js └── index.html ├── package-lock.json ├── package.json ├── readme.md ├── src ├── AnimateImages.js ├── Animation.js ├── DragInput.js ├── ImagePreloader.js ├── Poster.js ├── Render.js ├── index.js ├── settings.js └── utils.js └── tsconfig.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | >1% 2 | last 2 major versions 3 | not dead 4 | not iOS < 13.4 5 | iOS >= 13.4 6 | not Safari < 13.1 7 | Safari >= 13.1 8 | not Explorer <= 11 9 | not op_mini all 10 | not op_mob > 1 11 | not kaios > 1 12 | not baidu > 1 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | npm-debug.log 4 | yarn-debug.log* 5 | yarn-error.log* 6 | .npm 7 | .yarn-integrity 8 | .idea/ 9 | *.sublime-workspace 10 | .vscode/* 11 | *.DS_Store 12 | .AppleDouble 13 | .LSOverride 14 | *.swp 15 | *.psd 16 | Thumbs.db 17 | ehthumbs.db 18 | *.lnk 19 | /example/images/**/*.jpg 20 | /example/images/**/*.png 21 | /example/animate-images*.js 22 | /example/animate-images*.map 23 | /types 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example 2 | 3 | # .gitignore copy 4 | node_modules 5 | npm-debug.log 6 | yarn-debug.log* 7 | yarn-error.log* 8 | .npm 9 | .yarn-integrity 10 | .idea/ 11 | *.sublime-workspace 12 | .vscode/* 13 | *.DS_Store 14 | .AppleDouble 15 | .LSOverride 16 | *.swp 17 | *.psd 18 | Thumbs.db 19 | ehthumbs.db 20 | *.lnk -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## 2.3.1 3 | - animation started in ```playTo``` or ```playFrames``` in ```fastPreview``` mode now stops 4 | after full preload instead of playing endlessly 5 | - animation-end callback and event are now called after resetting values 6 | - reduced the number of operations in the animation function 7 | - move ```core-js``` to ```devDependencies``` 8 | - add ```direction``` to event.detail of ```animate-images:drag-end``` event 9 | ## 2.3.0 10 | - new ```setForward``` method 11 | - new ```responsiveAspect``` option that allows to control height instead of width 12 | - add ```direction``` to event.detail of ```animate-images:drag-change``` event 13 | - reset pixels correction when changing drag direction 14 | - fix dragging when mousemove is called without real movement 15 | ## 2.2.0 16 | - new ```fastPreview``` option, ```onFastPreloadFinished``` callback, 17 | ```animate-images:fast-preload-finished``` event, ```isFastPreloadFinished``` method 18 | - new ```isDragging``` method 19 | - reduce bundle size by 20% 20 | ## 2.1.2 21 | - fix wrong ```onPosterLoaded``` call 22 | ## 2.1.1 23 | - new ```shortestPath``` option for ```playTo()``` 24 | ## 2.1.0 25 | - ```inversion``` is now only used while dragging and doesn't affect animation 26 | ## 2.0.0 27 | - plugin import changed 28 | - new initialization with constructor instead of ```init``` method 29 | - ```togglePlay()``` renamed to ```toggle()``` 30 | - added types 31 | - new ```onAnimationEnd``` callback 32 | - new ```dragModifier``` option 33 | - ```playTo``` and ```playFrames``` now return plugin instance instead of Promise 34 | - ```onBeforeFrame``` and ```onAfterFrame``` parameters changed 35 | - ```getOption()``` accepts all the options 36 | - fix wrong animation duration 37 | ## 1.6 38 | - new ```inversion``` option 39 | ## 1.5.3 40 | - fix console.log() 41 | ## 1.5.2 42 | - ```preventTouchScroll``` replaced with ```touchScrollMode``` and ```pageScrollTimerDelay``` 43 | ## 1.5.1 44 | - add ```preventTouchScroll``` option 45 | ## 1.5.0 46 | - fix blurry images when devicePixelRatio > 1 47 | - add ```onBeforeFrame``` and ```onAfterFrame``` callbacks with access to the 48 | canvas context 49 | ## 1.4.0 50 | - add ```animate-images:drag-start```, ```animate-images:drag-change``` and 51 | ```animate-images:drag-end ``` events 52 | ## 1.3.2 53 | - fix wrong height after resize when canvas width/height ratio is 54 | a fractional number 55 | ## 1.3.1 56 | - fix readme 57 | ## 1.3.0 58 | - change build 59 | ## 1.2.0 60 | - plugin has been rewritten with classes 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present Dmitry Kovalev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "corejs": 3.24, 7 | "useBuiltIns": "usage", 8 | "exclude": ["es.promise", "es.error.cause"] 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /config/banner.js: -------------------------------------------------------------------------------- 1 | var pkg = require("../package.json"); 2 | 3 | module.exports = 4 | ` ${pkg.name} ${pkg.version} 5 | ${pkg.repository.url} 6 | 7 | Copyright (c) 2020-present ${pkg.author}, 8 | Released under the ${pkg.license} license`; 9 | -------------------------------------------------------------------------------- /config/copy-to-examples.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { LIB_FILE_NAME } = require( './shared'); 4 | 5 | let source = path.join(__dirname, `../build/${LIB_FILE_NAME}.umd.min.js`); 6 | let dest = path.join(__dirname, `../example/${LIB_FILE_NAME}.umd.min.js`); 7 | 8 | fs.copyFile(source, dest, function (err) { 9 | if (err) return console.error(err); 10 | console.log('Copied to ' + dest); 11 | }); 12 | -------------------------------------------------------------------------------- /config/rollup.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rollup'; 2 | import { terser } from "rollup-plugin-terser"; 3 | import { babel } from '@rollup/plugin-babel'; 4 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 5 | import commonjs from '@rollup/plugin-commonjs'; 6 | import bundleSize from 'rollup-plugin-bundle-size'; 7 | import dts from "rollup-plugin-dts"; 8 | 9 | const banner = require("./banner"); 10 | const bannerWithComments = "/*!\n" + banner + "\n*/"; 11 | const { LIB_FILE_NAME, TERSER_OPTIONS } = require( './shared'); 12 | 13 | const terserOptions = TERSER_OPTIONS; 14 | 15 | export default defineConfig([ 16 | { // Transpiled bundle 17 | input: `./src/index.js`, 18 | plugins: [ 19 | nodeResolve(), 20 | commonjs(), 21 | babel({ 22 | babelHelpers: 'bundled', 23 | exclude: "node_modules/**" 24 | }), 25 | bundleSize() 26 | ], 27 | output: [ 28 | { 29 | file: `./build/${LIB_FILE_NAME}.esm.js`, 30 | format: 'es', 31 | banner: bannerWithComments, 32 | sourcemap: true, 33 | }, 34 | { 35 | file: `./build/${LIB_FILE_NAME}.esm.min.js`, 36 | format: 'es', 37 | banner: bannerWithComments, 38 | sourcemap: true, 39 | plugins: [ terser(terserOptions) ] 40 | } 41 | ], 42 | }, 43 | { // Untranspiled bundle 44 | input: `./src/index.js`, 45 | plugins: [ 46 | bundleSize() 47 | ], 48 | output: [ 49 | { 50 | file: `./build/untranspiled/${LIB_FILE_NAME}.esm.js`, 51 | format: 'es', 52 | banner: bannerWithComments, 53 | sourcemap: true, 54 | }, 55 | { 56 | file: `./build/untranspiled/${LIB_FILE_NAME}.esm.min.js`, 57 | format: 'es', 58 | banner: bannerWithComments, 59 | sourcemap: true, 60 | plugins: [ terser(terserOptions) ] 61 | } 62 | ], 63 | }, 64 | { // bundle types to hide internal modules, because @internal is not recommended in ts docs 65 | input: "./types/index.d.ts", 66 | output: [{ file: `types/${LIB_FILE_NAME}.d.ts`, format: "es" }], 67 | plugins: [dts()], 68 | }, 69 | ]); 70 | -------------------------------------------------------------------------------- /config/shared.js: -------------------------------------------------------------------------------- 1 | const LIB_FILE_NAME = 'animate-images'; 2 | const LIB_NAME = 'AnimateImages'; 3 | const TERSER_OPTIONS = { 4 | mangle: { 5 | properties: { 6 | regex: /^_/, 7 | } 8 | }, 9 | } 10 | module.exports = {LIB_FILE_NAME, LIB_NAME, TERSER_OPTIONS}; 11 | 12 | 13 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { LIB_FILE_NAME, LIB_NAME } = require( './shared'); 3 | 4 | const config = { 5 | entry: path.join(__dirname, "../src/index.js"), 6 | output: { 7 | path: path.resolve(__dirname, '../build'), 8 | filename: `${LIB_FILE_NAME}.umd.min.js`, 9 | library: { 10 | name: LIB_NAME, 11 | type: 'umd', 12 | export: 'default', 13 | //umdNamedDefine: true, 14 | }, 15 | globalObject: 'this', 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js$/, 21 | exclude: /(node_modules)/, 22 | use: { 23 | loader: 'babel-loader', 24 | } 25 | } 26 | ] 27 | }, 28 | plugins: [ ], 29 | }; 30 | module.exports = config; 31 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'development', 6 | devtool: 'eval-source-map', 7 | devServer: { 8 | port: 7701, 9 | historyApiFallback: true, 10 | open: true, 11 | devMiddleware: { 12 | index: 'index.html', 13 | }, 14 | client: { 15 | overlay: true, 16 | }, 17 | static: { 18 | directory: './example', 19 | watch: true, 20 | } 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const { merge } = require('webpack-merge'); 3 | const TerserPlugin = require("terser-webpack-plugin"); 4 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 5 | 6 | const common = require('./webpack.common.js'); 7 | const banner = require("./banner"); 8 | const { TERSER_OPTIONS } = require( './shared'); 9 | 10 | module.exports = [ 11 | merge(common, { // Minified 12 | mode: 'production', 13 | //devtool: 'source-map', 14 | optimization: { 15 | minimizer: [ // fix license.txt from bannerPlugin 16 | new TerserPlugin({ 17 | extractComments: false, 18 | terserOptions: TERSER_OPTIONS 19 | }), 20 | ] 21 | }, 22 | plugins: [ 23 | new webpack.BannerPlugin(banner), 24 | new CleanWebpackPlugin(), 25 | ], 26 | }), 27 | ]; 28 | -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function() { 2 | let element = document.getElementById('canvas1'); 3 | let imagesArray = Array.from(new Array(90), (v, k) => { 4 | let number = String(k).padStart(4, "0"); 5 | return `https://distracted-villani-e19534.netlify.app/train/rotation${number}.jpg`; 6 | }); 7 | let loadingBlock = document.querySelector('.loading1'); 8 | 9 | // Initialization 10 | let instance1 = new AnimateImages(element, { 11 | images: imagesArray, 12 | preload: "all", 13 | preloadNumber: 15, 14 | poster: imagesArray[0], 15 | fps: 45, 16 | loop: true, 17 | //reverse: true, 18 | autoplay: false, 19 | //ratio: 2.56, 20 | fillMode: 'cover', 21 | draggable: true, 22 | //inversion: true, 23 | //dragModifier: 1.5, 24 | touchScrollMode: "pageScrollTimer", 25 | //pageScrollTimerDelay: 2500, 26 | //responsiveAspect: "height", 27 | // fastPreview: { 28 | // images: imagesArray.filter( (val, i) => i % 5 === 0 ), 29 | // matchFrame: function (currentFrame){ 30 | // return ((currentFrame-1) * 5) + 1; 31 | // }, 32 | // fpsAfter: 60, 33 | // }, 34 | onPreloadFinished: (plugin) => { 35 | console.log('Callback: onPreloadFinished'); 36 | //plugin.play(); 37 | }, 38 | onFastPreloadFinished: (plugin) => { 39 | console.log('Callback: onFastPreloadFinished'); 40 | }, 41 | onPosterLoaded(plugin){ 42 | console.log('Callback: onPosterLoaded'); 43 | }, 44 | onAnimationEnd(plugin){ 45 | console.log('Callback: onAnimationEnd'); 46 | }, 47 | // onBeforeFrame(plugin, {context, width, height }){ 48 | // 49 | // }, 50 | // onAfterFrame(plugin, {context, width, height }){ 51 | // 52 | // }, 53 | }); 54 | //instance1.preloadImages(); 55 | setupControls(); 56 | 57 | // Events 58 | element.addEventListener('animate-images:loading-progress', function (e){ 59 | //console.log(`Event: loading progress: ${e.detail.progress}`); 60 | loadingBlock.querySelector('span').textContent = Math.floor( +e.detail.progress * 100); 61 | }); 62 | element.addEventListener('animate-images:preload-finished', function (e){ 63 | console.log(`Event: animate-images:preload-finished`); 64 | }); 65 | element.addEventListener('animate-images:fast-preload-finished', function (e){ 66 | console.log(`Event: animate-images:fast-preload-finished`); 67 | }); 68 | element.addEventListener('animate-images:animation-end', function () { 69 | console.log(`Event: animate-images:animation-end`); 70 | }); 71 | element.addEventListener('animate-images:poster-loaded', function () { 72 | console.log(`Event: animate-images:poster-loaded`); 73 | }); 74 | element.addEventListener('animate-images:loading-error', function () { 75 | console.log(`Event: animate-images:loading-error`); 76 | }); 77 | element.addEventListener('animate-images:drag-start', function () { 78 | console.log(`Event: animate-images:drag-start`); 79 | }); 80 | element.addEventListener('animate-images:drag-change', function (e) { 81 | //console.log(`Event: animate-images:drag-change`); 82 | //console.log(`Drag direction: ${e.detail.direction}`); 83 | }); 84 | element.addEventListener('animate-images:drag-end', function (e) { 85 | console.log(`Event: animate-images:drag-end`); 86 | //console.log(`Drag direction: ${e.detail.direction}`); 87 | }); 88 | 89 | // Controls 90 | function setupControls(lib){ 91 | console.log('setup controls'); 92 | document.querySelector('.js-play').addEventListener('click', () => { 93 | instance1.play(); 94 | }); 95 | document.querySelector('.js-stop').addEventListener('click', () => { 96 | instance1.stop(); 97 | }); 98 | document.querySelector('.js-toggle').addEventListener('click', () => { 99 | instance1.toggle(); 100 | }); 101 | document.querySelector('.js-next').addEventListener('click', () => { 102 | instance1.next(); 103 | }); 104 | document.querySelector('.js-prev').addEventListener('click', () => { 105 | instance1.prev(); 106 | }); 107 | document.querySelector('.js-reset').addEventListener('click', () => { 108 | instance1.reset(); 109 | }); 110 | 111 | let reverse = instance1.getOption('reverse'); 112 | document.querySelector('.js-reverse').addEventListener('change', () => { 113 | reverse = !reverse; 114 | instance1.setReverse(reverse); 115 | }); 116 | document.querySelector(".js-reverse").checked = reverse; 117 | 118 | let loop = instance1.getOption('loop'); 119 | document.querySelector('.js-loop').addEventListener('change', () => { 120 | loop = !loop; 121 | instance1.setOption('loop', loop); 122 | }); 123 | document.querySelector('.js-loop').checked = loop; 124 | 125 | let draggable = instance1.getOption('draggable'); 126 | document.querySelector('.js-draggable').addEventListener('change', () => { 127 | draggable = !draggable; 128 | instance1.setOption('draggable', draggable); 129 | }); 130 | document.querySelector('.js-draggable').checked = draggable; 131 | 132 | let fillMode = instance1.getOption('fillMode'); 133 | document.querySelector(".js-fill-mode[value='"+ fillMode +"']").checked = true; 134 | document.querySelectorAll(".js-fill-mode").forEach(function (el){ 135 | el.addEventListener('change', function(){ 136 | instance1.setOption('fillMode', this.value); 137 | }); 138 | }); 139 | 140 | let framesInput = document.querySelector('.js-frames-input'); 141 | framesInput.setAttribute('max', instance1.getTotalImages()); 142 | framesInput.addEventListener('input', function() { 143 | instance1.setFrame(this.value); 144 | }); 145 | 146 | // Inputs 147 | document.querySelector('.js-set-frame').addEventListener('click', function() { 148 | instance1.setFrame(+this.closest('.js-option-block').querySelector('input').value); 149 | }); 150 | document.querySelector('.js-play-to').addEventListener('click', function() { 151 | instance1.playTo(+this.closest('.js-option-block').querySelector('input').value); 152 | }); 153 | document.querySelector('.js-play-to-shortest').addEventListener('click', function() { 154 | instance1.playTo(+this.closest('.js-option-block').querySelector('input').value, { 155 | shortestPath: true, 156 | }); 157 | }); 158 | document.querySelector('.js-play-frames').addEventListener('click', function() { 159 | instance1.playFrames(+this.closest('.js-option-block').querySelector('input').value); 160 | }); 161 | document.querySelector('.js-set-fps').addEventListener('click', function() { 162 | instance1.setOption("fps", this.closest('.js-option-block').querySelector('input').value); 163 | }); 164 | document.querySelector('.js-set-ratio').addEventListener('click', function() { 165 | instance1.setOption("ratio", this.closest('.js-option-block').querySelector('input').value); 166 | }); 167 | } 168 | 169 | }); 170 | 171 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Animation test 10 | 11 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 |

96 | animate-images example 97 |

98 | 99 | 100 |
101 |
102 |
103 |
104 |
105 |
106 | 107 | 108 | 109 | 110 | 111 | 112 |
113 |
114 |
115 |
116 |
117 | 119 | 120 | 122 | 123 |
124 | 125 |
126 | 127 | 128 |
129 |
130 | 131 | 132 |
133 |
134 | 135 | 136 |
137 |
138 |
139 |
140 | 141 | 143 |
144 |
145 |
146 |
147 |
148 |
149 | 150 |
151 | 152 |
153 |
154 |
155 |
156 |
157 | 158 |
159 | 160 |
161 |
162 |
163 |
164 |
165 | 166 |
167 | 170 |
171 |
172 |
173 |
174 |
175 | 176 |
177 | 178 |
179 |
180 |
181 |
182 |
183 | 184 |
185 | 186 |
187 |
188 |
189 |
190 |
191 | 192 |
193 | 194 |
195 |
196 |
197 |
198 | 199 | 200 |
201 | 202 |
Loading 0%
203 |
204 | 205 | 206 | 207 |
208 | 209 |
210 |
211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@its2easy/animate-images", 3 | "version": "2.3.1", 4 | "description": "Javascript library that animates a sequence of images to use in complex animations or pseudo 3d product view", 5 | "author": "Dmitry Kovalev", 6 | "license": "MIT", 7 | "main": "build/animate-images.umd.min.js", 8 | "module": "build/animate-images.esm.min.js", 9 | "types": "types/animate-images.d.ts", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "scripts": { 14 | "build": "webpack --config config/webpack.prod.js && npx tsc && rollup --config config/rollup.config.js && node config/copy-to-examples.js", 15 | "start": "webpack-dev-server --progress --config config/webpack.dev.js --open", 16 | "test": "echo \"Error: no test specified\"", 17 | "version": "npm run build && git add .", 18 | "postversion": "git push && git push --tags" 19 | }, 20 | "files": [ 21 | "build", 22 | "types/animate-images.d.ts" 23 | ], 24 | "keywords": [ 25 | "animation", 26 | "animate", 27 | "sequence", 28 | "frames", 29 | "image sequence", 30 | "image animation", 31 | "frames animation", 32 | "360 animation", 33 | "3D", 34 | "3D spin", 35 | "3Dview", 36 | "3d rotation", 37 | "360", 38 | "view360" 39 | ], 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/its2easy/animate-images" 43 | }, 44 | "sideEffects": false, 45 | "devDependencies": { 46 | "@babel/core": "^7.17.8", 47 | "@babel/preset-env": "^7.16.11", 48 | "@rollup/plugin-babel": "^5.3.1", 49 | "@rollup/plugin-commonjs": "^21.0.2", 50 | "@rollup/plugin-node-resolve": "^13.1.3", 51 | "babel-loader": "^8.2.3", 52 | "clean-webpack-plugin": "^4.0.0", 53 | "core-js": "^3.24.1", 54 | "rollup": "^2.70.1", 55 | "rollup-plugin-bundle-size": "^1.0.3", 56 | "rollup-plugin-dts": "^4.2.0", 57 | "rollup-plugin-terser": "^7.0.2", 58 | "typescript": "^4.6.2", 59 | "webpack": "^5.70.0", 60 | "webpack-cli": "^4.9.2", 61 | "webpack-dev-server": "^4.7.4", 62 | "webpack-merge": "^5.8.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | AnimateImages 3 |

4 | 5 | ![npm](https://img.shields.io/npm/v/@its2easy/animate-images) 6 | 7 | Demo - [codepen](https://codepen.io/its2easy/pen/powQJmd) 8 | 9 | **AnimateImages** is a lightweight library (17kb) that animates a sequence of images 10 | to use in animations or pseudo 3d product view. It works WITHOUT BUILT-IN UI and mainly 11 | developed for complex animations. 12 | 13 | To use it, you have to get a series of frames from a video or 3d app. 14 | The frames must be separated images of the same size. 15 | 16 | * [Installation](#installation) 17 | * [Usage](#usage) 18 | * [Sizes and responsive behavior](#responsive) 19 | * [Options](#options) 20 | * [Methods](#methods) 21 | * [Events](#events) 22 | * [Browser support](#browser_support) 23 | * [License](#license) 24 | 25 | ## Installation 26 | ### Browser script tag 27 | Add with CDN link: 28 | ```html 29 | 30 | ``` 31 | Or download minified version 32 | and include in html: 33 | ```html 34 | 35 | ``` 36 | ```javascript 37 | let instance = new AnimateImages(element, options); 38 | ``` 39 | ### npm 40 | ``` 41 | npm i @its2easy/animate-images --save 42 | ``` 43 | ```javascript 44 | import AnimateImages from "@its2easy/animate-images"; 45 | let instance = new AnimateImages(element, options); 46 | ``` 47 | 48 |
49 | It is possible to directly import untranspiled esm version: 50 | 51 | This version has not been processed by babel: 52 | ```javascript 53 | import AnimateImages from '@its2easy/animate-images/build/untranspiled/animate-images.esm.min.js'; //or animate-images.esm.js 54 | ``` 55 | > :warning: You should probably add it to your build process if you use untranspiled version. Example for webpack: 56 | ```javascript 57 | rules: [ 58 | { 59 | test: /\.js$/, 60 | exclude: /node_modules(?!(\/|\\)@its2easy(\/|\\)animate-images(\/|\\)build)/, 61 | use: { 62 | loader: 'babel-loader', 63 | } 64 | } 65 | ] 66 | ``` 67 | or 68 | ```javascript 69 | rules: [ 70 | { 71 | // basic js rule 72 | test: /\.js$/, 73 | exclude: /node_modules/, 74 | use: { 75 | loader: 'babel-loader', 76 | } 77 | }, 78 | { 79 | // additional rule 80 | test: /\.js$/, 81 | include: /node_modules(\/|\\)@its2easy(\/|\\)animate-images(\/|\\)build/, 82 | use: { 83 | loader: 'babel-loader', 84 | } 85 | }, 86 | ] 87 | ``` 88 | 89 |
90 | 91 |
92 | All available versions: 93 | 94 | umd build: 95 | 96 | `@its2easy/animate-images/build/animate-images.umd.min.js` - default for browser script tag and legacy bundlers 97 | 98 | esm builds processed whit babel: 99 | 100 | `@its2easy/animate-images/build/animate-images.esm.min.js` - default for webpack and module environments 101 | 102 | `@its2easy/animate-images/build/animate-images.esm.js` 103 | 104 | esm builds without babel transformation: 105 | 106 | `@its2easy/animate-images/build/untranspiled/animate-images.esm.min.js` 107 | 108 | `@its2easy/animate-images/build/untranspiled/animate-images.esm.js` 109 | 110 | :information_source: If you are using **webpack 4** and babel with modern target browsers, 111 | then you might get an error while importing, because webpack 4 doesn't support some modern js 112 | syntax and babel doesn't transpile it because browsers support for this syntax is high enough now. 113 | Use **webpack 5** to fix it. 114 |
115 | 116 | 117 | ## Usage 118 | Create canvas element 119 | ```html 120 | 121 | ``` 122 | 123 | Initialize with options 124 | ```javascript 125 | let element = document.querySelector('canvas.canvas-el'); 126 | let imagesArray = Array.from(new Array(90), (v, k) => { // generate array of urls 127 | let number = String(k).padStart(4, "0"); 128 | return `path/to/your/images/frame-${number}.jpg`; 129 | }); 130 | let instance = new AnimateImages(element, { 131 | images: imagesArray, /* required */ 132 | preload: "partial", 133 | preloadNumber: 20, 134 | loop: true, 135 | fps: 60, 136 | poster: 'path/to/poster/image.jpg', 137 | }); 138 | instance.play(); 139 | ``` 140 | 141 | Methods called from `onPreloadFinished` callback will start immediately, but you have to use `options.preload: 'all'` 142 | or call `plugin.preload()`. The plugin loads each image only once, so it's safe to call `preload` multiple times, 143 | even after the load has been completed. If `autoplay: true`, full preload will start immediately. 144 | ```javascript 145 | let instance = new AnimateImages(element, { 146 | images: imagesArray, /* required */ 147 | preload: "none", // if 'all', you don't need to call preload() 148 | onPreloadFinished: function (plugin){ 149 | plugin.play(); 150 | } 151 | }); 152 | instance.preload(30);//load the first part 153 | 154 | setTimeout(() => { 155 | instance.preload(60);// laod the rest 156 | }, 1000); 157 | // or instance.preload() to load all the images 158 | ``` 159 | Methods that were called from outside of `onPreloadFinished` will trigger the load of all remaining images, 160 | wait for the full load, and then execute the action. If multiple methods have been called before 161 | load, only the last will be executed 162 | ```javascript 163 | let instance = animateImages.init(element, 164 | { 165 | images: imagesArray, 166 | preload: "none", 167 | } 168 | ); 169 | // if preload: "none", it will trigger the load, and play after all the image loaded 170 | // if preload: "all", it will wait until full load and then start 171 | instance.play(); 172 | ``` 173 | In general, the plugin will load all the frames before any action, but you can preload a part of the 174 | images in some cases, for example, when the user is scrolling to the section in which the animation will 175 | take place. 176 | 177 | ### Loading errors 178 | All images that have been loaded with errors will be removed from the array of frames. Duration 179 | of the animation will be recalculated. 180 | 181 | New frames count could be obtained in preload callback: 182 | ```javascript 183 | new AnimateImages(element, { 184 | ... 185 | onPreloadFinished: function (instance){ 186 | if ( instance.isLoadedWithErrors() ) { 187 | let newFramesCount = instance.getTotalImages(); 188 | } 189 | }, 190 | }); 191 | ``` 192 | 193 | ## Sizes and responsive behavior 194 | **TL;DR** 195 | use your image dimensions as width and height canvas properties, add ```width: 100%``` to canvas, 196 | add more css if you need: 197 | ```html 198 | 199 | ``` 200 | 201 | --- 202 | 203 | Size calculation is controlled by ```responsiveAspect```. Default is ```width``` which means that canvas **width** 204 | should be defined or restricted by CSS, and **height** will be calculated by the plugin. With ```responsiveAspect: "height"``` 205 | you should control **height** and leave **width** auto. 206 | 207 | Canvas itself should have ```width: 100%``` (```height: 100%``` if responsiveAspect is "height"). Size restrictions 208 | could be set on canvas or on wrapper element: 209 | ```html 210 | 211 | ``` 212 |
213 | responsive height example 214 | 215 | ```html 216 |
217 | 218 |
219 | ``` 220 | 221 |
222 | 223 | :information_source: Secondary side of the canvas should be ```auto``` and not fixed by CSS. If it's fixed, 224 | the ratio can't be used, and the canvas will use its natural CSS size. 225 | 226 | #### Ratio 227 | To calculate secondary side, plugin uses ratio of 228 | inline width and height canvas properties `` (if they're not set, default 229 | is 300x150). This ratio can be overwritten by `options.ratio`. 230 | 231 | #### Fill mode 232 | If **the canvas and images have the same ratio**, the full image will be displayed. If **the ratios are the same, 233 | but sizes are different**, the image will be scaled to fit the canvas. The dimensions of the images are taken from the 234 | image itself after load, and they do not need to be set anywhere in the settings.On the page the canvas 235 | with the image will be scaled to canvas CSS size. 236 | 237 | If **canvas and image ratios are different**, then image will use `options.fillMode`, which works like 238 | background-size `cover` and `contain`, and the image will be centered. 239 | 240 | To display the full image, check the image width and height, and set it as canvas inline `width` and `height` 241 | (or set `options.ratio`). 242 | Then set canvas width by CSS (width="500px" or width="100%" or max-width="800px" etc), and don't set 243 | canvas height (with default responsiveAspect). 244 | 245 | #### Other 246 | For example, <canvas width="800" height="400">, image 1200x600, canvas has css max-width="500px". 247 | Image will be scaled to 800x400 inside canvas and fully visible, canvas on the page will be displayed 248 | 500px x 250px. 249 | 250 | After page resize, the sizes will be recalculated automatically, but if canvas was resized **by a script**, call 251 | `instance.updateCanvas()` 252 | 253 | 254 | ## Options 255 | 256 | ```javascript 257 | new AnimateImages(element, options); 258 | ``` 259 | element : HTMLCanvasElement - canvas DOM element (required) 260 | 261 | options: 262 | 263 | | Parameter | Type | Default | Description | 264 | | :--- | :---: | :---:| :--- | 265 | | **images** | Array<string> | | **(Required)** Array with images URLs | 266 | | **preload** | string | 'all' | Preload mode ("`all`", "`none`", "`partial`") | 267 | | **preloadNumber** | number | 0 | Number of images to preload when `preload: "partial"` (0 for all images) | 268 | | **poster** | string | | URL of the poster image, works like poster in ```