├── examples ├── static │ ├── .gitkeep │ ├── logo.png │ ├── vue.png │ ├── ember.png │ └── react.png ├── config │ ├── prod.env.js │ ├── dev.env.js │ └── index.js ├── build │ ├── logo.png │ ├── vue-loader.conf.js │ ├── build.js │ ├── check-versions.js │ ├── webpack.base.conf.js │ ├── utils.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── .editorconfig ├── .gitignore ├── .babelrc ├── .postcssrc.js ├── src │ ├── components │ │ ├── Rectangle.vue │ │ ├── Circle.vue │ │ ├── HelloWorld.vue │ │ ├── Triangle.vue │ │ ├── Sandbox.vue │ │ ├── Shapes.vue │ │ ├── TileDetail.vue │ │ ├── IconIndex.vue │ │ ├── LibraryIndex.vue │ │ ├── IconDetail.vue │ │ ├── Tiles.vue │ │ └── LibraryDetail.vue │ ├── main.js │ ├── store │ │ └── index.js │ ├── App.vue │ └── router │ │ └── index.js ├── index.html ├── README.md └── package.json ├── .npmignore ├── .gitignore ├── .vscode └── settings.json ├── .babelrc ├── .eslintrc ├── ISSUE_TEMPLATE.md ├── rollup.config.js ├── src ├── index.js └── Overdrive.vue ├── package.json ├── README.md └── dist ├── overdrive.esm.js ├── overdrive.min.js └── overdrive.umd.js /examples/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples/ 2 | build/ 3 | .babelrc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | .yarn-error.log 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } -------------------------------------------------------------------------------- /examples/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /examples/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattrothenberg/vue-overdrive/HEAD/examples/build/logo.png -------------------------------------------------------------------------------- /examples/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattrothenberg/vue-overdrive/HEAD/examples/static/logo.png -------------------------------------------------------------------------------- /examples/static/vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattrothenberg/vue-overdrive/HEAD/examples/static/vue.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["latest", { 4 | "es2015": { "modules": false } 5 | }] 6 | ] 7 | } -------------------------------------------------------------------------------- /examples/static/ember.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattrothenberg/vue-overdrive/HEAD/examples/static/ember.png -------------------------------------------------------------------------------- /examples/static/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattrothenberg/vue-overdrive/HEAD/examples/static/react.png -------------------------------------------------------------------------------- /examples/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /examples/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier"], 3 | "extends": ["plugin:vue/recommended", "plugin:prettier/recommended"], 4 | "rules": { 5 | "vue/no-unused-vars": 1, 6 | "prettier/prettier": "error" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: 17 | - Subsystem: 18 | -------------------------------------------------------------------------------- /examples/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /examples/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/src/components/Rectangle.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ramjet-experimentation 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/src/components/Circle.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 28 | -------------------------------------------------------------------------------- /examples/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import Overdrive, { VOverdrive } from 'vue-overdrive' 7 | import store from './store' 8 | Vue.use(Overdrive) 9 | Vue.config.productionTip = false 10 | 11 | /* eslint-disable no-new */ 12 | new Vue({ 13 | el: '#app', 14 | router, 15 | store, 16 | components: { App }, 17 | template: '' 18 | }) 19 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # ramjet-experimentation 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /examples/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 18 | 34 | -------------------------------------------------------------------------------- /examples/src/components/Triangle.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 29 | -------------------------------------------------------------------------------- /examples/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "rollup-plugin-commonjs"; 2 | import VuePlugin from "rollup-plugin-vue"; 3 | import resolve from "rollup-plugin-node-resolve"; 4 | 5 | export default { 6 | input: "src/index.js", 7 | plugins: [commonjs(), VuePlugin(), resolve()], 8 | output: [ 9 | { 10 | file: "dist/overdrive.umd.js", 11 | exports: "named", 12 | format: "umd", 13 | name: "VOverdrive" 14 | }, 15 | { 16 | file: "dist/overdrive.esm.js", 17 | format: "esm" 18 | }, 19 | { 20 | file: "dist/overdrive.min.js", 21 | format: "iife", 22 | exports: "named", 23 | name: "VOverdrive" 24 | } 25 | ] 26 | }; 27 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Overdrive from "./Overdrive.vue"; 2 | 3 | export function install(Vue) { 4 | if (install.installed) return; 5 | install.installed = true; 6 | Vue.component("overdrive", Overdrive); 7 | } 8 | 9 | export const VOverdrive = Overdrive; 10 | 11 | const plugin = { 12 | install, 13 | 14 | get enabled() { 15 | return state.enabled; 16 | }, 17 | 18 | set enabled(value) { 19 | state.enabled = value; 20 | } 21 | }; 22 | 23 | // Auto-install 24 | let GlobalVue = null; 25 | if (typeof window !== "undefined") { 26 | GlobalVue = window.Vue; 27 | } else if (typeof global !== "undefined") { 28 | GlobalVue = global.Vue; 29 | } 30 | if (GlobalVue) { 31 | GlobalVue.use(plugin); 32 | } 33 | 34 | export default plugin; 35 | -------------------------------------------------------------------------------- /examples/src/components/Sandbox.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 27 | 28 | -------------------------------------------------------------------------------- /examples/src/components/Shapes.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | -------------------------------------------------------------------------------- /examples/src/components/TileDetail.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | -------------------------------------------------------------------------------- /examples/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { times } from 'lodash' 4 | import chroma from 'chroma-js' 5 | Vue.use(Vuex) 6 | 7 | const generateIcons = (num) => { 8 | let arr = [] 9 | const scale = chroma.scale('Spectral').colors(num) 10 | times(num, i => { 11 | arr.push({ 12 | color: scale[i], 13 | id: `icon-${i}` 14 | }) 15 | }) 16 | return arr 17 | } 18 | 19 | const store = new Vuex.Store({ 20 | state: { 21 | libraries: [ 22 | { 23 | name: 'Vue.js', 24 | slug: 'vue', 25 | image: 'vue.png' 26 | }, 27 | { 28 | name: 'React', 29 | slug: 'react', 30 | image: 'react.png' 31 | }, 32 | { 33 | name: 'Ember', 34 | slug: 'ember', 35 | image: 'ember.png' 36 | } 37 | ], 38 | icons: generateIcons(9) 39 | }, 40 | getters: { 41 | getLibBySlug: state => slug => state.libraries.find(s => s.slug === slug), 42 | getIconById: state => id => state.icons.find(i => i.id === id) 43 | } 44 | }) 45 | 46 | export default store -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-overdrive", 3 | "version": "1.2.0", 4 | "description": "Super easy magic-move transitions for Vue apps, powered by Ramjet", 5 | "main": "dist/overdrive.umd.js", 6 | "module": "dist/overdrive.esm.js", 7 | "unpkg": "dist/overdrive.min.js", 8 | "repository": "https://github.com/mattrothenberg/vue-overdrive", 9 | "author": "Matt Rothenberg", 10 | "license": "MIT", 11 | "scripts": { 12 | "build": "rollup -c rollup.config.js" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.2.2", 16 | "@vue/component-compiler-utils": "^2.3.1", 17 | "babel-preset-latest": "^6.24.1", 18 | "eslint": "^5.11.0", 19 | "eslint-config-prettier": "^3.3.0", 20 | "eslint-plugin-prettier": "^3.0.0", 21 | "eslint-plugin-vue": "^5.0.0", 22 | "prettier": "^1.15.3", 23 | "rollup": "^1.6.0", 24 | "rollup-plugin-commonjs": "^9.2.1", 25 | "rollup-plugin-node-resolve": "^4.0.1", 26 | "rollup-plugin-vue": "^4.7.2", 27 | "vue": "^2.5.21", 28 | "vue-hot-reload-api": "^2.3.1", 29 | "vue-template-compiler": "^2.5.21" 30 | }, 31 | "dependencies": { 32 | "ramjet": "^0.6.0-alpha" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/src/components/IconIndex.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | 32 | 51 | -------------------------------------------------------------------------------- /examples/src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 60 | -------------------------------------------------------------------------------- /examples/src/components/LibraryIndex.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | 40 | 52 | -------------------------------------------------------------------------------- /examples/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /examples/src/components/IconDetail.vue: -------------------------------------------------------------------------------- 1 | 13 | 49 | 50 | 51 | 58 | -------------------------------------------------------------------------------- /examples/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/src/components/Tiles.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 49 | 50 | -------------------------------------------------------------------------------- /examples/src/components/LibraryDetail.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 54 | 55 | 67 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ramjet-experimentation", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "Matt Rothenberg ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "chroma-js": "^1.3.6", 14 | "eases": "^1.0.8", 15 | "lodash": "^4.17.5", 16 | "vue": "^2.5.2", 17 | "vue-router": "^3.0.1", 18 | "vuex": "^3.0.1" 19 | }, 20 | "devDependencies": { 21 | "autoprefixer": "^7.1.2", 22 | "babel-core": "^6.22.1", 23 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 24 | "babel-loader": "^7.1.1", 25 | "babel-plugin-syntax-jsx": "^6.18.0", 26 | "babel-plugin-transform-runtime": "^6.22.0", 27 | "babel-plugin-transform-vue-jsx": "^3.5.0", 28 | "babel-preset-env": "^1.3.2", 29 | "babel-preset-stage-2": "^6.22.0", 30 | "chalk": "^2.0.1", 31 | "copy-webpack-plugin": "^4.0.1", 32 | "css-loader": "^0.28.0", 33 | "extract-text-webpack-plugin": "^3.0.0", 34 | "file-loader": "^1.1.4", 35 | "friendly-errors-webpack-plugin": "^1.6.1", 36 | "html-webpack-plugin": "^2.30.1", 37 | "node-notifier": "^5.1.2", 38 | "optimize-css-assets-webpack-plugin": "^3.2.0", 39 | "ora": "^1.2.0", 40 | "portfinder": "^1.0.13", 41 | "postcss-import": "^11.0.0", 42 | "postcss-loader": "^2.0.8", 43 | "postcss-url": "^7.2.1", 44 | "rimraf": "^2.6.0", 45 | "semver": "^5.3.0", 46 | "shelljs": "^0.7.6", 47 | "uglifyjs-webpack-plugin": "^1.1.1", 48 | "url-loader": "^0.5.8", 49 | "vue-loader": "^13.3.0", 50 | "vue-style-loader": "^3.0.1", 51 | "vue-template-compiler": "^2.5.2", 52 | "webpack": "^3.6.0", 53 | "webpack-bundle-analyzer": "^2.9.0", 54 | "webpack-dev-server": "^2.9.1", 55 | "webpack-merge": "^4.1.0" 56 | }, 57 | "engines": { 58 | "node": ">= 6.0.0", 59 | "npm": ">= 3.0.0" 60 | }, 61 | "browserslist": [ 62 | "> 1%", 63 | "last 2 versions", 64 | "not ie <= 8" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /examples/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import HelloWorld from '@/components/HelloWorld' 4 | import Circle from '@/components/Circle' 5 | import Rectangle from '@/components/Rectangle' 6 | import Triangle from '@/components/Triangle' 7 | import LibraryIndex from '@/components/LibraryIndex' 8 | import LibraryDetail from '@/components/LibraryDetail' 9 | import Shapes from '@/components/Shapes' 10 | import IconIndex from '@/components/IconIndex' 11 | import IconDetail from '@/components/IconDetail' 12 | import Sandbox from '@/components/Sandbox' 13 | import Tiles from '@/components/Tiles' 14 | import TileDetail from '@/components/TileDetail' 15 | 16 | Vue.use(Router) 17 | 18 | export default new Router({ 19 | routes: [ 20 | { 21 | path: '/sandbox', 22 | components: { 23 | main: Sandbox 24 | } 25 | }, 26 | { 27 | path: '/shapes', 28 | components: { 29 | main: Shapes 30 | }, 31 | children: [ 32 | { 33 | path: '/circle', 34 | name: 'Circle', 35 | component: Circle 36 | }, 37 | { 38 | path: '/rectangle', 39 | name: 'Rectangle', 40 | component: Rectangle 41 | }, 42 | { 43 | path: '/triangle', 44 | name: 'Triangle', 45 | component: Triangle 46 | } 47 | ] 48 | }, 49 | { 50 | path: '/libraries', 51 | name: 'libraries', 52 | components: { 53 | main: LibraryIndex 54 | } 55 | }, 56 | { 57 | path: '/libraries/:slug/', 58 | name: 'library-detail', 59 | components: { 60 | main: LibraryDetail 61 | } 62 | }, 63 | { 64 | path: '/tiles', 65 | name: 'tiles', 66 | components: { 67 | main: Tiles 68 | } 69 | }, 70 | { 71 | path: '/tiles/:name/', 72 | name: 'tile-detail', 73 | components: { 74 | main: TileDetail 75 | } 76 | }, 77 | { 78 | path: '/icons', 79 | components: { 80 | icon: IconIndex 81 | } 82 | }, 83 | { 84 | path: '/icons/:id', 85 | components: { 86 | icon: IconDetail 87 | } 88 | } 89 | ] 90 | }) 91 | -------------------------------------------------------------------------------- /examples/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | 24 | /** 25 | * Source Maps 26 | */ 27 | 28 | // https://webpack.js.org/configuration/devtool/#development 29 | devtool: 'cheap-module-eval-source-map', 30 | 31 | // If you have problems debugging vue-files in devtools, 32 | // set this to false - it *may* help 33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 34 | cacheBusting: true, 35 | 36 | cssSourceMap: true 37 | }, 38 | 39 | build: { 40 | // Template for index.html 41 | index: path.resolve(__dirname, '../dist/index.html'), 42 | 43 | // Paths 44 | assetsRoot: path.resolve(__dirname, '../dist'), 45 | assetsSubDirectory: 'static', 46 | assetsPublicPath: '/', 47 | 48 | /** 49 | * Source Maps 50 | */ 51 | 52 | productionSourceMap: true, 53 | // https://webpack.js.org/configuration/devtool/#production 54 | devtool: '#source-map', 55 | 56 | // Gzip off by default as many popular static hosts such as 57 | // Surge or Netlify already gzip all static assets for you. 58 | // Before setting to `true`, make sure to: 59 | // npm install --save-dev compression-webpack-plugin 60 | productionGzip: false, 61 | productionGzipExtensions: ['js', 'css'], 62 | 63 | // Run the build command with an extra argument to 64 | // View the bundle analyzer report after build finishes: 65 | // `npm run build --report` 66 | // Set to `true` or `false` to always turn it on or off 67 | bundleAnalyzerReport: process.env.npm_config_report 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | 12 | 13 | module.exports = { 14 | context: path.resolve(__dirname, '../'), 15 | entry: { 16 | app: './src/main.js' 17 | }, 18 | output: { 19 | path: config.build.assetsRoot, 20 | filename: '[name].js', 21 | publicPath: process.env.NODE_ENV === 'production' 22 | ? config.build.assetsPublicPath 23 | : config.dev.assetsPublicPath 24 | }, 25 | resolve: { 26 | extensions: ['.js', '.vue', '.json'], 27 | alias: { 28 | 'vue$': 'vue/dist/vue.esm.js', 29 | '@': resolve('src'), 30 | } 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.vue$/, 36 | loader: 'vue-loader', 37 | options: vueLoaderConfig 38 | }, 39 | { 40 | test: /\.js$/, 41 | loader: 'babel-loader', 42 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 43 | }, 44 | { 45 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 46 | loader: 'url-loader', 47 | options: { 48 | limit: 10000, 49 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 50 | } 51 | }, 52 | { 53 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 54 | loader: 'url-loader', 55 | options: { 56 | limit: 10000, 57 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 58 | } 59 | }, 60 | { 61 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 62 | loader: 'url-loader', 63 | options: { 64 | limit: 10000, 65 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 66 | } 67 | } 68 | ] 69 | }, 70 | node: { 71 | // prevent webpack from injecting useless setImmediate polyfill because Vue 72 | // source contains it (although only uses it if it's native). 73 | setImmediate: false, 74 | // prevent webpack from injecting mocks to Node native modules 75 | // that does not make sense for the client 76 | dgram: 'empty', 77 | fs: 'empty', 78 | net: 'empty', 79 | tls: 'empty', 80 | child_process: 'empty' 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Overdrive.vue: -------------------------------------------------------------------------------- 1 | 115 | -------------------------------------------------------------------------------- /examples/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Heads up! 2 | The fate of this repo is uncertain. I recommend using my new library, [`vue-flip-toolkit`](https://github.com/mattrothenberg/vue-flip-toolkit) for all of your magic-move transition needs. 3 | 4 |

5 | 6 |

7 | 8 |

vue-overdrive

9 |
Super easy magic-move transitions for Vue apps, powered by Ramjet
10 |

11 | 12 | npm version 13 | 14 |

15 | 16 | 17 | ## Project Install 18 | 19 | ``` bash 20 | # npm 21 | npm install vue-overdrive 22 | ``` 23 | 24 | ``` bash 25 | # yarn 26 | yarn add vue-overdrive 27 | ``` 28 | 29 | ## Warning! 30 | Currently, `vue-overdrive` isn't able to morph between shapes with percentage-based `border-radius` values. You'll need to use a pixel-based value, or you'll get a nasty `TypeError`. The issue is being tracked here: https://github.com/Rich-Harris/ramjet/issues/57 31 | 32 | ## Examples 33 | 34 | Material 35 | 36 | ### 1) Morph Shapes ([source](examples/src/router/index.js#L22-L38)) 37 | https://vue-overdrive.netlify.com/#/shapes 38 | 39 | Shape morph 40 | 41 | ### 2) Material-esque transformation ([source](examples/src/router/index.js#L41-L53)) 42 | https://vue-overdrive.netlify.com/#/libraries 43 | 44 | Material transformation 45 | 46 | ### 3) iOS-inspired icon effect ([source](examples/src/router/index.js#L54-L65)) 47 | https://vue-overdrive.netlify.com/#/icons 48 | 49 | iOS icon effect 50 | 51 | 52 | ## What is it? 53 | A Vue.js port of the amazing [React Overdrive](https://github.com/berzniz/react-overdrive), using [Ramjet](https://github.com/Rich-Harris/ramjet) under the hood. 54 | 55 | ## How does it work? 56 | Just like with React Overdrive, wrap any two elements in a component. Add the same id to create a transition between the elements. 57 | 58 | ### Import and install 59 | 60 | ```js 61 | import Overdrive from 'vue-overdrive' 62 | Vue.use(Overdrive) 63 | ``` 64 | 65 | or 66 | 67 | ```js 68 | import { VOverdrive } from 'vue-overdrive' 69 | 70 | // Register the component locally 71 | components: { 72 | 'overdrive': VOverdrive 73 | } 74 | ``` 75 | 76 | ### Set up (at least) two different routes with Vue Router 77 | 78 | Inside your routes file – 79 | ```js 80 | { 81 | path: '/circle', 82 | name: 'Circle', 83 | component: Circle 84 | }, 85 | { 86 | path: '/rectangle', 87 | name: 'Rectangle', 88 | component: Rectangle 89 | } 90 | ``` 91 | 92 | ### Scaffold your components 93 | 94 | In `Circle.vue`: 95 | ```vue 96 | 101 | 102 | 113 | 114 | 123 | 124 | ``` 125 | 126 | And in `Rectangle.vue` – 127 | 128 | ```vue 129 | 134 | ``` 135 | 136 | ### Usage with `v-if` 137 | If you're not using Vue Router (and instead using Vue's built-in `v-if` directive), be sure to specify a unique `key` prop on each instance of `` 138 | 139 | ```vue 140 | 141 | 142 | 143 | 144 | 145 | 146 | ``` 147 | 148 | ### Customize it (API) 149 | 150 | 151 | | Prop | Description | Default Value | 152 | |---------- |------------------------------------------------------ |----------------- | 153 | | id | Required. A unique string or number to identify the component. | | 154 | | tag | Wrapping element type | `div` | 155 | | duration | Animation duration (in milliseconds) | `250` | 156 | | easing | Easing Function (must pass a function) | `ramjet.linear` | 157 | 158 | | Event | Description | 159 | |------------------ |----------------------------------------------- | 160 | | `@animation-end` | Fires once the ramjet animation has completed | 161 | -------------------------------------------------------------------------------- /examples/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('css/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: true 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vendor modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /dist/overdrive.esm.js: -------------------------------------------------------------------------------- 1 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 2 | 3 | function createCommonjsModule(fn, module) { 4 | return module = { exports: {} }, fn(module, module.exports), module.exports; 5 | } 6 | 7 | var ramjet_umd = createCommonjsModule(function (module, exports) { 8 | (function (global, factory) { 9 | factory(exports); 10 | }(commonjsGlobal, function (exports) { 11 | var babelHelpers = {}; 12 | 13 | babelHelpers.classCallCheck = function (instance, Constructor) { 14 | if (!(instance instanceof Constructor)) { 15 | throw new TypeError("Cannot call a class as a function"); 16 | } 17 | }; 18 | 19 | var props = /\b(?:position|zIndex|opacity|transform|webkitTransform|mixBlendMode|filter|webkitFilter|isolation)\b/; 20 | 21 | function isFlexItem(node) { 22 | var display = getComputedStyle(node.parentNode).display; 23 | return display === 'flex' || display === 'inline-flex'; 24 | } 25 | 26 | function createsStackingContext(node) { 27 | var style = getComputedStyle(node); 28 | 29 | // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context 30 | if (style.position === 'fixed') return true; 31 | if (style.zIndex !== 'auto' && style.position !== 'static' || isFlexItem(node)) return true; 32 | if (+style.opacity < 1) return true; 33 | if ('transform' in style && style.transform !== 'none') return true; 34 | if ('webkitTransform' in style && style.webkitTransform !== 'none') return true; 35 | if ('mixBlendMode' in style && style.mixBlendMode !== 'normal') return true; 36 | if ('filter' in style && style.filter !== 'none') return true; 37 | if ('webkitFilter' in style && style.webkitFilter !== 'none') return true; 38 | if ('isolation' in style && style.isolation === 'isolate') return true; 39 | if (props.test(style.willChange)) return true; 40 | if (style.webkitOverflowScrolling === 'touch') return true; 41 | 42 | return false; 43 | } 44 | 45 | function findStackingContext(nodes) { 46 | var i = nodes.length; 47 | 48 | while (i--) { 49 | if (createsStackingContext(nodes[i])) return nodes[i]; 50 | } 51 | 52 | return null; 53 | } 54 | 55 | function getAncestors(node) { 56 | var ancestors = []; 57 | 58 | while (node) { 59 | ancestors.push(node); 60 | node = node.parentNode; 61 | } 62 | 63 | return ancestors; // [ node, ... , , document ] 64 | } 65 | 66 | function getZIndex(node) { 67 | return node && Number(getComputedStyle(node).zIndex) || 0; 68 | } 69 | 70 | function last(array) { 71 | return array[array.length - 1]; 72 | } 73 | 74 | function compare(a, b) { 75 | if (a === b) throw new Error('Cannot compare node with itself'); 76 | 77 | var ancestors = { 78 | a: getAncestors(a), 79 | b: getAncestors(b) 80 | }; 81 | 82 | var commonAncestor = undefined; 83 | 84 | // remove shared ancestors 85 | while (last(ancestors.a) === last(ancestors.b)) { 86 | a = ancestors.a.pop(); 87 | b = ancestors.b.pop(); 88 | 89 | commonAncestor = a; 90 | } 91 | 92 | var stackingContexts = { 93 | a: findStackingContext(ancestors.a), 94 | b: findStackingContext(ancestors.b) 95 | }; 96 | 97 | var zIndexes = { 98 | a: getZIndex(stackingContexts.a), 99 | b: getZIndex(stackingContexts.b) 100 | }; 101 | 102 | if (zIndexes.a === zIndexes.b) { 103 | var children = commonAncestor.childNodes; 104 | 105 | var furthestAncestors = { 106 | a: last(ancestors.a), 107 | b: last(ancestors.b) 108 | }; 109 | 110 | var i = children.length; 111 | while (i--) { 112 | var child = children[i]; 113 | if (child === furthestAncestors.a) return 1; 114 | if (child === furthestAncestors.b) return -1; 115 | } 116 | } 117 | 118 | return Math.sign(zIndexes.a - zIndexes.b); 119 | } 120 | 121 | var svgns = 'http://www.w3.org/2000/svg'; 122 | 123 | function hideNode(node) { 124 | node.__ramjetOriginalTransition__ = node.style.webkitTransition || node.style.transition; 125 | node.__ramjetOriginalOpacity__ = node.style.opacity; 126 | 127 | node.style.webkitTransition = node.style.transition = ''; 128 | 129 | node.style.opacity = 0; 130 | } 131 | 132 | function showNode(node) { 133 | if ('__ramjetOriginalOpacity__' in node) { 134 | node.style.transition = ''; 135 | node.style.opacity = node.__ramjetOriginalOpacity__; 136 | 137 | if (node.__ramjetOriginalTransition__) { 138 | setTimeout(function () { 139 | node.style.transition = node.__ramjetOriginalTransition__; 140 | }); 141 | } 142 | } 143 | } 144 | 145 | function cloneNode(node) { 146 | var clone = node.cloneNode(); 147 | 148 | var isSvg = node.parentNode && node.parentNode.namespaceURI === svgns; 149 | 150 | if (node.nodeType === 1) { 151 | var width = node.style.width; 152 | var height = node.style.height; 153 | 154 | clone.setAttribute('style', window.getComputedStyle(node).cssText); 155 | 156 | if (isSvg) { 157 | clone.style.width = width; 158 | clone.style.height = height; 159 | } 160 | 161 | var len = node.childNodes.length; 162 | var i = undefined; 163 | 164 | for (i = 0; i < len; i += 1) { 165 | clone.appendChild(cloneNode(node.childNodes[i])); 166 | } 167 | } 168 | 169 | return clone; 170 | } 171 | 172 | var bgColorRegexp = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d?.\d+))?\)$/; 173 | 174 | function parseColor(str) { 175 | var match = bgColorRegexp.exec(str); 176 | 177 | if (!match) return null; 178 | 179 | return { 180 | r: +match[1], 181 | g: +match[2], 182 | b: +match[3], 183 | alpha: match[4] ? +match[4] : 1 184 | }; 185 | } 186 | 187 | var borderRadiusRegex = /^(\d+)px(?: (\d+)px)?$/; 188 | 189 | function parseBorderRadius(str) { 190 | var match = borderRadiusRegex.exec(str); 191 | 192 | return match[2] ? { x: +match[1], y: +match[2] } : { x: +match[1], y: +match[1] }; 193 | } 194 | 195 | function findParentByTagName(node, tagName) { 196 | while (node) { 197 | if (node.tagName === tagName) { 198 | return node; 199 | } 200 | 201 | node = node.parentNode; 202 | } 203 | } 204 | 205 | function findTransformParent(node) { 206 | var isSvg = node.namespaceURI === svgns && node.tagName !== 'svg'; 207 | return isSvg ? findParentByTagName(node, 'svg') : node.parentNode; 208 | } 209 | 210 | var div = document.createElement('div'); 211 | 212 | var keyframesSupported = true; 213 | var TRANSFORM = undefined; 214 | var TRANSFORM_ORIGIN = undefined; 215 | var TRANSFORM_CSS = undefined; 216 | var KEYFRAMES = undefined; 217 | var ANIMATION = undefined; 218 | var ANIMATION_DIRECTION = undefined; 219 | var ANIMATION_DURATION = undefined; 220 | var ANIMATION_ITERATION_COUNT = undefined; 221 | var ANIMATION_NAME = undefined; 222 | var ANIMATION_TIMING_FUNCTION = undefined; 223 | var ANIMATION_END = undefined; 224 | 225 | // We have to browser-sniff for IE11, because it was apparently written 226 | // by a barrel of stoned monkeys - http://jsfiddle.net/rich_harris/oquLu2qL/ 227 | 228 | // http://stackoverflow.com/questions/17907445/how-to-detect-ie11 229 | var isIe11 = !window.ActiveXObject && 'ActiveXObject' in window; 230 | 231 | if (!isIe11 && ('transform' in div.style || 'webkitTransform' in div.style) && ('animation' in div.style || 'webkitAnimation' in div.style)) { 232 | keyframesSupported = true; 233 | 234 | if ('webkitTransform' in div.style) { 235 | TRANSFORM = 'webkitTransform'; 236 | TRANSFORM_CSS = '-webkit-transform'; 237 | TRANSFORM_ORIGIN = 'webkitTransformOrigin'; 238 | } else { 239 | TRANSFORM = TRANSFORM_CSS = 'transform'; 240 | TRANSFORM_ORIGIN = 'transformOrigin'; 241 | } 242 | 243 | if ('animation' in div.style) { 244 | KEYFRAMES = '@keyframes'; 245 | 246 | ANIMATION = 'animation'; 247 | ANIMATION_DIRECTION = 'animationDirection'; 248 | ANIMATION_DURATION = 'animationDuration'; 249 | ANIMATION_ITERATION_COUNT = 'animationIterationCount'; 250 | ANIMATION_NAME = 'animationName'; 251 | ANIMATION_TIMING_FUNCTION = 'animationTimingFunction'; 252 | 253 | ANIMATION_END = 'animationend'; 254 | } else { 255 | KEYFRAMES = '@-webkit-keyframes'; 256 | 257 | ANIMATION = 'webkitAnimation'; 258 | ANIMATION_DIRECTION = 'webkitAnimationDirection'; 259 | ANIMATION_DURATION = 'webkitAnimationDuration'; 260 | ANIMATION_ITERATION_COUNT = 'webkitAnimationIterationCount'; 261 | ANIMATION_NAME = 'webkitAnimationName'; 262 | ANIMATION_TIMING_FUNCTION = 'webkitAnimationTimingFunction'; 263 | 264 | ANIMATION_END = 'webkitAnimationEnd'; 265 | } 266 | } else { 267 | keyframesSupported = false; 268 | } 269 | 270 | var IDENTITY = [1, 0, 0, 1, 0, 0]; 271 | 272 | function multiply(_ref, _ref2) { 273 | var a1 = _ref[0]; 274 | var b1 = _ref[1]; 275 | var c1 = _ref[2]; 276 | var d1 = _ref[3]; 277 | var e1 = _ref[4]; 278 | var f1 = _ref[5]; 279 | var a2 = _ref2[0]; 280 | var b2 = _ref2[1]; 281 | var c2 = _ref2[2]; 282 | var d2 = _ref2[3]; 283 | var e2 = _ref2[4]; 284 | var f2 = _ref2[5]; 285 | 286 | return [a1 * a2 + c1 * b2, // a 287 | b1 * a2 + d1 * b2, // b 288 | a1 * c2 + c1 * d2, // c 289 | b1 * c2 + d1 * d2, // d 290 | a1 * e2 + c1 * f2 + e1, // e 291 | b1 * e2 + d1 * f2 + f1 // f 292 | ]; 293 | } 294 | 295 | function invert(_ref3) { 296 | var a = _ref3[0]; 297 | var b = _ref3[1]; 298 | var c = _ref3[2]; 299 | var d = _ref3[3]; 300 | var e = _ref3[4]; 301 | var f = _ref3[5]; 302 | 303 | var determinant = a * d - c * b; 304 | 305 | return [d / determinant, b / -determinant, c / -determinant, a / determinant, (c * f - e * d) / determinant, (e * b - a * f) / determinant]; 306 | } 307 | 308 | function pythag(a, b) { 309 | return Math.sqrt(a * a + b * b); 310 | } 311 | 312 | function decompose(_ref4) { 313 | var a = _ref4[0]; 314 | var b = _ref4[1]; 315 | var c = _ref4[2]; 316 | var d = _ref4[3]; 317 | var e = _ref4[4]; 318 | var f = _ref4[5]; 319 | 320 | // If determinant equals zero (e.g. x scale or y scale equals zero), 321 | // the matrix cannot be decomposed 322 | if (a * d - b * c === 0) return null; 323 | 324 | // See https://github.com/Rich-Harris/Neo/blob/master/Neo.js for 325 | // an explanation of the following 326 | var scaleX = pythag(a, b); 327 | a /= scaleX; 328 | b /= scaleX; 329 | 330 | var scaledShear = a * c + b * d; 331 | var desheared = [a * -scaledShear + c, b * -scaledShear + d]; 332 | 333 | var scaleY = pythag(desheared[0], desheared[1]); 334 | 335 | var skewX = scaledShear / scaleY; 336 | 337 | var rotate = b > 0 ? Math.acos(a) : 2 * Math.PI - Math.acos(a); 338 | 339 | return { 340 | rotate: rotate, 341 | scaleX: scaleX, 342 | scaleY: scaleY, 343 | skewX: skewX, 344 | translateX: e, 345 | translateY: f 346 | }; 347 | } 348 | 349 | function parseMatrixTransformString(transform) { 350 | if (transform.slice(0, 7) !== 'matrix(') { 351 | throw new Error('Could not parse transform string (' + transform + ')'); 352 | } 353 | 354 | return transform.slice(7, -1).split(' ').map(parseFloat); 355 | } 356 | 357 | function getCumulativeTransformMatrix(node) { 358 | if (node.namespaceURI === svgns) { 359 | var _node$getCTM = node.getCTM(); 360 | 361 | var a = _node$getCTM.a; 362 | var b = _node$getCTM.b; 363 | var c = _node$getCTM.c; 364 | var d = _node$getCTM.d; 365 | var e = _node$getCTM.e; 366 | var f = _node$getCTM.f; 367 | 368 | return [a, b, c, d, e, f]; 369 | } 370 | 371 | var matrix = [1, 0, 0, 1, 0, 0]; 372 | 373 | while (node instanceof Element) { 374 | var parentMatrix = getTransformMatrix(node); 375 | 376 | if (parentMatrix) { 377 | matrix = multiply(parentMatrix, matrix); 378 | } 379 | 380 | node = findTransformParent(node); 381 | } 382 | 383 | return matrix; 384 | } 385 | 386 | function getTransformMatrix(node) { 387 | if (node.namespaceURI === svgns) { 388 | var ctm = getCumulativeTransformMatrix(node); 389 | var parentCTM = getCumulativeTransformMatrix(node.parentNode); 390 | return multiply(invert(parentCTM), ctm); 391 | } 392 | 393 | var style = getComputedStyle(node); 394 | var transform = style[TRANSFORM]; 395 | 396 | if (transform === 'none') { 397 | return null; 398 | } 399 | 400 | var origin = style[TRANSFORM_ORIGIN].split(' ').map(parseFloat); 401 | 402 | var matrix = parseMatrixTransformString(transform); 403 | 404 | // compensate for the transform origin (we want to express everything in [0,0] terms) 405 | matrix = multiply([1, 0, 0, 1, origin[0], origin[1]], matrix); 406 | matrix = multiply(matrix, [1, 0, 0, 1, -origin[0], -origin[1]]); 407 | 408 | // TODO if is SVG, multiply by CTM, to account for viewBox 409 | 410 | return matrix; 411 | } 412 | 413 | function getBoundingClientRect(node, invertedParentCTM) { 414 | var originalTransformOrigin = node.style[TRANSFORM_ORIGIN]; 415 | var originalTransform = node.style[TRANSFORM]; 416 | var originalTransformAttribute = node.getAttribute('transform'); // SVG 417 | 418 | node.style[TRANSFORM_ORIGIN] = '0 0'; 419 | node.style[TRANSFORM] = 'matrix(' + invertedParentCTM.join(',') + ')'; 420 | 421 | var bcr = node.getBoundingClientRect(); 422 | 423 | // reset 424 | node.style[TRANSFORM_ORIGIN] = originalTransformOrigin; 425 | node.style[TRANSFORM] = originalTransform; 426 | node.setAttribute('transform', originalTransformAttribute || ''); // TODO remove attribute altogether if null? 427 | 428 | return bcr; 429 | } 430 | 431 | var Wrapper = function () { 432 | function Wrapper(node, options) { 433 | babelHelpers.classCallCheck(this, Wrapper); 434 | 435 | this.init(node, options); 436 | } 437 | 438 | Wrapper.prototype.init = function init(node) { 439 | this._node = node; 440 | this._clone = cloneNode(node); 441 | 442 | var style = window.getComputedStyle(node); 443 | this.style = style; 444 | 445 | // we need to get the 'naked' boundingClientRect, i.e. 446 | // without any transforms 447 | // TODO what if the node is the root node? 448 | var parentCTM = node.namespaceURI === 'svg' ? node.parentNode.getScreenCTM() : getCumulativeTransformMatrix(node.parentNode); 449 | this.invertedParentCTM = invert(parentCTM); 450 | this.transform = getTransformMatrix(node) || IDENTITY; 451 | this.ctm = multiply(parentCTM, this.transform); 452 | 453 | var bcr = getBoundingClientRect(node, this.invertedParentCTM); 454 | this.bcr = bcr; 455 | 456 | // TODO create a flat array? easier to work with later? 457 | var borderRadius = { 458 | tl: parseBorderRadius(style.borderTopLeftRadius), 459 | tr: parseBorderRadius(style.borderTopRightRadius), 460 | br: parseBorderRadius(style.borderBottomRightRadius), 461 | bl: parseBorderRadius(style.borderBottomLeftRadius) 462 | }; 463 | 464 | this.borderRadius = borderRadius; 465 | this.opacity = +style.opacity; 466 | this.rgba = parseColor(style.backgroundColor); 467 | 468 | this.left = bcr.left; 469 | this.top = bcr.top; 470 | this.width = bcr.width; 471 | this.height = bcr.height; 472 | }; 473 | 474 | Wrapper.prototype.insert = function insert() { 475 | var bcr = this.bcr; 476 | 477 | var offsetParent = this._node.offsetParent; 478 | 479 | var clone = undefined; 480 | 481 | if (this._node.namespaceURI === svgns) { 482 | // TODO what if it's the itself, not a child? 483 | var svg = findParentByTagName(this._node, 'svg'); // TODO should be the namespace boundary - could be SVG inside SVG 484 | 485 | clone = svg.cloneNode(false); 486 | clone.appendChild(this._clone); // TODO what about transforms? 487 | } else { 488 | clone = this._clone; 489 | } 490 | 491 | var offsetParentStyle = window.getComputedStyle(offsetParent); 492 | var offsetParentBcr = getBoundingClientRect(offsetParent, invert(getCumulativeTransformMatrix(offsetParent.parentNode))); 493 | 494 | clone.style.position = 'absolute'; 495 | clone.style[TRANSFORM_ORIGIN] = '0 0'; 496 | clone.style.top = bcr.top - parseInt(this.style.marginTop, 10) - (offsetParentBcr.top - parseInt(offsetParentStyle.marginTop, 10)) + 'px'; 497 | clone.style.left = bcr.left - parseInt(this.style.marginLeft, 10) - (offsetParentBcr.left - parseInt(offsetParentStyle.marginLeft, 10)) + 'px'; 498 | 499 | // TODO we need to account for transforms *between* the offset parent and the node 500 | 501 | offsetParent.appendChild(clone); 502 | }; 503 | 504 | Wrapper.prototype.detach = function detach() { 505 | this._clone.parentNode.removeChild(this._clone); 506 | }; 507 | 508 | Wrapper.prototype.setOpacity = function setOpacity(opacity) { 509 | this._clone.style.opacity = opacity; 510 | }; 511 | 512 | Wrapper.prototype.setTransform = function setTransform(transform) { 513 | this._clone.style.transform = this._clone.style.webkitTransform = this._clone.style.msTransform = transform; 514 | }; 515 | 516 | Wrapper.prototype.setBackgroundColor = function setBackgroundColor(color) { 517 | this._clone.style.backgroundColor = color; 518 | }; 519 | 520 | Wrapper.prototype.setBorderRadius = function setBorderRadius(borderRadius) { 521 | this._clone.style.borderRadius = borderRadius; 522 | }; 523 | 524 | Wrapper.prototype.animateWithKeyframes = function animateWithKeyframes(id, duration) { 525 | this._clone.style[ANIMATION_DIRECTION] = 'alternate'; 526 | this._clone.style[ANIMATION_DURATION] = duration / 1000 + 's'; 527 | this._clone.style[ANIMATION_ITERATION_COUNT] = 1; 528 | this._clone.style[ANIMATION_NAME] = id; 529 | this._clone.style[ANIMATION_TIMING_FUNCTION] = 'linear'; 530 | }; 531 | 532 | Wrapper.prototype.freeze = function freeze() { 533 | var computedStyle = getComputedStyle(this._clone); 534 | 535 | this.setOpacity(computedStyle.opacity); 536 | this.setTransform(computedStyle.transform); 537 | this.setBackgroundColor(computedStyle.backgroundColor); 538 | this.setBorderRadius(computedStyle.borderRadius); 539 | 540 | this._clone.style[ANIMATION] = 'none'; 541 | }; 542 | 543 | return Wrapper; 544 | }(); 545 | 546 | function getOpacityInterpolator(from, to, order) { 547 | var opacity = {}; 548 | 549 | return function (t) { 550 | var targetOpacity = (to - from) * t + from; 551 | 552 | // Based on the blending formula here. (http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending) 553 | // This is a quadratic blending function that makes the top layer and bottom layer blend linearly. 554 | // However there is an asymptote at target=1 so that needs to be handled with an if else statement. 555 | if (targetOpacity === 1) { 556 | if (order === 1) { 557 | opacity.from = 1 - t; 558 | opacity.to = 1; 559 | } else { 560 | opacity.from = 1; 561 | opacity.to = t; 562 | } 563 | } else { 564 | opacity.from = targetOpacity - t * t * targetOpacity; 565 | opacity.to = (targetOpacity - opacity.from) / (1 - opacity.from); 566 | } 567 | 568 | return opacity; 569 | }; 570 | } 571 | 572 | function getRgbaInterpolator(a, b, order) { 573 | if (a.alpha === 1 && b.alpha === 1) { 574 | // no need to animate anything 575 | return null; 576 | } 577 | 578 | var rgba = {}; 579 | var opacityAt = getOpacityInterpolator(a.alpha, b.alpha, order); 580 | 581 | return function (t) { 582 | var opacity = opacityAt(t); 583 | 584 | rgba.from = 'rgba(' + a.r + ',' + a.g + ',' + a.b + ',' + opacity.from + ')'; 585 | rgba.to = 'rgba(' + b.r + ',' + b.g + ',' + b.b + ',' + opacity.to + ')'; 586 | 587 | return rgba; 588 | }; 589 | } 590 | 591 | function interpolateArray(a, b) { 592 | var len = a.length; 593 | var array = new Array(len); 594 | 595 | return function (t) { 596 | var i = len; 597 | while (i--) { 598 | array[i] = a[i] + t * (b[i] - a[i]); 599 | } 600 | 601 | return array; 602 | }; 603 | } 604 | 605 | // Border radius is given as a string in the following form 606 | // 607 | // tl.x tr.x br.x bl.x / tl.y tr.y br.y bl.y 608 | // 609 | // ...where t, r, b and l are top, right, bottom, left, and 610 | // x and y are self-explanatory. Each value is followed by 'px' 611 | 612 | // TODO it must be possible to do this more simply. Maybe have 613 | // a flat array from the start? 614 | 615 | function getBorderRadiusInterpolator(a, b) { 616 | // TODO fast path - no transition needed 617 | 618 | var aWidth = a.width; 619 | var aHeight = a.height; 620 | 621 | var bWidth = b.width; 622 | var bHeight = b.height; 623 | 624 | a = a.borderRadius; 625 | b = b.borderRadius; 626 | 627 | var a_x_t0 = [a.tl.x, a.tr.x, a.br.x, a.bl.x]; 628 | var a_y_t0 = [a.tl.y, a.tr.y, a.br.y, a.bl.y]; 629 | 630 | var b_x_t1 = [b.tl.x, b.tr.x, b.br.x, b.bl.x]; 631 | var b_y_t1 = [b.tl.y, b.tr.y, b.br.y, b.bl.y]; 632 | 633 | var a_x_t1 = b_x_t1.map(function (x) { 634 | return x * aWidth / bWidth; 635 | }); 636 | var a_y_t1 = b_y_t1.map(function (y) { 637 | return y * aHeight / bHeight; 638 | }); 639 | 640 | var b_x_t0 = a_x_t0.map(function (x) { 641 | return x * bWidth / aWidth; 642 | }); 643 | var b_y_t0 = a_y_t0.map(function (y) { 644 | return y * bHeight / aHeight; 645 | }); 646 | 647 | var ax = interpolateArray(a_x_t0, a_x_t1); 648 | var ay = interpolateArray(a_y_t0, a_y_t1); 649 | 650 | var bx = interpolateArray(b_x_t0, b_x_t1); 651 | var by = interpolateArray(b_y_t0, b_y_t1); 652 | 653 | var borderRadius = {}; 654 | 655 | return function (t) { 656 | var x = ax(t); 657 | var y = ay(t); 658 | 659 | borderRadius.from = x.join('px ') + 'px / ' + y.join('px ') + 'px'; 660 | 661 | x = bx(t); 662 | y = by(t); 663 | 664 | borderRadius.to = x.join('px ') + 'px / ' + y.join('px ') + 'px'; 665 | 666 | return borderRadius; 667 | }; 668 | } 669 | 670 | function interpolateMatrices(a, b) { 671 | var transform = []; 672 | 673 | return function (t) { 674 | var i = a.length; 675 | while (i--) { 676 | var from = a[i]; 677 | var to = b[i]; 678 | transform[i] = from + t * (to - from); 679 | } 680 | 681 | return 'matrix(' + transform.join(',') + ')'; 682 | }; 683 | } 684 | 685 | function interpolate(a, b) { 686 | var d = b - a; 687 | return function (t) { 688 | return a + t * d; 689 | }; 690 | } 691 | 692 | function getRotation(radians) { 693 | while (radians > Math.PI) { 694 | radians -= Math.PI * 2; 695 | }while (radians < -Math.PI) { 696 | radians += Math.PI * 2; 697 | }return radians; 698 | } 699 | 700 | function interpolateDecomposedTransforms(a, b) { 701 | var rotate = interpolate(getRotation(a.rotate), getRotation(b.rotate)); 702 | var skewX = interpolate(a.skewX, b.skewX); 703 | var scaleX = interpolate(a.scaleX, b.scaleX); 704 | var scaleY = interpolate(a.scaleY, b.scaleY); 705 | var translateX = interpolate(a.translateX, b.translateX); 706 | var translateY = interpolate(a.translateY, b.translateY); 707 | 708 | return function (t) { 709 | var transform = 'translate(' + translateX(t) + 'px, ' + translateY(t) + 'px) rotate(' + rotate(t) + 'rad) skewX(' + skewX(t) + 'rad) scale(' + scaleX(t) + ', ' + scaleY(t) + ')'; 710 | return transform; 711 | }; 712 | } 713 | 714 | function getTransformInterpolator(a, b) { 715 | var scale_x = b.width / a.width; 716 | var scale_y = b.height / a.height; 717 | var d_x = b.left - a.left; 718 | var d_y = b.top - a.top; 719 | 720 | var a_start = a.transform; 721 | 722 | var move_a_to_b = [1, 0, 0, 1, d_x, d_y]; 723 | var scale_a_to_b = [scale_x, 0, 0, scale_y, 0, 0]; 724 | 725 | var matrix = IDENTITY; 726 | 727 | matrix = multiply(matrix, a.invertedParentCTM); 728 | matrix = multiply(matrix, move_a_to_b); 729 | matrix = multiply(matrix, b.ctm); 730 | matrix = multiply(matrix, scale_a_to_b); 731 | 732 | var decomposed_start = decompose(a_start); 733 | var decomposed_end = decompose(matrix); 734 | 735 | if (!decomposed_start || !decomposed_end) return interpolateMatrices(a_start, matrix); 736 | return interpolateDecomposedTransforms(decomposed_start, decomposed_end); 737 | } 738 | 739 | function linear(pos) { 740 | return pos; 741 | } 742 | 743 | function easeIn(pos) { 744 | return Math.pow(pos, 3); 745 | } 746 | 747 | function easeOut(pos) { 748 | return Math.pow(pos - 1, 3) + 1; 749 | } 750 | 751 | function easeInOut(pos) { 752 | if ((pos /= 0.5) < 1) { 753 | return 0.5 * Math.pow(pos, 3); 754 | } 755 | 756 | return 0.5 * (Math.pow(pos - 2, 3) + 2); 757 | } 758 | 759 | var head = document.getElementsByTagName('head')[0]; 760 | 761 | function addCss(css) { 762 | var styleElement = document.createElement('style'); 763 | styleElement.type = 'text/css'; 764 | 765 | // Internet Exploder won't let you use styleSheet.innerHTML - we have to 766 | // use styleSheet.cssText instead 767 | var styleSheet = styleElement.styleSheet; 768 | 769 | if (styleSheet) { 770 | styleSheet.cssText = css; 771 | } else { 772 | styleElement.innerHTML = css; 773 | } 774 | 775 | head.appendChild(styleElement); 776 | 777 | return function () { 778 | return head.removeChild(styleElement); 779 | }; 780 | } 781 | 782 | function getKeyframes(from, to, interpolators, easing, remaining, duration) { 783 | var numFrames = remaining / 16; 784 | 785 | var fromKeyframes = ''; 786 | var toKeyframes = ''; 787 | 788 | function addKeyframes(pc, t) { 789 | var opacity = interpolators.opacity(t); 790 | var backgroundColor = interpolators.backgroundColor ? interpolators.backgroundColor(t) : null; 791 | var borderRadius = interpolators.borderRadius ? interpolators.borderRadius(t) : null; 792 | var transformFrom = interpolators.transformFrom(t); 793 | var transformTo = interpolators.transformTo(1 - t); 794 | 795 | fromKeyframes += '\n' + (pc + '% {') + ('opacity: ' + opacity.from + ';') + (TRANSFORM_CSS + ': ' + transformFrom + ';') + (backgroundColor ? 'background-color: ' + backgroundColor.from + ';' : '') + (borderRadius ? 'border-radius: ' + borderRadius.from + ';' : '') + '}'; 796 | 797 | toKeyframes += '\n' + (pc + '% {') + ('opacity: ' + opacity.to + ';') + (TRANSFORM_CSS + ': ' + transformTo + ';') + (backgroundColor ? 'background-color: ' + backgroundColor.to + ';' : '') + (borderRadius ? 'border-radius: ' + borderRadius.to + ';' : '') + '}'; 798 | } 799 | 800 | var i = undefined; 801 | var startPos = 1 - remaining / duration; 802 | 803 | for (i = 0; i < numFrames; i += 1) { 804 | var relPos = i / numFrames; 805 | var absPos = startPos + remaining / duration * relPos; 806 | 807 | var pc = 100 * relPos; 808 | var t = easing(absPos); 809 | 810 | addKeyframes(pc, t); 811 | } 812 | 813 | addKeyframes(100, 1); 814 | 815 | return { fromKeyframes: fromKeyframes, toKeyframes: toKeyframes }; 816 | } 817 | 818 | function generateId() { 819 | return 'ramjet' + ~ ~(Math.random() * 1000000); 820 | } 821 | 822 | var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (fn) { 823 | return setTimeout(fn, 16); 824 | }; 825 | 826 | function transformer(from, to, options) { 827 | var duration = options.duration || 400; 828 | var easing = options.easing || linear; 829 | 830 | var useTimer = !keyframesSupported || !!options.useTimer; 831 | 832 | var order = compare(from._node, to._node); 833 | 834 | var interpolators = { 835 | opacity: getOpacityInterpolator(from.opacity, to.opacity, order), 836 | backgroundColor: options.interpolateBackgroundColor ? getRgbaInterpolator(from.rgba, to.rgba, order) : null, 837 | borderRadius: options.interpolateBorderRadius ? getBorderRadiusInterpolator(from, to) : null, 838 | transformFrom: getTransformInterpolator(from, to), 839 | transformTo: getTransformInterpolator(to, from) 840 | }; 841 | 842 | var running = undefined; 843 | var disposeCss = undefined; 844 | var torndown = undefined; 845 | 846 | var remaining = duration; 847 | var endTime = undefined; 848 | 849 | function tick() { 850 | if (!running) return; 851 | 852 | var timeNow = Date.now(); 853 | remaining = endTime - timeNow; 854 | 855 | if (remaining < 0) { 856 | transformer.teardown(); 857 | if (options.done) options.done(); 858 | 859 | return; 860 | } 861 | 862 | var t = easing(1 - remaining / duration); 863 | transformer.goto(t); 864 | 865 | rAF(tick); 866 | } 867 | 868 | var transformer = { 869 | teardown: function () { 870 | if (torndown) return transformer; 871 | 872 | running = false; 873 | torndown = true; 874 | 875 | from.detach(); 876 | to.detach(); 877 | 878 | from = null; 879 | to = null; 880 | 881 | return transformer; 882 | }, 883 | goto: function (pos) { 884 | transformer.pause(); 885 | 886 | var t = easing(pos); 887 | 888 | // opacity 889 | var opacity = interpolators.opacity(t); 890 | from.setOpacity(opacity.from); 891 | to.setOpacity(opacity.to); 892 | 893 | // transform 894 | var transformFrom = interpolators.transformFrom(t); 895 | var transformTo = interpolators.transformTo(1 - t); 896 | from.setTransform(transformFrom); 897 | to.setTransform(transformTo); 898 | 899 | // background color 900 | if (interpolators.backgroundColor) { 901 | var backgroundColor = interpolators.backgroundColor(t); 902 | from.setBackgroundColor(backgroundColor.from); 903 | to.setBackgroundColor(backgroundColor.to); 904 | } 905 | 906 | // border radius 907 | if (interpolators.borderRadius) { 908 | var borderRadius = interpolators.borderRadius(t); 909 | from.setBorderRadius(borderRadius.from); 910 | to.setBorderRadius(borderRadius.to); 911 | } 912 | 913 | return transformer; 914 | }, 915 | pause: function () { 916 | if (!running) return transformer; 917 | running = false; 918 | 919 | if (!useTimer) { 920 | // TODO derive current position somehow, use that rather than 921 | // current computed style (from and to get out of sync in 922 | // some browsers?) 923 | remaining = endTime - Date.now(); 924 | 925 | from.freeze(); 926 | to.freeze(); 927 | disposeCss(); 928 | } 929 | 930 | return transformer; 931 | }, 932 | play: function () { 933 | if (running) return transformer; 934 | running = true; 935 | 936 | endTime = Date.now() + remaining; 937 | 938 | if (useTimer) { 939 | rAF(tick); 940 | } else { 941 | var _getKeyframes = getKeyframes(from, to, interpolators, options.easing || linear, remaining, duration); 942 | 943 | var fromKeyframes = _getKeyframes.fromKeyframes; 944 | var toKeyframes = _getKeyframes.toKeyframes; 945 | 946 | var fromId = generateId(); 947 | var toId = generateId(); 948 | 949 | var css = '\n\t\t\t\t\t' + KEYFRAMES + ' ' + fromId + ' { ' + fromKeyframes + ' }\n\t\t\t\t\t' + KEYFRAMES + ' ' + toId + ' { ' + toKeyframes + ' }'; 950 | 951 | disposeCss = addCss(css); 952 | 953 | from.animateWithKeyframes(fromId, remaining); 954 | to.animateWithKeyframes(toId, remaining); 955 | } 956 | 957 | return transformer; 958 | } 959 | }; 960 | 961 | // handle animation end 962 | if (!useTimer) { 963 | (function () { 964 | var animating = 2; 965 | 966 | var done = function () { 967 | if (! --animating) { 968 | transformer.teardown(); 969 | 970 | if (options.done) options.done(); 971 | 972 | disposeCss(); 973 | } 974 | }; 975 | 976 | from._clone.addEventListener(ANIMATION_END, done); 977 | to._clone.addEventListener(ANIMATION_END, done); 978 | })(); 979 | } 980 | 981 | return transformer.play(); 982 | } 983 | 984 | function transform(fromNode, toNode) { 985 | var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 986 | 987 | if (typeof options === 'function') { 988 | options = { done: options }; 989 | } 990 | 991 | if (!('duration' in options)) { 992 | options.duration = 400; 993 | } 994 | 995 | var from = new Wrapper(fromNode, options); 996 | var to = new Wrapper(toNode, options); 997 | 998 | var order = compare(from._node, to._node); 999 | 1000 | from.setOpacity(1); 1001 | to.setOpacity(0); 1002 | 1003 | // in many cases, the stacking order of `from` and `to` is 1004 | // determined by their relative location in the document – 1005 | // so we need to preserve it 1006 | if (order === 1) { 1007 | to.insert(); 1008 | from.insert(); 1009 | } else { 1010 | from.insert(); 1011 | to.insert(); 1012 | } 1013 | 1014 | return transformer(from, to, options); 1015 | } 1016 | 1017 | function hide() { 1018 | for (var _len = arguments.length, nodes = Array(_len), _key = 0; _key < _len; _key++) { 1019 | nodes[_key] = arguments[_key]; 1020 | } 1021 | 1022 | nodes.forEach(hideNode); 1023 | } 1024 | 1025 | function show() { 1026 | for (var _len2 = arguments.length, nodes = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 1027 | nodes[_key2] = arguments[_key2]; 1028 | } 1029 | 1030 | nodes.forEach(showNode); 1031 | } 1032 | 1033 | exports.transform = transform; 1034 | exports.hide = hide; 1035 | exports.show = show; 1036 | exports.linear = linear; 1037 | exports.easeIn = easeIn; 1038 | exports.easeOut = easeOut; 1039 | exports.easeInOut = easeInOut; 1040 | 1041 | })); 1042 | 1043 | }); 1044 | 1045 | const components = {}; 1046 | const getPosition = (node, addOffset = false) => { 1047 | const rect = node.getBoundingClientRect(); 1048 | const computedStyle = window.getComputedStyle(node); 1049 | const marginTop = parseInt(computedStyle.marginTop, 10); 1050 | const marginLeft = parseInt(computedStyle.marginLeft, 10); 1051 | 1052 | return { 1053 | top: `${rect.top - 1054 | marginTop + 1055 | (addOffset ? 1 : 0) * 1056 | (window.pageYOffset || document.documentElement.scrollTop)}px`, 1057 | left: `${rect.left - marginLeft}px`, 1058 | width: `${rect.width}px`, 1059 | height: `${rect.height}px`, 1060 | borderRadius: computedStyle.borderRadius, 1061 | position: "absolute" 1062 | }; 1063 | }; 1064 | var script = { 1065 | props: { 1066 | tag: { 1067 | type: String, 1068 | default: "div" 1069 | }, 1070 | id: { 1071 | type: String, 1072 | required: true 1073 | }, 1074 | duration: { 1075 | type: Number, 1076 | duration: 400 1077 | }, 1078 | easing: { 1079 | type: Function, 1080 | default: ramjet_umd.linear 1081 | } 1082 | }, 1083 | data() { 1084 | return { 1085 | animating: false, 1086 | transformer: {} 1087 | }; 1088 | }, 1089 | methods: { 1090 | cache() { 1091 | components[this.id] = { 1092 | el: this.$slots.default, 1093 | pos: getPosition(this.$el.firstChild) 1094 | }; 1095 | }, 1096 | cloneAndAppend() { 1097 | const { el, pos } = components[this.id]; 1098 | const clone = el[0].elm.cloneNode(true); 1099 | clone.setAttribute("data-clone", this.id); 1100 | Object.assign(clone.style, pos); 1101 | document.body.appendChild(clone); 1102 | }, 1103 | bustCache() { 1104 | Object.keys(components).forEach(id => { 1105 | components[id] = false; 1106 | }); 1107 | }, 1108 | animate(cb = () => {}) { 1109 | const a = document.querySelector(`[data-clone="${this.id}"]`); 1110 | const b = this.$el.firstChild; 1111 | this.animating = true; 1112 | this.transformer = ramjet_umd.transform(a, b, { 1113 | duration: this.duration, 1114 | easing: this.easing, 1115 | appendToBody: true, 1116 | done: () => { 1117 | cb(a, b); 1118 | this.animating = false; 1119 | this.$emit("animation-end"); 1120 | } 1121 | }); 1122 | ramjet_umd.hide(a, b); 1123 | }, 1124 | handleMatch() { 1125 | this.cloneAndAppend(); 1126 | const cb = (a, b) => { 1127 | ramjet_umd.show(b); 1128 | }; 1129 | this.$nextTick(() => { 1130 | this.animate(cb); 1131 | const clone = document.querySelector(`[data-clone="${this.id}"]`); 1132 | document.body.removeChild(clone); 1133 | this.cache(); 1134 | }); 1135 | } 1136 | }, 1137 | mounted() { 1138 | const match = components[this.id]; 1139 | if (match) { 1140 | this.handleMatch(); 1141 | } else { 1142 | this.cache(); 1143 | } 1144 | }, 1145 | beforeDestroy() { 1146 | if (this.animating) { 1147 | this.transformer.teardown(); 1148 | } 1149 | }, 1150 | render(h) { 1151 | return h(this.tag, [this.$slots.default]); 1152 | } 1153 | }; 1154 | 1155 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier 1156 | /* server only */ 1157 | , shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 1158 | if (typeof shadowMode !== 'boolean') { 1159 | createInjectorSSR = createInjector; 1160 | createInjector = shadowMode; 1161 | shadowMode = false; 1162 | } // Vue.extend constructor export interop. 1163 | 1164 | 1165 | var options = typeof script === 'function' ? script.options : script; // render functions 1166 | 1167 | if (template && template.render) { 1168 | options.render = template.render; 1169 | options.staticRenderFns = template.staticRenderFns; 1170 | options._compiled = true; // functional template 1171 | 1172 | if (isFunctionalTemplate) { 1173 | options.functional = true; 1174 | } 1175 | } // scopedId 1176 | 1177 | 1178 | if (scopeId) { 1179 | options._scopeId = scopeId; 1180 | } 1181 | 1182 | var hook; 1183 | 1184 | if (moduleIdentifier) { 1185 | // server build 1186 | hook = function hook(context) { 1187 | // 2.3 injection 1188 | context = context || // cached call 1189 | this.$vnode && this.$vnode.ssrContext || // stateful 1190 | this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional 1191 | // 2.2 with runInNewContext: true 1192 | 1193 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 1194 | context = __VUE_SSR_CONTEXT__; 1195 | } // inject component styles 1196 | 1197 | 1198 | if (style) { 1199 | style.call(this, createInjectorSSR(context)); 1200 | } // register component module identifier for async chunk inference 1201 | 1202 | 1203 | if (context && context._registeredComponents) { 1204 | context._registeredComponents.add(moduleIdentifier); 1205 | } 1206 | }; // used by ssr in case component is cached and beforeCreate 1207 | // never gets called 1208 | 1209 | 1210 | options._ssrRegister = hook; 1211 | } else if (style) { 1212 | hook = shadowMode ? function () { 1213 | style.call(this, createInjectorShadow(this.$root.$options.shadowRoot)); 1214 | } : function (context) { 1215 | style.call(this, createInjector(context)); 1216 | }; 1217 | } 1218 | 1219 | if (hook) { 1220 | if (options.functional) { 1221 | // register for functional component in vue file 1222 | var originalRender = options.render; 1223 | 1224 | options.render = function renderWithStyleInjection(h, context) { 1225 | hook.call(context); 1226 | return originalRender(h, context); 1227 | }; 1228 | } else { 1229 | // inject component registration as beforeCreate hook 1230 | var existing = options.beforeCreate; 1231 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 1232 | } 1233 | } 1234 | 1235 | return script; 1236 | } 1237 | 1238 | var normalizeComponent_1 = normalizeComponent; 1239 | 1240 | /* script */ 1241 | const __vue_script__ = script; 1242 | 1243 | /* template */ 1244 | 1245 | /* style */ 1246 | const __vue_inject_styles__ = undefined; 1247 | /* scoped */ 1248 | const __vue_scope_id__ = undefined; 1249 | /* module identifier */ 1250 | const __vue_module_identifier__ = undefined; 1251 | /* functional template */ 1252 | const __vue_is_functional_template__ = undefined; 1253 | /* style inject */ 1254 | 1255 | /* style inject SSR */ 1256 | 1257 | 1258 | 1259 | var Overdrive = normalizeComponent_1( 1260 | {}, 1261 | __vue_inject_styles__, 1262 | __vue_script__, 1263 | __vue_scope_id__, 1264 | __vue_is_functional_template__, 1265 | __vue_module_identifier__, 1266 | undefined, 1267 | undefined 1268 | ); 1269 | 1270 | function install(Vue) { 1271 | if (install.installed) return; 1272 | install.installed = true; 1273 | Vue.component("overdrive", Overdrive); 1274 | } 1275 | 1276 | const VOverdrive = Overdrive; 1277 | 1278 | const plugin = { 1279 | install, 1280 | 1281 | get enabled() { 1282 | return state.enabled; 1283 | }, 1284 | 1285 | set enabled(value) { 1286 | state.enabled = value; 1287 | } 1288 | }; 1289 | 1290 | // Auto-install 1291 | let GlobalVue = null; 1292 | if (typeof window !== "undefined") { 1293 | GlobalVue = window.Vue; 1294 | } else if (typeof global !== "undefined") { 1295 | GlobalVue = global.Vue; 1296 | } 1297 | if (GlobalVue) { 1298 | GlobalVue.use(plugin); 1299 | } 1300 | 1301 | export default plugin; 1302 | export { install, VOverdrive }; 1303 | -------------------------------------------------------------------------------- /dist/overdrive.min.js: -------------------------------------------------------------------------------- 1 | var VOverdrive = (function (exports) { 2 | 'use strict'; 3 | 4 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 5 | 6 | function createCommonjsModule(fn, module) { 7 | return module = { exports: {} }, fn(module, module.exports), module.exports; 8 | } 9 | 10 | var ramjet_umd = createCommonjsModule(function (module, exports) { 11 | (function (global, factory) { 12 | factory(exports); 13 | }(commonjsGlobal, function (exports) { 14 | var babelHelpers = {}; 15 | 16 | babelHelpers.classCallCheck = function (instance, Constructor) { 17 | if (!(instance instanceof Constructor)) { 18 | throw new TypeError("Cannot call a class as a function"); 19 | } 20 | }; 21 | 22 | var props = /\b(?:position|zIndex|opacity|transform|webkitTransform|mixBlendMode|filter|webkitFilter|isolation)\b/; 23 | 24 | function isFlexItem(node) { 25 | var display = getComputedStyle(node.parentNode).display; 26 | return display === 'flex' || display === 'inline-flex'; 27 | } 28 | 29 | function createsStackingContext(node) { 30 | var style = getComputedStyle(node); 31 | 32 | // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context 33 | if (style.position === 'fixed') return true; 34 | if (style.zIndex !== 'auto' && style.position !== 'static' || isFlexItem(node)) return true; 35 | if (+style.opacity < 1) return true; 36 | if ('transform' in style && style.transform !== 'none') return true; 37 | if ('webkitTransform' in style && style.webkitTransform !== 'none') return true; 38 | if ('mixBlendMode' in style && style.mixBlendMode !== 'normal') return true; 39 | if ('filter' in style && style.filter !== 'none') return true; 40 | if ('webkitFilter' in style && style.webkitFilter !== 'none') return true; 41 | if ('isolation' in style && style.isolation === 'isolate') return true; 42 | if (props.test(style.willChange)) return true; 43 | if (style.webkitOverflowScrolling === 'touch') return true; 44 | 45 | return false; 46 | } 47 | 48 | function findStackingContext(nodes) { 49 | var i = nodes.length; 50 | 51 | while (i--) { 52 | if (createsStackingContext(nodes[i])) return nodes[i]; 53 | } 54 | 55 | return null; 56 | } 57 | 58 | function getAncestors(node) { 59 | var ancestors = []; 60 | 61 | while (node) { 62 | ancestors.push(node); 63 | node = node.parentNode; 64 | } 65 | 66 | return ancestors; // [ node, ... , , document ] 67 | } 68 | 69 | function getZIndex(node) { 70 | return node && Number(getComputedStyle(node).zIndex) || 0; 71 | } 72 | 73 | function last(array) { 74 | return array[array.length - 1]; 75 | } 76 | 77 | function compare(a, b) { 78 | if (a === b) throw new Error('Cannot compare node with itself'); 79 | 80 | var ancestors = { 81 | a: getAncestors(a), 82 | b: getAncestors(b) 83 | }; 84 | 85 | var commonAncestor = undefined; 86 | 87 | // remove shared ancestors 88 | while (last(ancestors.a) === last(ancestors.b)) { 89 | a = ancestors.a.pop(); 90 | b = ancestors.b.pop(); 91 | 92 | commonAncestor = a; 93 | } 94 | 95 | var stackingContexts = { 96 | a: findStackingContext(ancestors.a), 97 | b: findStackingContext(ancestors.b) 98 | }; 99 | 100 | var zIndexes = { 101 | a: getZIndex(stackingContexts.a), 102 | b: getZIndex(stackingContexts.b) 103 | }; 104 | 105 | if (zIndexes.a === zIndexes.b) { 106 | var children = commonAncestor.childNodes; 107 | 108 | var furthestAncestors = { 109 | a: last(ancestors.a), 110 | b: last(ancestors.b) 111 | }; 112 | 113 | var i = children.length; 114 | while (i--) { 115 | var child = children[i]; 116 | if (child === furthestAncestors.a) return 1; 117 | if (child === furthestAncestors.b) return -1; 118 | } 119 | } 120 | 121 | return Math.sign(zIndexes.a - zIndexes.b); 122 | } 123 | 124 | var svgns = 'http://www.w3.org/2000/svg'; 125 | 126 | function hideNode(node) { 127 | node.__ramjetOriginalTransition__ = node.style.webkitTransition || node.style.transition; 128 | node.__ramjetOriginalOpacity__ = node.style.opacity; 129 | 130 | node.style.webkitTransition = node.style.transition = ''; 131 | 132 | node.style.opacity = 0; 133 | } 134 | 135 | function showNode(node) { 136 | if ('__ramjetOriginalOpacity__' in node) { 137 | node.style.transition = ''; 138 | node.style.opacity = node.__ramjetOriginalOpacity__; 139 | 140 | if (node.__ramjetOriginalTransition__) { 141 | setTimeout(function () { 142 | node.style.transition = node.__ramjetOriginalTransition__; 143 | }); 144 | } 145 | } 146 | } 147 | 148 | function cloneNode(node) { 149 | var clone = node.cloneNode(); 150 | 151 | var isSvg = node.parentNode && node.parentNode.namespaceURI === svgns; 152 | 153 | if (node.nodeType === 1) { 154 | var width = node.style.width; 155 | var height = node.style.height; 156 | 157 | clone.setAttribute('style', window.getComputedStyle(node).cssText); 158 | 159 | if (isSvg) { 160 | clone.style.width = width; 161 | clone.style.height = height; 162 | } 163 | 164 | var len = node.childNodes.length; 165 | var i = undefined; 166 | 167 | for (i = 0; i < len; i += 1) { 168 | clone.appendChild(cloneNode(node.childNodes[i])); 169 | } 170 | } 171 | 172 | return clone; 173 | } 174 | 175 | var bgColorRegexp = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d?.\d+))?\)$/; 176 | 177 | function parseColor(str) { 178 | var match = bgColorRegexp.exec(str); 179 | 180 | if (!match) return null; 181 | 182 | return { 183 | r: +match[1], 184 | g: +match[2], 185 | b: +match[3], 186 | alpha: match[4] ? +match[4] : 1 187 | }; 188 | } 189 | 190 | var borderRadiusRegex = /^(\d+)px(?: (\d+)px)?$/; 191 | 192 | function parseBorderRadius(str) { 193 | var match = borderRadiusRegex.exec(str); 194 | 195 | return match[2] ? { x: +match[1], y: +match[2] } : { x: +match[1], y: +match[1] }; 196 | } 197 | 198 | function findParentByTagName(node, tagName) { 199 | while (node) { 200 | if (node.tagName === tagName) { 201 | return node; 202 | } 203 | 204 | node = node.parentNode; 205 | } 206 | } 207 | 208 | function findTransformParent(node) { 209 | var isSvg = node.namespaceURI === svgns && node.tagName !== 'svg'; 210 | return isSvg ? findParentByTagName(node, 'svg') : node.parentNode; 211 | } 212 | 213 | var div = document.createElement('div'); 214 | 215 | var keyframesSupported = true; 216 | var TRANSFORM = undefined; 217 | var TRANSFORM_ORIGIN = undefined; 218 | var TRANSFORM_CSS = undefined; 219 | var KEYFRAMES = undefined; 220 | var ANIMATION = undefined; 221 | var ANIMATION_DIRECTION = undefined; 222 | var ANIMATION_DURATION = undefined; 223 | var ANIMATION_ITERATION_COUNT = undefined; 224 | var ANIMATION_NAME = undefined; 225 | var ANIMATION_TIMING_FUNCTION = undefined; 226 | var ANIMATION_END = undefined; 227 | 228 | // We have to browser-sniff for IE11, because it was apparently written 229 | // by a barrel of stoned monkeys - http://jsfiddle.net/rich_harris/oquLu2qL/ 230 | 231 | // http://stackoverflow.com/questions/17907445/how-to-detect-ie11 232 | var isIe11 = !window.ActiveXObject && 'ActiveXObject' in window; 233 | 234 | if (!isIe11 && ('transform' in div.style || 'webkitTransform' in div.style) && ('animation' in div.style || 'webkitAnimation' in div.style)) { 235 | keyframesSupported = true; 236 | 237 | if ('webkitTransform' in div.style) { 238 | TRANSFORM = 'webkitTransform'; 239 | TRANSFORM_CSS = '-webkit-transform'; 240 | TRANSFORM_ORIGIN = 'webkitTransformOrigin'; 241 | } else { 242 | TRANSFORM = TRANSFORM_CSS = 'transform'; 243 | TRANSFORM_ORIGIN = 'transformOrigin'; 244 | } 245 | 246 | if ('animation' in div.style) { 247 | KEYFRAMES = '@keyframes'; 248 | 249 | ANIMATION = 'animation'; 250 | ANIMATION_DIRECTION = 'animationDirection'; 251 | ANIMATION_DURATION = 'animationDuration'; 252 | ANIMATION_ITERATION_COUNT = 'animationIterationCount'; 253 | ANIMATION_NAME = 'animationName'; 254 | ANIMATION_TIMING_FUNCTION = 'animationTimingFunction'; 255 | 256 | ANIMATION_END = 'animationend'; 257 | } else { 258 | KEYFRAMES = '@-webkit-keyframes'; 259 | 260 | ANIMATION = 'webkitAnimation'; 261 | ANIMATION_DIRECTION = 'webkitAnimationDirection'; 262 | ANIMATION_DURATION = 'webkitAnimationDuration'; 263 | ANIMATION_ITERATION_COUNT = 'webkitAnimationIterationCount'; 264 | ANIMATION_NAME = 'webkitAnimationName'; 265 | ANIMATION_TIMING_FUNCTION = 'webkitAnimationTimingFunction'; 266 | 267 | ANIMATION_END = 'webkitAnimationEnd'; 268 | } 269 | } else { 270 | keyframesSupported = false; 271 | } 272 | 273 | var IDENTITY = [1, 0, 0, 1, 0, 0]; 274 | 275 | function multiply(_ref, _ref2) { 276 | var a1 = _ref[0]; 277 | var b1 = _ref[1]; 278 | var c1 = _ref[2]; 279 | var d1 = _ref[3]; 280 | var e1 = _ref[4]; 281 | var f1 = _ref[5]; 282 | var a2 = _ref2[0]; 283 | var b2 = _ref2[1]; 284 | var c2 = _ref2[2]; 285 | var d2 = _ref2[3]; 286 | var e2 = _ref2[4]; 287 | var f2 = _ref2[5]; 288 | 289 | return [a1 * a2 + c1 * b2, // a 290 | b1 * a2 + d1 * b2, // b 291 | a1 * c2 + c1 * d2, // c 292 | b1 * c2 + d1 * d2, // d 293 | a1 * e2 + c1 * f2 + e1, // e 294 | b1 * e2 + d1 * f2 + f1 // f 295 | ]; 296 | } 297 | 298 | function invert(_ref3) { 299 | var a = _ref3[0]; 300 | var b = _ref3[1]; 301 | var c = _ref3[2]; 302 | var d = _ref3[3]; 303 | var e = _ref3[4]; 304 | var f = _ref3[5]; 305 | 306 | var determinant = a * d - c * b; 307 | 308 | return [d / determinant, b / -determinant, c / -determinant, a / determinant, (c * f - e * d) / determinant, (e * b - a * f) / determinant]; 309 | } 310 | 311 | function pythag(a, b) { 312 | return Math.sqrt(a * a + b * b); 313 | } 314 | 315 | function decompose(_ref4) { 316 | var a = _ref4[0]; 317 | var b = _ref4[1]; 318 | var c = _ref4[2]; 319 | var d = _ref4[3]; 320 | var e = _ref4[4]; 321 | var f = _ref4[5]; 322 | 323 | // If determinant equals zero (e.g. x scale or y scale equals zero), 324 | // the matrix cannot be decomposed 325 | if (a * d - b * c === 0) return null; 326 | 327 | // See https://github.com/Rich-Harris/Neo/blob/master/Neo.js for 328 | // an explanation of the following 329 | var scaleX = pythag(a, b); 330 | a /= scaleX; 331 | b /= scaleX; 332 | 333 | var scaledShear = a * c + b * d; 334 | var desheared = [a * -scaledShear + c, b * -scaledShear + d]; 335 | 336 | var scaleY = pythag(desheared[0], desheared[1]); 337 | 338 | var skewX = scaledShear / scaleY; 339 | 340 | var rotate = b > 0 ? Math.acos(a) : 2 * Math.PI - Math.acos(a); 341 | 342 | return { 343 | rotate: rotate, 344 | scaleX: scaleX, 345 | scaleY: scaleY, 346 | skewX: skewX, 347 | translateX: e, 348 | translateY: f 349 | }; 350 | } 351 | 352 | function parseMatrixTransformString(transform) { 353 | if (transform.slice(0, 7) !== 'matrix(') { 354 | throw new Error('Could not parse transform string (' + transform + ')'); 355 | } 356 | 357 | return transform.slice(7, -1).split(' ').map(parseFloat); 358 | } 359 | 360 | function getCumulativeTransformMatrix(node) { 361 | if (node.namespaceURI === svgns) { 362 | var _node$getCTM = node.getCTM(); 363 | 364 | var a = _node$getCTM.a; 365 | var b = _node$getCTM.b; 366 | var c = _node$getCTM.c; 367 | var d = _node$getCTM.d; 368 | var e = _node$getCTM.e; 369 | var f = _node$getCTM.f; 370 | 371 | return [a, b, c, d, e, f]; 372 | } 373 | 374 | var matrix = [1, 0, 0, 1, 0, 0]; 375 | 376 | while (node instanceof Element) { 377 | var parentMatrix = getTransformMatrix(node); 378 | 379 | if (parentMatrix) { 380 | matrix = multiply(parentMatrix, matrix); 381 | } 382 | 383 | node = findTransformParent(node); 384 | } 385 | 386 | return matrix; 387 | } 388 | 389 | function getTransformMatrix(node) { 390 | if (node.namespaceURI === svgns) { 391 | var ctm = getCumulativeTransformMatrix(node); 392 | var parentCTM = getCumulativeTransformMatrix(node.parentNode); 393 | return multiply(invert(parentCTM), ctm); 394 | } 395 | 396 | var style = getComputedStyle(node); 397 | var transform = style[TRANSFORM]; 398 | 399 | if (transform === 'none') { 400 | return null; 401 | } 402 | 403 | var origin = style[TRANSFORM_ORIGIN].split(' ').map(parseFloat); 404 | 405 | var matrix = parseMatrixTransformString(transform); 406 | 407 | // compensate for the transform origin (we want to express everything in [0,0] terms) 408 | matrix = multiply([1, 0, 0, 1, origin[0], origin[1]], matrix); 409 | matrix = multiply(matrix, [1, 0, 0, 1, -origin[0], -origin[1]]); 410 | 411 | // TODO if is SVG, multiply by CTM, to account for viewBox 412 | 413 | return matrix; 414 | } 415 | 416 | function getBoundingClientRect(node, invertedParentCTM) { 417 | var originalTransformOrigin = node.style[TRANSFORM_ORIGIN]; 418 | var originalTransform = node.style[TRANSFORM]; 419 | var originalTransformAttribute = node.getAttribute('transform'); // SVG 420 | 421 | node.style[TRANSFORM_ORIGIN] = '0 0'; 422 | node.style[TRANSFORM] = 'matrix(' + invertedParentCTM.join(',') + ')'; 423 | 424 | var bcr = node.getBoundingClientRect(); 425 | 426 | // reset 427 | node.style[TRANSFORM_ORIGIN] = originalTransformOrigin; 428 | node.style[TRANSFORM] = originalTransform; 429 | node.setAttribute('transform', originalTransformAttribute || ''); // TODO remove attribute altogether if null? 430 | 431 | return bcr; 432 | } 433 | 434 | var Wrapper = function () { 435 | function Wrapper(node, options) { 436 | babelHelpers.classCallCheck(this, Wrapper); 437 | 438 | this.init(node, options); 439 | } 440 | 441 | Wrapper.prototype.init = function init(node) { 442 | this._node = node; 443 | this._clone = cloneNode(node); 444 | 445 | var style = window.getComputedStyle(node); 446 | this.style = style; 447 | 448 | // we need to get the 'naked' boundingClientRect, i.e. 449 | // without any transforms 450 | // TODO what if the node is the root node? 451 | var parentCTM = node.namespaceURI === 'svg' ? node.parentNode.getScreenCTM() : getCumulativeTransformMatrix(node.parentNode); 452 | this.invertedParentCTM = invert(parentCTM); 453 | this.transform = getTransformMatrix(node) || IDENTITY; 454 | this.ctm = multiply(parentCTM, this.transform); 455 | 456 | var bcr = getBoundingClientRect(node, this.invertedParentCTM); 457 | this.bcr = bcr; 458 | 459 | // TODO create a flat array? easier to work with later? 460 | var borderRadius = { 461 | tl: parseBorderRadius(style.borderTopLeftRadius), 462 | tr: parseBorderRadius(style.borderTopRightRadius), 463 | br: parseBorderRadius(style.borderBottomRightRadius), 464 | bl: parseBorderRadius(style.borderBottomLeftRadius) 465 | }; 466 | 467 | this.borderRadius = borderRadius; 468 | this.opacity = +style.opacity; 469 | this.rgba = parseColor(style.backgroundColor); 470 | 471 | this.left = bcr.left; 472 | this.top = bcr.top; 473 | this.width = bcr.width; 474 | this.height = bcr.height; 475 | }; 476 | 477 | Wrapper.prototype.insert = function insert() { 478 | var bcr = this.bcr; 479 | 480 | var offsetParent = this._node.offsetParent; 481 | 482 | var clone = undefined; 483 | 484 | if (this._node.namespaceURI === svgns) { 485 | // TODO what if it's the itself, not a child? 486 | var svg = findParentByTagName(this._node, 'svg'); // TODO should be the namespace boundary - could be SVG inside SVG 487 | 488 | clone = svg.cloneNode(false); 489 | clone.appendChild(this._clone); // TODO what about transforms? 490 | } else { 491 | clone = this._clone; 492 | } 493 | 494 | var offsetParentStyle = window.getComputedStyle(offsetParent); 495 | var offsetParentBcr = getBoundingClientRect(offsetParent, invert(getCumulativeTransformMatrix(offsetParent.parentNode))); 496 | 497 | clone.style.position = 'absolute'; 498 | clone.style[TRANSFORM_ORIGIN] = '0 0'; 499 | clone.style.top = bcr.top - parseInt(this.style.marginTop, 10) - (offsetParentBcr.top - parseInt(offsetParentStyle.marginTop, 10)) + 'px'; 500 | clone.style.left = bcr.left - parseInt(this.style.marginLeft, 10) - (offsetParentBcr.left - parseInt(offsetParentStyle.marginLeft, 10)) + 'px'; 501 | 502 | // TODO we need to account for transforms *between* the offset parent and the node 503 | 504 | offsetParent.appendChild(clone); 505 | }; 506 | 507 | Wrapper.prototype.detach = function detach() { 508 | this._clone.parentNode.removeChild(this._clone); 509 | }; 510 | 511 | Wrapper.prototype.setOpacity = function setOpacity(opacity) { 512 | this._clone.style.opacity = opacity; 513 | }; 514 | 515 | Wrapper.prototype.setTransform = function setTransform(transform) { 516 | this._clone.style.transform = this._clone.style.webkitTransform = this._clone.style.msTransform = transform; 517 | }; 518 | 519 | Wrapper.prototype.setBackgroundColor = function setBackgroundColor(color) { 520 | this._clone.style.backgroundColor = color; 521 | }; 522 | 523 | Wrapper.prototype.setBorderRadius = function setBorderRadius(borderRadius) { 524 | this._clone.style.borderRadius = borderRadius; 525 | }; 526 | 527 | Wrapper.prototype.animateWithKeyframes = function animateWithKeyframes(id, duration) { 528 | this._clone.style[ANIMATION_DIRECTION] = 'alternate'; 529 | this._clone.style[ANIMATION_DURATION] = duration / 1000 + 's'; 530 | this._clone.style[ANIMATION_ITERATION_COUNT] = 1; 531 | this._clone.style[ANIMATION_NAME] = id; 532 | this._clone.style[ANIMATION_TIMING_FUNCTION] = 'linear'; 533 | }; 534 | 535 | Wrapper.prototype.freeze = function freeze() { 536 | var computedStyle = getComputedStyle(this._clone); 537 | 538 | this.setOpacity(computedStyle.opacity); 539 | this.setTransform(computedStyle.transform); 540 | this.setBackgroundColor(computedStyle.backgroundColor); 541 | this.setBorderRadius(computedStyle.borderRadius); 542 | 543 | this._clone.style[ANIMATION] = 'none'; 544 | }; 545 | 546 | return Wrapper; 547 | }(); 548 | 549 | function getOpacityInterpolator(from, to, order) { 550 | var opacity = {}; 551 | 552 | return function (t) { 553 | var targetOpacity = (to - from) * t + from; 554 | 555 | // Based on the blending formula here. (http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending) 556 | // This is a quadratic blending function that makes the top layer and bottom layer blend linearly. 557 | // However there is an asymptote at target=1 so that needs to be handled with an if else statement. 558 | if (targetOpacity === 1) { 559 | if (order === 1) { 560 | opacity.from = 1 - t; 561 | opacity.to = 1; 562 | } else { 563 | opacity.from = 1; 564 | opacity.to = t; 565 | } 566 | } else { 567 | opacity.from = targetOpacity - t * t * targetOpacity; 568 | opacity.to = (targetOpacity - opacity.from) / (1 - opacity.from); 569 | } 570 | 571 | return opacity; 572 | }; 573 | } 574 | 575 | function getRgbaInterpolator(a, b, order) { 576 | if (a.alpha === 1 && b.alpha === 1) { 577 | // no need to animate anything 578 | return null; 579 | } 580 | 581 | var rgba = {}; 582 | var opacityAt = getOpacityInterpolator(a.alpha, b.alpha, order); 583 | 584 | return function (t) { 585 | var opacity = opacityAt(t); 586 | 587 | rgba.from = 'rgba(' + a.r + ',' + a.g + ',' + a.b + ',' + opacity.from + ')'; 588 | rgba.to = 'rgba(' + b.r + ',' + b.g + ',' + b.b + ',' + opacity.to + ')'; 589 | 590 | return rgba; 591 | }; 592 | } 593 | 594 | function interpolateArray(a, b) { 595 | var len = a.length; 596 | var array = new Array(len); 597 | 598 | return function (t) { 599 | var i = len; 600 | while (i--) { 601 | array[i] = a[i] + t * (b[i] - a[i]); 602 | } 603 | 604 | return array; 605 | }; 606 | } 607 | 608 | // Border radius is given as a string in the following form 609 | // 610 | // tl.x tr.x br.x bl.x / tl.y tr.y br.y bl.y 611 | // 612 | // ...where t, r, b and l are top, right, bottom, left, and 613 | // x and y are self-explanatory. Each value is followed by 'px' 614 | 615 | // TODO it must be possible to do this more simply. Maybe have 616 | // a flat array from the start? 617 | 618 | function getBorderRadiusInterpolator(a, b) { 619 | // TODO fast path - no transition needed 620 | 621 | var aWidth = a.width; 622 | var aHeight = a.height; 623 | 624 | var bWidth = b.width; 625 | var bHeight = b.height; 626 | 627 | a = a.borderRadius; 628 | b = b.borderRadius; 629 | 630 | var a_x_t0 = [a.tl.x, a.tr.x, a.br.x, a.bl.x]; 631 | var a_y_t0 = [a.tl.y, a.tr.y, a.br.y, a.bl.y]; 632 | 633 | var b_x_t1 = [b.tl.x, b.tr.x, b.br.x, b.bl.x]; 634 | var b_y_t1 = [b.tl.y, b.tr.y, b.br.y, b.bl.y]; 635 | 636 | var a_x_t1 = b_x_t1.map(function (x) { 637 | return x * aWidth / bWidth; 638 | }); 639 | var a_y_t1 = b_y_t1.map(function (y) { 640 | return y * aHeight / bHeight; 641 | }); 642 | 643 | var b_x_t0 = a_x_t0.map(function (x) { 644 | return x * bWidth / aWidth; 645 | }); 646 | var b_y_t0 = a_y_t0.map(function (y) { 647 | return y * bHeight / aHeight; 648 | }); 649 | 650 | var ax = interpolateArray(a_x_t0, a_x_t1); 651 | var ay = interpolateArray(a_y_t0, a_y_t1); 652 | 653 | var bx = interpolateArray(b_x_t0, b_x_t1); 654 | var by = interpolateArray(b_y_t0, b_y_t1); 655 | 656 | var borderRadius = {}; 657 | 658 | return function (t) { 659 | var x = ax(t); 660 | var y = ay(t); 661 | 662 | borderRadius.from = x.join('px ') + 'px / ' + y.join('px ') + 'px'; 663 | 664 | x = bx(t); 665 | y = by(t); 666 | 667 | borderRadius.to = x.join('px ') + 'px / ' + y.join('px ') + 'px'; 668 | 669 | return borderRadius; 670 | }; 671 | } 672 | 673 | function interpolateMatrices(a, b) { 674 | var transform = []; 675 | 676 | return function (t) { 677 | var i = a.length; 678 | while (i--) { 679 | var from = a[i]; 680 | var to = b[i]; 681 | transform[i] = from + t * (to - from); 682 | } 683 | 684 | return 'matrix(' + transform.join(',') + ')'; 685 | }; 686 | } 687 | 688 | function interpolate(a, b) { 689 | var d = b - a; 690 | return function (t) { 691 | return a + t * d; 692 | }; 693 | } 694 | 695 | function getRotation(radians) { 696 | while (radians > Math.PI) { 697 | radians -= Math.PI * 2; 698 | }while (radians < -Math.PI) { 699 | radians += Math.PI * 2; 700 | }return radians; 701 | } 702 | 703 | function interpolateDecomposedTransforms(a, b) { 704 | var rotate = interpolate(getRotation(a.rotate), getRotation(b.rotate)); 705 | var skewX = interpolate(a.skewX, b.skewX); 706 | var scaleX = interpolate(a.scaleX, b.scaleX); 707 | var scaleY = interpolate(a.scaleY, b.scaleY); 708 | var translateX = interpolate(a.translateX, b.translateX); 709 | var translateY = interpolate(a.translateY, b.translateY); 710 | 711 | return function (t) { 712 | var transform = 'translate(' + translateX(t) + 'px, ' + translateY(t) + 'px) rotate(' + rotate(t) + 'rad) skewX(' + skewX(t) + 'rad) scale(' + scaleX(t) + ', ' + scaleY(t) + ')'; 713 | return transform; 714 | }; 715 | } 716 | 717 | function getTransformInterpolator(a, b) { 718 | var scale_x = b.width / a.width; 719 | var scale_y = b.height / a.height; 720 | var d_x = b.left - a.left; 721 | var d_y = b.top - a.top; 722 | 723 | var a_start = a.transform; 724 | 725 | var move_a_to_b = [1, 0, 0, 1, d_x, d_y]; 726 | var scale_a_to_b = [scale_x, 0, 0, scale_y, 0, 0]; 727 | 728 | var matrix = IDENTITY; 729 | 730 | matrix = multiply(matrix, a.invertedParentCTM); 731 | matrix = multiply(matrix, move_a_to_b); 732 | matrix = multiply(matrix, b.ctm); 733 | matrix = multiply(matrix, scale_a_to_b); 734 | 735 | var decomposed_start = decompose(a_start); 736 | var decomposed_end = decompose(matrix); 737 | 738 | if (!decomposed_start || !decomposed_end) return interpolateMatrices(a_start, matrix); 739 | return interpolateDecomposedTransforms(decomposed_start, decomposed_end); 740 | } 741 | 742 | function linear(pos) { 743 | return pos; 744 | } 745 | 746 | function easeIn(pos) { 747 | return Math.pow(pos, 3); 748 | } 749 | 750 | function easeOut(pos) { 751 | return Math.pow(pos - 1, 3) + 1; 752 | } 753 | 754 | function easeInOut(pos) { 755 | if ((pos /= 0.5) < 1) { 756 | return 0.5 * Math.pow(pos, 3); 757 | } 758 | 759 | return 0.5 * (Math.pow(pos - 2, 3) + 2); 760 | } 761 | 762 | var head = document.getElementsByTagName('head')[0]; 763 | 764 | function addCss(css) { 765 | var styleElement = document.createElement('style'); 766 | styleElement.type = 'text/css'; 767 | 768 | // Internet Exploder won't let you use styleSheet.innerHTML - we have to 769 | // use styleSheet.cssText instead 770 | var styleSheet = styleElement.styleSheet; 771 | 772 | if (styleSheet) { 773 | styleSheet.cssText = css; 774 | } else { 775 | styleElement.innerHTML = css; 776 | } 777 | 778 | head.appendChild(styleElement); 779 | 780 | return function () { 781 | return head.removeChild(styleElement); 782 | }; 783 | } 784 | 785 | function getKeyframes(from, to, interpolators, easing, remaining, duration) { 786 | var numFrames = remaining / 16; 787 | 788 | var fromKeyframes = ''; 789 | var toKeyframes = ''; 790 | 791 | function addKeyframes(pc, t) { 792 | var opacity = interpolators.opacity(t); 793 | var backgroundColor = interpolators.backgroundColor ? interpolators.backgroundColor(t) : null; 794 | var borderRadius = interpolators.borderRadius ? interpolators.borderRadius(t) : null; 795 | var transformFrom = interpolators.transformFrom(t); 796 | var transformTo = interpolators.transformTo(1 - t); 797 | 798 | fromKeyframes += '\n' + (pc + '% {') + ('opacity: ' + opacity.from + ';') + (TRANSFORM_CSS + ': ' + transformFrom + ';') + (backgroundColor ? 'background-color: ' + backgroundColor.from + ';' : '') + (borderRadius ? 'border-radius: ' + borderRadius.from + ';' : '') + '}'; 799 | 800 | toKeyframes += '\n' + (pc + '% {') + ('opacity: ' + opacity.to + ';') + (TRANSFORM_CSS + ': ' + transformTo + ';') + (backgroundColor ? 'background-color: ' + backgroundColor.to + ';' : '') + (borderRadius ? 'border-radius: ' + borderRadius.to + ';' : '') + '}'; 801 | } 802 | 803 | var i = undefined; 804 | var startPos = 1 - remaining / duration; 805 | 806 | for (i = 0; i < numFrames; i += 1) { 807 | var relPos = i / numFrames; 808 | var absPos = startPos + remaining / duration * relPos; 809 | 810 | var pc = 100 * relPos; 811 | var t = easing(absPos); 812 | 813 | addKeyframes(pc, t); 814 | } 815 | 816 | addKeyframes(100, 1); 817 | 818 | return { fromKeyframes: fromKeyframes, toKeyframes: toKeyframes }; 819 | } 820 | 821 | function generateId() { 822 | return 'ramjet' + ~ ~(Math.random() * 1000000); 823 | } 824 | 825 | var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (fn) { 826 | return setTimeout(fn, 16); 827 | }; 828 | 829 | function transformer(from, to, options) { 830 | var duration = options.duration || 400; 831 | var easing = options.easing || linear; 832 | 833 | var useTimer = !keyframesSupported || !!options.useTimer; 834 | 835 | var order = compare(from._node, to._node); 836 | 837 | var interpolators = { 838 | opacity: getOpacityInterpolator(from.opacity, to.opacity, order), 839 | backgroundColor: options.interpolateBackgroundColor ? getRgbaInterpolator(from.rgba, to.rgba, order) : null, 840 | borderRadius: options.interpolateBorderRadius ? getBorderRadiusInterpolator(from, to) : null, 841 | transformFrom: getTransformInterpolator(from, to), 842 | transformTo: getTransformInterpolator(to, from) 843 | }; 844 | 845 | var running = undefined; 846 | var disposeCss = undefined; 847 | var torndown = undefined; 848 | 849 | var remaining = duration; 850 | var endTime = undefined; 851 | 852 | function tick() { 853 | if (!running) return; 854 | 855 | var timeNow = Date.now(); 856 | remaining = endTime - timeNow; 857 | 858 | if (remaining < 0) { 859 | transformer.teardown(); 860 | if (options.done) options.done(); 861 | 862 | return; 863 | } 864 | 865 | var t = easing(1 - remaining / duration); 866 | transformer.goto(t); 867 | 868 | rAF(tick); 869 | } 870 | 871 | var transformer = { 872 | teardown: function () { 873 | if (torndown) return transformer; 874 | 875 | running = false; 876 | torndown = true; 877 | 878 | from.detach(); 879 | to.detach(); 880 | 881 | from = null; 882 | to = null; 883 | 884 | return transformer; 885 | }, 886 | goto: function (pos) { 887 | transformer.pause(); 888 | 889 | var t = easing(pos); 890 | 891 | // opacity 892 | var opacity = interpolators.opacity(t); 893 | from.setOpacity(opacity.from); 894 | to.setOpacity(opacity.to); 895 | 896 | // transform 897 | var transformFrom = interpolators.transformFrom(t); 898 | var transformTo = interpolators.transformTo(1 - t); 899 | from.setTransform(transformFrom); 900 | to.setTransform(transformTo); 901 | 902 | // background color 903 | if (interpolators.backgroundColor) { 904 | var backgroundColor = interpolators.backgroundColor(t); 905 | from.setBackgroundColor(backgroundColor.from); 906 | to.setBackgroundColor(backgroundColor.to); 907 | } 908 | 909 | // border radius 910 | if (interpolators.borderRadius) { 911 | var borderRadius = interpolators.borderRadius(t); 912 | from.setBorderRadius(borderRadius.from); 913 | to.setBorderRadius(borderRadius.to); 914 | } 915 | 916 | return transformer; 917 | }, 918 | pause: function () { 919 | if (!running) return transformer; 920 | running = false; 921 | 922 | if (!useTimer) { 923 | // TODO derive current position somehow, use that rather than 924 | // current computed style (from and to get out of sync in 925 | // some browsers?) 926 | remaining = endTime - Date.now(); 927 | 928 | from.freeze(); 929 | to.freeze(); 930 | disposeCss(); 931 | } 932 | 933 | return transformer; 934 | }, 935 | play: function () { 936 | if (running) return transformer; 937 | running = true; 938 | 939 | endTime = Date.now() + remaining; 940 | 941 | if (useTimer) { 942 | rAF(tick); 943 | } else { 944 | var _getKeyframes = getKeyframes(from, to, interpolators, options.easing || linear, remaining, duration); 945 | 946 | var fromKeyframes = _getKeyframes.fromKeyframes; 947 | var toKeyframes = _getKeyframes.toKeyframes; 948 | 949 | var fromId = generateId(); 950 | var toId = generateId(); 951 | 952 | var css = '\n\t\t\t\t\t' + KEYFRAMES + ' ' + fromId + ' { ' + fromKeyframes + ' }\n\t\t\t\t\t' + KEYFRAMES + ' ' + toId + ' { ' + toKeyframes + ' }'; 953 | 954 | disposeCss = addCss(css); 955 | 956 | from.animateWithKeyframes(fromId, remaining); 957 | to.animateWithKeyframes(toId, remaining); 958 | } 959 | 960 | return transformer; 961 | } 962 | }; 963 | 964 | // handle animation end 965 | if (!useTimer) { 966 | (function () { 967 | var animating = 2; 968 | 969 | var done = function () { 970 | if (! --animating) { 971 | transformer.teardown(); 972 | 973 | if (options.done) options.done(); 974 | 975 | disposeCss(); 976 | } 977 | }; 978 | 979 | from._clone.addEventListener(ANIMATION_END, done); 980 | to._clone.addEventListener(ANIMATION_END, done); 981 | })(); 982 | } 983 | 984 | return transformer.play(); 985 | } 986 | 987 | function transform(fromNode, toNode) { 988 | var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 989 | 990 | if (typeof options === 'function') { 991 | options = { done: options }; 992 | } 993 | 994 | if (!('duration' in options)) { 995 | options.duration = 400; 996 | } 997 | 998 | var from = new Wrapper(fromNode, options); 999 | var to = new Wrapper(toNode, options); 1000 | 1001 | var order = compare(from._node, to._node); 1002 | 1003 | from.setOpacity(1); 1004 | to.setOpacity(0); 1005 | 1006 | // in many cases, the stacking order of `from` and `to` is 1007 | // determined by their relative location in the document – 1008 | // so we need to preserve it 1009 | if (order === 1) { 1010 | to.insert(); 1011 | from.insert(); 1012 | } else { 1013 | from.insert(); 1014 | to.insert(); 1015 | } 1016 | 1017 | return transformer(from, to, options); 1018 | } 1019 | 1020 | function hide() { 1021 | for (var _len = arguments.length, nodes = Array(_len), _key = 0; _key < _len; _key++) { 1022 | nodes[_key] = arguments[_key]; 1023 | } 1024 | 1025 | nodes.forEach(hideNode); 1026 | } 1027 | 1028 | function show() { 1029 | for (var _len2 = arguments.length, nodes = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 1030 | nodes[_key2] = arguments[_key2]; 1031 | } 1032 | 1033 | nodes.forEach(showNode); 1034 | } 1035 | 1036 | exports.transform = transform; 1037 | exports.hide = hide; 1038 | exports.show = show; 1039 | exports.linear = linear; 1040 | exports.easeIn = easeIn; 1041 | exports.easeOut = easeOut; 1042 | exports.easeInOut = easeInOut; 1043 | 1044 | })); 1045 | 1046 | }); 1047 | 1048 | const components = {}; 1049 | const getPosition = (node, addOffset = false) => { 1050 | const rect = node.getBoundingClientRect(); 1051 | const computedStyle = window.getComputedStyle(node); 1052 | const marginTop = parseInt(computedStyle.marginTop, 10); 1053 | const marginLeft = parseInt(computedStyle.marginLeft, 10); 1054 | 1055 | return { 1056 | top: `${rect.top - 1057 | marginTop + 1058 | (addOffset ? 1 : 0) * 1059 | (window.pageYOffset || document.documentElement.scrollTop)}px`, 1060 | left: `${rect.left - marginLeft}px`, 1061 | width: `${rect.width}px`, 1062 | height: `${rect.height}px`, 1063 | borderRadius: computedStyle.borderRadius, 1064 | position: "absolute" 1065 | }; 1066 | }; 1067 | var script = { 1068 | props: { 1069 | tag: { 1070 | type: String, 1071 | default: "div" 1072 | }, 1073 | id: { 1074 | type: String, 1075 | required: true 1076 | }, 1077 | duration: { 1078 | type: Number, 1079 | duration: 400 1080 | }, 1081 | easing: { 1082 | type: Function, 1083 | default: ramjet_umd.linear 1084 | } 1085 | }, 1086 | data() { 1087 | return { 1088 | animating: false, 1089 | transformer: {} 1090 | }; 1091 | }, 1092 | methods: { 1093 | cache() { 1094 | components[this.id] = { 1095 | el: this.$slots.default, 1096 | pos: getPosition(this.$el.firstChild) 1097 | }; 1098 | }, 1099 | cloneAndAppend() { 1100 | const { el, pos } = components[this.id]; 1101 | const clone = el[0].elm.cloneNode(true); 1102 | clone.setAttribute("data-clone", this.id); 1103 | Object.assign(clone.style, pos); 1104 | document.body.appendChild(clone); 1105 | }, 1106 | bustCache() { 1107 | Object.keys(components).forEach(id => { 1108 | components[id] = false; 1109 | }); 1110 | }, 1111 | animate(cb = () => {}) { 1112 | const a = document.querySelector(`[data-clone="${this.id}"]`); 1113 | const b = this.$el.firstChild; 1114 | this.animating = true; 1115 | this.transformer = ramjet_umd.transform(a, b, { 1116 | duration: this.duration, 1117 | easing: this.easing, 1118 | appendToBody: true, 1119 | done: () => { 1120 | cb(a, b); 1121 | this.animating = false; 1122 | this.$emit("animation-end"); 1123 | } 1124 | }); 1125 | ramjet_umd.hide(a, b); 1126 | }, 1127 | handleMatch() { 1128 | this.cloneAndAppend(); 1129 | const cb = (a, b) => { 1130 | ramjet_umd.show(b); 1131 | }; 1132 | this.$nextTick(() => { 1133 | this.animate(cb); 1134 | const clone = document.querySelector(`[data-clone="${this.id}"]`); 1135 | document.body.removeChild(clone); 1136 | this.cache(); 1137 | }); 1138 | } 1139 | }, 1140 | mounted() { 1141 | const match = components[this.id]; 1142 | if (match) { 1143 | this.handleMatch(); 1144 | } else { 1145 | this.cache(); 1146 | } 1147 | }, 1148 | beforeDestroy() { 1149 | if (this.animating) { 1150 | this.transformer.teardown(); 1151 | } 1152 | }, 1153 | render(h) { 1154 | return h(this.tag, [this.$slots.default]); 1155 | } 1156 | }; 1157 | 1158 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier 1159 | /* server only */ 1160 | , shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 1161 | if (typeof shadowMode !== 'boolean') { 1162 | createInjectorSSR = createInjector; 1163 | createInjector = shadowMode; 1164 | shadowMode = false; 1165 | } // Vue.extend constructor export interop. 1166 | 1167 | 1168 | var options = typeof script === 'function' ? script.options : script; // render functions 1169 | 1170 | if (template && template.render) { 1171 | options.render = template.render; 1172 | options.staticRenderFns = template.staticRenderFns; 1173 | options._compiled = true; // functional template 1174 | 1175 | if (isFunctionalTemplate) { 1176 | options.functional = true; 1177 | } 1178 | } // scopedId 1179 | 1180 | 1181 | if (scopeId) { 1182 | options._scopeId = scopeId; 1183 | } 1184 | 1185 | var hook; 1186 | 1187 | if (moduleIdentifier) { 1188 | // server build 1189 | hook = function hook(context) { 1190 | // 2.3 injection 1191 | context = context || // cached call 1192 | this.$vnode && this.$vnode.ssrContext || // stateful 1193 | this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional 1194 | // 2.2 with runInNewContext: true 1195 | 1196 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 1197 | context = __VUE_SSR_CONTEXT__; 1198 | } // inject component styles 1199 | 1200 | 1201 | if (style) { 1202 | style.call(this, createInjectorSSR(context)); 1203 | } // register component module identifier for async chunk inference 1204 | 1205 | 1206 | if (context && context._registeredComponents) { 1207 | context._registeredComponents.add(moduleIdentifier); 1208 | } 1209 | }; // used by ssr in case component is cached and beforeCreate 1210 | // never gets called 1211 | 1212 | 1213 | options._ssrRegister = hook; 1214 | } else if (style) { 1215 | hook = shadowMode ? function () { 1216 | style.call(this, createInjectorShadow(this.$root.$options.shadowRoot)); 1217 | } : function (context) { 1218 | style.call(this, createInjector(context)); 1219 | }; 1220 | } 1221 | 1222 | if (hook) { 1223 | if (options.functional) { 1224 | // register for functional component in vue file 1225 | var originalRender = options.render; 1226 | 1227 | options.render = function renderWithStyleInjection(h, context) { 1228 | hook.call(context); 1229 | return originalRender(h, context); 1230 | }; 1231 | } else { 1232 | // inject component registration as beforeCreate hook 1233 | var existing = options.beforeCreate; 1234 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 1235 | } 1236 | } 1237 | 1238 | return script; 1239 | } 1240 | 1241 | var normalizeComponent_1 = normalizeComponent; 1242 | 1243 | /* script */ 1244 | const __vue_script__ = script; 1245 | 1246 | /* template */ 1247 | 1248 | /* style */ 1249 | const __vue_inject_styles__ = undefined; 1250 | /* scoped */ 1251 | const __vue_scope_id__ = undefined; 1252 | /* module identifier */ 1253 | const __vue_module_identifier__ = undefined; 1254 | /* functional template */ 1255 | const __vue_is_functional_template__ = undefined; 1256 | /* style inject */ 1257 | 1258 | /* style inject SSR */ 1259 | 1260 | 1261 | 1262 | var Overdrive = normalizeComponent_1( 1263 | {}, 1264 | __vue_inject_styles__, 1265 | __vue_script__, 1266 | __vue_scope_id__, 1267 | __vue_is_functional_template__, 1268 | __vue_module_identifier__, 1269 | undefined, 1270 | undefined 1271 | ); 1272 | 1273 | function install(Vue) { 1274 | if (install.installed) return; 1275 | install.installed = true; 1276 | Vue.component("overdrive", Overdrive); 1277 | } 1278 | 1279 | const VOverdrive = Overdrive; 1280 | 1281 | const plugin = { 1282 | install, 1283 | 1284 | get enabled() { 1285 | return state.enabled; 1286 | }, 1287 | 1288 | set enabled(value) { 1289 | state.enabled = value; 1290 | } 1291 | }; 1292 | 1293 | // Auto-install 1294 | let GlobalVue = null; 1295 | if (typeof window !== "undefined") { 1296 | GlobalVue = window.Vue; 1297 | } else if (typeof global !== "undefined") { 1298 | GlobalVue = global.Vue; 1299 | } 1300 | if (GlobalVue) { 1301 | GlobalVue.use(plugin); 1302 | } 1303 | 1304 | exports.install = install; 1305 | exports.VOverdrive = VOverdrive; 1306 | exports.default = plugin; 1307 | 1308 | return exports; 1309 | 1310 | }({})); 1311 | -------------------------------------------------------------------------------- /dist/overdrive.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (global = global || self, factory(global.VOverdrive = {})); 5 | }(this, function (exports) { 'use strict'; 6 | 7 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 8 | 9 | function createCommonjsModule(fn, module) { 10 | return module = { exports: {} }, fn(module, module.exports), module.exports; 11 | } 12 | 13 | var ramjet_umd = createCommonjsModule(function (module, exports) { 14 | (function (global, factory) { 15 | factory(exports); 16 | }(commonjsGlobal, function (exports) { 17 | var babelHelpers = {}; 18 | 19 | babelHelpers.classCallCheck = function (instance, Constructor) { 20 | if (!(instance instanceof Constructor)) { 21 | throw new TypeError("Cannot call a class as a function"); 22 | } 23 | }; 24 | 25 | var props = /\b(?:position|zIndex|opacity|transform|webkitTransform|mixBlendMode|filter|webkitFilter|isolation)\b/; 26 | 27 | function isFlexItem(node) { 28 | var display = getComputedStyle(node.parentNode).display; 29 | return display === 'flex' || display === 'inline-flex'; 30 | } 31 | 32 | function createsStackingContext(node) { 33 | var style = getComputedStyle(node); 34 | 35 | // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context 36 | if (style.position === 'fixed') return true; 37 | if (style.zIndex !== 'auto' && style.position !== 'static' || isFlexItem(node)) return true; 38 | if (+style.opacity < 1) return true; 39 | if ('transform' in style && style.transform !== 'none') return true; 40 | if ('webkitTransform' in style && style.webkitTransform !== 'none') return true; 41 | if ('mixBlendMode' in style && style.mixBlendMode !== 'normal') return true; 42 | if ('filter' in style && style.filter !== 'none') return true; 43 | if ('webkitFilter' in style && style.webkitFilter !== 'none') return true; 44 | if ('isolation' in style && style.isolation === 'isolate') return true; 45 | if (props.test(style.willChange)) return true; 46 | if (style.webkitOverflowScrolling === 'touch') return true; 47 | 48 | return false; 49 | } 50 | 51 | function findStackingContext(nodes) { 52 | var i = nodes.length; 53 | 54 | while (i--) { 55 | if (createsStackingContext(nodes[i])) return nodes[i]; 56 | } 57 | 58 | return null; 59 | } 60 | 61 | function getAncestors(node) { 62 | var ancestors = []; 63 | 64 | while (node) { 65 | ancestors.push(node); 66 | node = node.parentNode; 67 | } 68 | 69 | return ancestors; // [ node, ... , , document ] 70 | } 71 | 72 | function getZIndex(node) { 73 | return node && Number(getComputedStyle(node).zIndex) || 0; 74 | } 75 | 76 | function last(array) { 77 | return array[array.length - 1]; 78 | } 79 | 80 | function compare(a, b) { 81 | if (a === b) throw new Error('Cannot compare node with itself'); 82 | 83 | var ancestors = { 84 | a: getAncestors(a), 85 | b: getAncestors(b) 86 | }; 87 | 88 | var commonAncestor = undefined; 89 | 90 | // remove shared ancestors 91 | while (last(ancestors.a) === last(ancestors.b)) { 92 | a = ancestors.a.pop(); 93 | b = ancestors.b.pop(); 94 | 95 | commonAncestor = a; 96 | } 97 | 98 | var stackingContexts = { 99 | a: findStackingContext(ancestors.a), 100 | b: findStackingContext(ancestors.b) 101 | }; 102 | 103 | var zIndexes = { 104 | a: getZIndex(stackingContexts.a), 105 | b: getZIndex(stackingContexts.b) 106 | }; 107 | 108 | if (zIndexes.a === zIndexes.b) { 109 | var children = commonAncestor.childNodes; 110 | 111 | var furthestAncestors = { 112 | a: last(ancestors.a), 113 | b: last(ancestors.b) 114 | }; 115 | 116 | var i = children.length; 117 | while (i--) { 118 | var child = children[i]; 119 | if (child === furthestAncestors.a) return 1; 120 | if (child === furthestAncestors.b) return -1; 121 | } 122 | } 123 | 124 | return Math.sign(zIndexes.a - zIndexes.b); 125 | } 126 | 127 | var svgns = 'http://www.w3.org/2000/svg'; 128 | 129 | function hideNode(node) { 130 | node.__ramjetOriginalTransition__ = node.style.webkitTransition || node.style.transition; 131 | node.__ramjetOriginalOpacity__ = node.style.opacity; 132 | 133 | node.style.webkitTransition = node.style.transition = ''; 134 | 135 | node.style.opacity = 0; 136 | } 137 | 138 | function showNode(node) { 139 | if ('__ramjetOriginalOpacity__' in node) { 140 | node.style.transition = ''; 141 | node.style.opacity = node.__ramjetOriginalOpacity__; 142 | 143 | if (node.__ramjetOriginalTransition__) { 144 | setTimeout(function () { 145 | node.style.transition = node.__ramjetOriginalTransition__; 146 | }); 147 | } 148 | } 149 | } 150 | 151 | function cloneNode(node) { 152 | var clone = node.cloneNode(); 153 | 154 | var isSvg = node.parentNode && node.parentNode.namespaceURI === svgns; 155 | 156 | if (node.nodeType === 1) { 157 | var width = node.style.width; 158 | var height = node.style.height; 159 | 160 | clone.setAttribute('style', window.getComputedStyle(node).cssText); 161 | 162 | if (isSvg) { 163 | clone.style.width = width; 164 | clone.style.height = height; 165 | } 166 | 167 | var len = node.childNodes.length; 168 | var i = undefined; 169 | 170 | for (i = 0; i < len; i += 1) { 171 | clone.appendChild(cloneNode(node.childNodes[i])); 172 | } 173 | } 174 | 175 | return clone; 176 | } 177 | 178 | var bgColorRegexp = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d?.\d+))?\)$/; 179 | 180 | function parseColor(str) { 181 | var match = bgColorRegexp.exec(str); 182 | 183 | if (!match) return null; 184 | 185 | return { 186 | r: +match[1], 187 | g: +match[2], 188 | b: +match[3], 189 | alpha: match[4] ? +match[4] : 1 190 | }; 191 | } 192 | 193 | var borderRadiusRegex = /^(\d+)px(?: (\d+)px)?$/; 194 | 195 | function parseBorderRadius(str) { 196 | var match = borderRadiusRegex.exec(str); 197 | 198 | return match[2] ? { x: +match[1], y: +match[2] } : { x: +match[1], y: +match[1] }; 199 | } 200 | 201 | function findParentByTagName(node, tagName) { 202 | while (node) { 203 | if (node.tagName === tagName) { 204 | return node; 205 | } 206 | 207 | node = node.parentNode; 208 | } 209 | } 210 | 211 | function findTransformParent(node) { 212 | var isSvg = node.namespaceURI === svgns && node.tagName !== 'svg'; 213 | return isSvg ? findParentByTagName(node, 'svg') : node.parentNode; 214 | } 215 | 216 | var div = document.createElement('div'); 217 | 218 | var keyframesSupported = true; 219 | var TRANSFORM = undefined; 220 | var TRANSFORM_ORIGIN = undefined; 221 | var TRANSFORM_CSS = undefined; 222 | var KEYFRAMES = undefined; 223 | var ANIMATION = undefined; 224 | var ANIMATION_DIRECTION = undefined; 225 | var ANIMATION_DURATION = undefined; 226 | var ANIMATION_ITERATION_COUNT = undefined; 227 | var ANIMATION_NAME = undefined; 228 | var ANIMATION_TIMING_FUNCTION = undefined; 229 | var ANIMATION_END = undefined; 230 | 231 | // We have to browser-sniff for IE11, because it was apparently written 232 | // by a barrel of stoned monkeys - http://jsfiddle.net/rich_harris/oquLu2qL/ 233 | 234 | // http://stackoverflow.com/questions/17907445/how-to-detect-ie11 235 | var isIe11 = !window.ActiveXObject && 'ActiveXObject' in window; 236 | 237 | if (!isIe11 && ('transform' in div.style || 'webkitTransform' in div.style) && ('animation' in div.style || 'webkitAnimation' in div.style)) { 238 | keyframesSupported = true; 239 | 240 | if ('webkitTransform' in div.style) { 241 | TRANSFORM = 'webkitTransform'; 242 | TRANSFORM_CSS = '-webkit-transform'; 243 | TRANSFORM_ORIGIN = 'webkitTransformOrigin'; 244 | } else { 245 | TRANSFORM = TRANSFORM_CSS = 'transform'; 246 | TRANSFORM_ORIGIN = 'transformOrigin'; 247 | } 248 | 249 | if ('animation' in div.style) { 250 | KEYFRAMES = '@keyframes'; 251 | 252 | ANIMATION = 'animation'; 253 | ANIMATION_DIRECTION = 'animationDirection'; 254 | ANIMATION_DURATION = 'animationDuration'; 255 | ANIMATION_ITERATION_COUNT = 'animationIterationCount'; 256 | ANIMATION_NAME = 'animationName'; 257 | ANIMATION_TIMING_FUNCTION = 'animationTimingFunction'; 258 | 259 | ANIMATION_END = 'animationend'; 260 | } else { 261 | KEYFRAMES = '@-webkit-keyframes'; 262 | 263 | ANIMATION = 'webkitAnimation'; 264 | ANIMATION_DIRECTION = 'webkitAnimationDirection'; 265 | ANIMATION_DURATION = 'webkitAnimationDuration'; 266 | ANIMATION_ITERATION_COUNT = 'webkitAnimationIterationCount'; 267 | ANIMATION_NAME = 'webkitAnimationName'; 268 | ANIMATION_TIMING_FUNCTION = 'webkitAnimationTimingFunction'; 269 | 270 | ANIMATION_END = 'webkitAnimationEnd'; 271 | } 272 | } else { 273 | keyframesSupported = false; 274 | } 275 | 276 | var IDENTITY = [1, 0, 0, 1, 0, 0]; 277 | 278 | function multiply(_ref, _ref2) { 279 | var a1 = _ref[0]; 280 | var b1 = _ref[1]; 281 | var c1 = _ref[2]; 282 | var d1 = _ref[3]; 283 | var e1 = _ref[4]; 284 | var f1 = _ref[5]; 285 | var a2 = _ref2[0]; 286 | var b2 = _ref2[1]; 287 | var c2 = _ref2[2]; 288 | var d2 = _ref2[3]; 289 | var e2 = _ref2[4]; 290 | var f2 = _ref2[5]; 291 | 292 | return [a1 * a2 + c1 * b2, // a 293 | b1 * a2 + d1 * b2, // b 294 | a1 * c2 + c1 * d2, // c 295 | b1 * c2 + d1 * d2, // d 296 | a1 * e2 + c1 * f2 + e1, // e 297 | b1 * e2 + d1 * f2 + f1 // f 298 | ]; 299 | } 300 | 301 | function invert(_ref3) { 302 | var a = _ref3[0]; 303 | var b = _ref3[1]; 304 | var c = _ref3[2]; 305 | var d = _ref3[3]; 306 | var e = _ref3[4]; 307 | var f = _ref3[5]; 308 | 309 | var determinant = a * d - c * b; 310 | 311 | return [d / determinant, b / -determinant, c / -determinant, a / determinant, (c * f - e * d) / determinant, (e * b - a * f) / determinant]; 312 | } 313 | 314 | function pythag(a, b) { 315 | return Math.sqrt(a * a + b * b); 316 | } 317 | 318 | function decompose(_ref4) { 319 | var a = _ref4[0]; 320 | var b = _ref4[1]; 321 | var c = _ref4[2]; 322 | var d = _ref4[3]; 323 | var e = _ref4[4]; 324 | var f = _ref4[5]; 325 | 326 | // If determinant equals zero (e.g. x scale or y scale equals zero), 327 | // the matrix cannot be decomposed 328 | if (a * d - b * c === 0) return null; 329 | 330 | // See https://github.com/Rich-Harris/Neo/blob/master/Neo.js for 331 | // an explanation of the following 332 | var scaleX = pythag(a, b); 333 | a /= scaleX; 334 | b /= scaleX; 335 | 336 | var scaledShear = a * c + b * d; 337 | var desheared = [a * -scaledShear + c, b * -scaledShear + d]; 338 | 339 | var scaleY = pythag(desheared[0], desheared[1]); 340 | 341 | var skewX = scaledShear / scaleY; 342 | 343 | var rotate = b > 0 ? Math.acos(a) : 2 * Math.PI - Math.acos(a); 344 | 345 | return { 346 | rotate: rotate, 347 | scaleX: scaleX, 348 | scaleY: scaleY, 349 | skewX: skewX, 350 | translateX: e, 351 | translateY: f 352 | }; 353 | } 354 | 355 | function parseMatrixTransformString(transform) { 356 | if (transform.slice(0, 7) !== 'matrix(') { 357 | throw new Error('Could not parse transform string (' + transform + ')'); 358 | } 359 | 360 | return transform.slice(7, -1).split(' ').map(parseFloat); 361 | } 362 | 363 | function getCumulativeTransformMatrix(node) { 364 | if (node.namespaceURI === svgns) { 365 | var _node$getCTM = node.getCTM(); 366 | 367 | var a = _node$getCTM.a; 368 | var b = _node$getCTM.b; 369 | var c = _node$getCTM.c; 370 | var d = _node$getCTM.d; 371 | var e = _node$getCTM.e; 372 | var f = _node$getCTM.f; 373 | 374 | return [a, b, c, d, e, f]; 375 | } 376 | 377 | var matrix = [1, 0, 0, 1, 0, 0]; 378 | 379 | while (node instanceof Element) { 380 | var parentMatrix = getTransformMatrix(node); 381 | 382 | if (parentMatrix) { 383 | matrix = multiply(parentMatrix, matrix); 384 | } 385 | 386 | node = findTransformParent(node); 387 | } 388 | 389 | return matrix; 390 | } 391 | 392 | function getTransformMatrix(node) { 393 | if (node.namespaceURI === svgns) { 394 | var ctm = getCumulativeTransformMatrix(node); 395 | var parentCTM = getCumulativeTransformMatrix(node.parentNode); 396 | return multiply(invert(parentCTM), ctm); 397 | } 398 | 399 | var style = getComputedStyle(node); 400 | var transform = style[TRANSFORM]; 401 | 402 | if (transform === 'none') { 403 | return null; 404 | } 405 | 406 | var origin = style[TRANSFORM_ORIGIN].split(' ').map(parseFloat); 407 | 408 | var matrix = parseMatrixTransformString(transform); 409 | 410 | // compensate for the transform origin (we want to express everything in [0,0] terms) 411 | matrix = multiply([1, 0, 0, 1, origin[0], origin[1]], matrix); 412 | matrix = multiply(matrix, [1, 0, 0, 1, -origin[0], -origin[1]]); 413 | 414 | // TODO if is SVG, multiply by CTM, to account for viewBox 415 | 416 | return matrix; 417 | } 418 | 419 | function getBoundingClientRect(node, invertedParentCTM) { 420 | var originalTransformOrigin = node.style[TRANSFORM_ORIGIN]; 421 | var originalTransform = node.style[TRANSFORM]; 422 | var originalTransformAttribute = node.getAttribute('transform'); // SVG 423 | 424 | node.style[TRANSFORM_ORIGIN] = '0 0'; 425 | node.style[TRANSFORM] = 'matrix(' + invertedParentCTM.join(',') + ')'; 426 | 427 | var bcr = node.getBoundingClientRect(); 428 | 429 | // reset 430 | node.style[TRANSFORM_ORIGIN] = originalTransformOrigin; 431 | node.style[TRANSFORM] = originalTransform; 432 | node.setAttribute('transform', originalTransformAttribute || ''); // TODO remove attribute altogether if null? 433 | 434 | return bcr; 435 | } 436 | 437 | var Wrapper = function () { 438 | function Wrapper(node, options) { 439 | babelHelpers.classCallCheck(this, Wrapper); 440 | 441 | this.init(node, options); 442 | } 443 | 444 | Wrapper.prototype.init = function init(node) { 445 | this._node = node; 446 | this._clone = cloneNode(node); 447 | 448 | var style = window.getComputedStyle(node); 449 | this.style = style; 450 | 451 | // we need to get the 'naked' boundingClientRect, i.e. 452 | // without any transforms 453 | // TODO what if the node is the root node? 454 | var parentCTM = node.namespaceURI === 'svg' ? node.parentNode.getScreenCTM() : getCumulativeTransformMatrix(node.parentNode); 455 | this.invertedParentCTM = invert(parentCTM); 456 | this.transform = getTransformMatrix(node) || IDENTITY; 457 | this.ctm = multiply(parentCTM, this.transform); 458 | 459 | var bcr = getBoundingClientRect(node, this.invertedParentCTM); 460 | this.bcr = bcr; 461 | 462 | // TODO create a flat array? easier to work with later? 463 | var borderRadius = { 464 | tl: parseBorderRadius(style.borderTopLeftRadius), 465 | tr: parseBorderRadius(style.borderTopRightRadius), 466 | br: parseBorderRadius(style.borderBottomRightRadius), 467 | bl: parseBorderRadius(style.borderBottomLeftRadius) 468 | }; 469 | 470 | this.borderRadius = borderRadius; 471 | this.opacity = +style.opacity; 472 | this.rgba = parseColor(style.backgroundColor); 473 | 474 | this.left = bcr.left; 475 | this.top = bcr.top; 476 | this.width = bcr.width; 477 | this.height = bcr.height; 478 | }; 479 | 480 | Wrapper.prototype.insert = function insert() { 481 | var bcr = this.bcr; 482 | 483 | var offsetParent = this._node.offsetParent; 484 | 485 | var clone = undefined; 486 | 487 | if (this._node.namespaceURI === svgns) { 488 | // TODO what if it's the itself, not a child? 489 | var svg = findParentByTagName(this._node, 'svg'); // TODO should be the namespace boundary - could be SVG inside SVG 490 | 491 | clone = svg.cloneNode(false); 492 | clone.appendChild(this._clone); // TODO what about transforms? 493 | } else { 494 | clone = this._clone; 495 | } 496 | 497 | var offsetParentStyle = window.getComputedStyle(offsetParent); 498 | var offsetParentBcr = getBoundingClientRect(offsetParent, invert(getCumulativeTransformMatrix(offsetParent.parentNode))); 499 | 500 | clone.style.position = 'absolute'; 501 | clone.style[TRANSFORM_ORIGIN] = '0 0'; 502 | clone.style.top = bcr.top - parseInt(this.style.marginTop, 10) - (offsetParentBcr.top - parseInt(offsetParentStyle.marginTop, 10)) + 'px'; 503 | clone.style.left = bcr.left - parseInt(this.style.marginLeft, 10) - (offsetParentBcr.left - parseInt(offsetParentStyle.marginLeft, 10)) + 'px'; 504 | 505 | // TODO we need to account for transforms *between* the offset parent and the node 506 | 507 | offsetParent.appendChild(clone); 508 | }; 509 | 510 | Wrapper.prototype.detach = function detach() { 511 | this._clone.parentNode.removeChild(this._clone); 512 | }; 513 | 514 | Wrapper.prototype.setOpacity = function setOpacity(opacity) { 515 | this._clone.style.opacity = opacity; 516 | }; 517 | 518 | Wrapper.prototype.setTransform = function setTransform(transform) { 519 | this._clone.style.transform = this._clone.style.webkitTransform = this._clone.style.msTransform = transform; 520 | }; 521 | 522 | Wrapper.prototype.setBackgroundColor = function setBackgroundColor(color) { 523 | this._clone.style.backgroundColor = color; 524 | }; 525 | 526 | Wrapper.prototype.setBorderRadius = function setBorderRadius(borderRadius) { 527 | this._clone.style.borderRadius = borderRadius; 528 | }; 529 | 530 | Wrapper.prototype.animateWithKeyframes = function animateWithKeyframes(id, duration) { 531 | this._clone.style[ANIMATION_DIRECTION] = 'alternate'; 532 | this._clone.style[ANIMATION_DURATION] = duration / 1000 + 's'; 533 | this._clone.style[ANIMATION_ITERATION_COUNT] = 1; 534 | this._clone.style[ANIMATION_NAME] = id; 535 | this._clone.style[ANIMATION_TIMING_FUNCTION] = 'linear'; 536 | }; 537 | 538 | Wrapper.prototype.freeze = function freeze() { 539 | var computedStyle = getComputedStyle(this._clone); 540 | 541 | this.setOpacity(computedStyle.opacity); 542 | this.setTransform(computedStyle.transform); 543 | this.setBackgroundColor(computedStyle.backgroundColor); 544 | this.setBorderRadius(computedStyle.borderRadius); 545 | 546 | this._clone.style[ANIMATION] = 'none'; 547 | }; 548 | 549 | return Wrapper; 550 | }(); 551 | 552 | function getOpacityInterpolator(from, to, order) { 553 | var opacity = {}; 554 | 555 | return function (t) { 556 | var targetOpacity = (to - from) * t + from; 557 | 558 | // Based on the blending formula here. (http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending) 559 | // This is a quadratic blending function that makes the top layer and bottom layer blend linearly. 560 | // However there is an asymptote at target=1 so that needs to be handled with an if else statement. 561 | if (targetOpacity === 1) { 562 | if (order === 1) { 563 | opacity.from = 1 - t; 564 | opacity.to = 1; 565 | } else { 566 | opacity.from = 1; 567 | opacity.to = t; 568 | } 569 | } else { 570 | opacity.from = targetOpacity - t * t * targetOpacity; 571 | opacity.to = (targetOpacity - opacity.from) / (1 - opacity.from); 572 | } 573 | 574 | return opacity; 575 | }; 576 | } 577 | 578 | function getRgbaInterpolator(a, b, order) { 579 | if (a.alpha === 1 && b.alpha === 1) { 580 | // no need to animate anything 581 | return null; 582 | } 583 | 584 | var rgba = {}; 585 | var opacityAt = getOpacityInterpolator(a.alpha, b.alpha, order); 586 | 587 | return function (t) { 588 | var opacity = opacityAt(t); 589 | 590 | rgba.from = 'rgba(' + a.r + ',' + a.g + ',' + a.b + ',' + opacity.from + ')'; 591 | rgba.to = 'rgba(' + b.r + ',' + b.g + ',' + b.b + ',' + opacity.to + ')'; 592 | 593 | return rgba; 594 | }; 595 | } 596 | 597 | function interpolateArray(a, b) { 598 | var len = a.length; 599 | var array = new Array(len); 600 | 601 | return function (t) { 602 | var i = len; 603 | while (i--) { 604 | array[i] = a[i] + t * (b[i] - a[i]); 605 | } 606 | 607 | return array; 608 | }; 609 | } 610 | 611 | // Border radius is given as a string in the following form 612 | // 613 | // tl.x tr.x br.x bl.x / tl.y tr.y br.y bl.y 614 | // 615 | // ...where t, r, b and l are top, right, bottom, left, and 616 | // x and y are self-explanatory. Each value is followed by 'px' 617 | 618 | // TODO it must be possible to do this more simply. Maybe have 619 | // a flat array from the start? 620 | 621 | function getBorderRadiusInterpolator(a, b) { 622 | // TODO fast path - no transition needed 623 | 624 | var aWidth = a.width; 625 | var aHeight = a.height; 626 | 627 | var bWidth = b.width; 628 | var bHeight = b.height; 629 | 630 | a = a.borderRadius; 631 | b = b.borderRadius; 632 | 633 | var a_x_t0 = [a.tl.x, a.tr.x, a.br.x, a.bl.x]; 634 | var a_y_t0 = [a.tl.y, a.tr.y, a.br.y, a.bl.y]; 635 | 636 | var b_x_t1 = [b.tl.x, b.tr.x, b.br.x, b.bl.x]; 637 | var b_y_t1 = [b.tl.y, b.tr.y, b.br.y, b.bl.y]; 638 | 639 | var a_x_t1 = b_x_t1.map(function (x) { 640 | return x * aWidth / bWidth; 641 | }); 642 | var a_y_t1 = b_y_t1.map(function (y) { 643 | return y * aHeight / bHeight; 644 | }); 645 | 646 | var b_x_t0 = a_x_t0.map(function (x) { 647 | return x * bWidth / aWidth; 648 | }); 649 | var b_y_t0 = a_y_t0.map(function (y) { 650 | return y * bHeight / aHeight; 651 | }); 652 | 653 | var ax = interpolateArray(a_x_t0, a_x_t1); 654 | var ay = interpolateArray(a_y_t0, a_y_t1); 655 | 656 | var bx = interpolateArray(b_x_t0, b_x_t1); 657 | var by = interpolateArray(b_y_t0, b_y_t1); 658 | 659 | var borderRadius = {}; 660 | 661 | return function (t) { 662 | var x = ax(t); 663 | var y = ay(t); 664 | 665 | borderRadius.from = x.join('px ') + 'px / ' + y.join('px ') + 'px'; 666 | 667 | x = bx(t); 668 | y = by(t); 669 | 670 | borderRadius.to = x.join('px ') + 'px / ' + y.join('px ') + 'px'; 671 | 672 | return borderRadius; 673 | }; 674 | } 675 | 676 | function interpolateMatrices(a, b) { 677 | var transform = []; 678 | 679 | return function (t) { 680 | var i = a.length; 681 | while (i--) { 682 | var from = a[i]; 683 | var to = b[i]; 684 | transform[i] = from + t * (to - from); 685 | } 686 | 687 | return 'matrix(' + transform.join(',') + ')'; 688 | }; 689 | } 690 | 691 | function interpolate(a, b) { 692 | var d = b - a; 693 | return function (t) { 694 | return a + t * d; 695 | }; 696 | } 697 | 698 | function getRotation(radians) { 699 | while (radians > Math.PI) { 700 | radians -= Math.PI * 2; 701 | }while (radians < -Math.PI) { 702 | radians += Math.PI * 2; 703 | }return radians; 704 | } 705 | 706 | function interpolateDecomposedTransforms(a, b) { 707 | var rotate = interpolate(getRotation(a.rotate), getRotation(b.rotate)); 708 | var skewX = interpolate(a.skewX, b.skewX); 709 | var scaleX = interpolate(a.scaleX, b.scaleX); 710 | var scaleY = interpolate(a.scaleY, b.scaleY); 711 | var translateX = interpolate(a.translateX, b.translateX); 712 | var translateY = interpolate(a.translateY, b.translateY); 713 | 714 | return function (t) { 715 | var transform = 'translate(' + translateX(t) + 'px, ' + translateY(t) + 'px) rotate(' + rotate(t) + 'rad) skewX(' + skewX(t) + 'rad) scale(' + scaleX(t) + ', ' + scaleY(t) + ')'; 716 | return transform; 717 | }; 718 | } 719 | 720 | function getTransformInterpolator(a, b) { 721 | var scale_x = b.width / a.width; 722 | var scale_y = b.height / a.height; 723 | var d_x = b.left - a.left; 724 | var d_y = b.top - a.top; 725 | 726 | var a_start = a.transform; 727 | 728 | var move_a_to_b = [1, 0, 0, 1, d_x, d_y]; 729 | var scale_a_to_b = [scale_x, 0, 0, scale_y, 0, 0]; 730 | 731 | var matrix = IDENTITY; 732 | 733 | matrix = multiply(matrix, a.invertedParentCTM); 734 | matrix = multiply(matrix, move_a_to_b); 735 | matrix = multiply(matrix, b.ctm); 736 | matrix = multiply(matrix, scale_a_to_b); 737 | 738 | var decomposed_start = decompose(a_start); 739 | var decomposed_end = decompose(matrix); 740 | 741 | if (!decomposed_start || !decomposed_end) return interpolateMatrices(a_start, matrix); 742 | return interpolateDecomposedTransforms(decomposed_start, decomposed_end); 743 | } 744 | 745 | function linear(pos) { 746 | return pos; 747 | } 748 | 749 | function easeIn(pos) { 750 | return Math.pow(pos, 3); 751 | } 752 | 753 | function easeOut(pos) { 754 | return Math.pow(pos - 1, 3) + 1; 755 | } 756 | 757 | function easeInOut(pos) { 758 | if ((pos /= 0.5) < 1) { 759 | return 0.5 * Math.pow(pos, 3); 760 | } 761 | 762 | return 0.5 * (Math.pow(pos - 2, 3) + 2); 763 | } 764 | 765 | var head = document.getElementsByTagName('head')[0]; 766 | 767 | function addCss(css) { 768 | var styleElement = document.createElement('style'); 769 | styleElement.type = 'text/css'; 770 | 771 | // Internet Exploder won't let you use styleSheet.innerHTML - we have to 772 | // use styleSheet.cssText instead 773 | var styleSheet = styleElement.styleSheet; 774 | 775 | if (styleSheet) { 776 | styleSheet.cssText = css; 777 | } else { 778 | styleElement.innerHTML = css; 779 | } 780 | 781 | head.appendChild(styleElement); 782 | 783 | return function () { 784 | return head.removeChild(styleElement); 785 | }; 786 | } 787 | 788 | function getKeyframes(from, to, interpolators, easing, remaining, duration) { 789 | var numFrames = remaining / 16; 790 | 791 | var fromKeyframes = ''; 792 | var toKeyframes = ''; 793 | 794 | function addKeyframes(pc, t) { 795 | var opacity = interpolators.opacity(t); 796 | var backgroundColor = interpolators.backgroundColor ? interpolators.backgroundColor(t) : null; 797 | var borderRadius = interpolators.borderRadius ? interpolators.borderRadius(t) : null; 798 | var transformFrom = interpolators.transformFrom(t); 799 | var transformTo = interpolators.transformTo(1 - t); 800 | 801 | fromKeyframes += '\n' + (pc + '% {') + ('opacity: ' + opacity.from + ';') + (TRANSFORM_CSS + ': ' + transformFrom + ';') + (backgroundColor ? 'background-color: ' + backgroundColor.from + ';' : '') + (borderRadius ? 'border-radius: ' + borderRadius.from + ';' : '') + '}'; 802 | 803 | toKeyframes += '\n' + (pc + '% {') + ('opacity: ' + opacity.to + ';') + (TRANSFORM_CSS + ': ' + transformTo + ';') + (backgroundColor ? 'background-color: ' + backgroundColor.to + ';' : '') + (borderRadius ? 'border-radius: ' + borderRadius.to + ';' : '') + '}'; 804 | } 805 | 806 | var i = undefined; 807 | var startPos = 1 - remaining / duration; 808 | 809 | for (i = 0; i < numFrames; i += 1) { 810 | var relPos = i / numFrames; 811 | var absPos = startPos + remaining / duration * relPos; 812 | 813 | var pc = 100 * relPos; 814 | var t = easing(absPos); 815 | 816 | addKeyframes(pc, t); 817 | } 818 | 819 | addKeyframes(100, 1); 820 | 821 | return { fromKeyframes: fromKeyframes, toKeyframes: toKeyframes }; 822 | } 823 | 824 | function generateId() { 825 | return 'ramjet' + ~ ~(Math.random() * 1000000); 826 | } 827 | 828 | var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (fn) { 829 | return setTimeout(fn, 16); 830 | }; 831 | 832 | function transformer(from, to, options) { 833 | var duration = options.duration || 400; 834 | var easing = options.easing || linear; 835 | 836 | var useTimer = !keyframesSupported || !!options.useTimer; 837 | 838 | var order = compare(from._node, to._node); 839 | 840 | var interpolators = { 841 | opacity: getOpacityInterpolator(from.opacity, to.opacity, order), 842 | backgroundColor: options.interpolateBackgroundColor ? getRgbaInterpolator(from.rgba, to.rgba, order) : null, 843 | borderRadius: options.interpolateBorderRadius ? getBorderRadiusInterpolator(from, to) : null, 844 | transformFrom: getTransformInterpolator(from, to), 845 | transformTo: getTransformInterpolator(to, from) 846 | }; 847 | 848 | var running = undefined; 849 | var disposeCss = undefined; 850 | var torndown = undefined; 851 | 852 | var remaining = duration; 853 | var endTime = undefined; 854 | 855 | function tick() { 856 | if (!running) return; 857 | 858 | var timeNow = Date.now(); 859 | remaining = endTime - timeNow; 860 | 861 | if (remaining < 0) { 862 | transformer.teardown(); 863 | if (options.done) options.done(); 864 | 865 | return; 866 | } 867 | 868 | var t = easing(1 - remaining / duration); 869 | transformer.goto(t); 870 | 871 | rAF(tick); 872 | } 873 | 874 | var transformer = { 875 | teardown: function () { 876 | if (torndown) return transformer; 877 | 878 | running = false; 879 | torndown = true; 880 | 881 | from.detach(); 882 | to.detach(); 883 | 884 | from = null; 885 | to = null; 886 | 887 | return transformer; 888 | }, 889 | goto: function (pos) { 890 | transformer.pause(); 891 | 892 | var t = easing(pos); 893 | 894 | // opacity 895 | var opacity = interpolators.opacity(t); 896 | from.setOpacity(opacity.from); 897 | to.setOpacity(opacity.to); 898 | 899 | // transform 900 | var transformFrom = interpolators.transformFrom(t); 901 | var transformTo = interpolators.transformTo(1 - t); 902 | from.setTransform(transformFrom); 903 | to.setTransform(transformTo); 904 | 905 | // background color 906 | if (interpolators.backgroundColor) { 907 | var backgroundColor = interpolators.backgroundColor(t); 908 | from.setBackgroundColor(backgroundColor.from); 909 | to.setBackgroundColor(backgroundColor.to); 910 | } 911 | 912 | // border radius 913 | if (interpolators.borderRadius) { 914 | var borderRadius = interpolators.borderRadius(t); 915 | from.setBorderRadius(borderRadius.from); 916 | to.setBorderRadius(borderRadius.to); 917 | } 918 | 919 | return transformer; 920 | }, 921 | pause: function () { 922 | if (!running) return transformer; 923 | running = false; 924 | 925 | if (!useTimer) { 926 | // TODO derive current position somehow, use that rather than 927 | // current computed style (from and to get out of sync in 928 | // some browsers?) 929 | remaining = endTime - Date.now(); 930 | 931 | from.freeze(); 932 | to.freeze(); 933 | disposeCss(); 934 | } 935 | 936 | return transformer; 937 | }, 938 | play: function () { 939 | if (running) return transformer; 940 | running = true; 941 | 942 | endTime = Date.now() + remaining; 943 | 944 | if (useTimer) { 945 | rAF(tick); 946 | } else { 947 | var _getKeyframes = getKeyframes(from, to, interpolators, options.easing || linear, remaining, duration); 948 | 949 | var fromKeyframes = _getKeyframes.fromKeyframes; 950 | var toKeyframes = _getKeyframes.toKeyframes; 951 | 952 | var fromId = generateId(); 953 | var toId = generateId(); 954 | 955 | var css = '\n\t\t\t\t\t' + KEYFRAMES + ' ' + fromId + ' { ' + fromKeyframes + ' }\n\t\t\t\t\t' + KEYFRAMES + ' ' + toId + ' { ' + toKeyframes + ' }'; 956 | 957 | disposeCss = addCss(css); 958 | 959 | from.animateWithKeyframes(fromId, remaining); 960 | to.animateWithKeyframes(toId, remaining); 961 | } 962 | 963 | return transformer; 964 | } 965 | }; 966 | 967 | // handle animation end 968 | if (!useTimer) { 969 | (function () { 970 | var animating = 2; 971 | 972 | var done = function () { 973 | if (! --animating) { 974 | transformer.teardown(); 975 | 976 | if (options.done) options.done(); 977 | 978 | disposeCss(); 979 | } 980 | }; 981 | 982 | from._clone.addEventListener(ANIMATION_END, done); 983 | to._clone.addEventListener(ANIMATION_END, done); 984 | })(); 985 | } 986 | 987 | return transformer.play(); 988 | } 989 | 990 | function transform(fromNode, toNode) { 991 | var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 992 | 993 | if (typeof options === 'function') { 994 | options = { done: options }; 995 | } 996 | 997 | if (!('duration' in options)) { 998 | options.duration = 400; 999 | } 1000 | 1001 | var from = new Wrapper(fromNode, options); 1002 | var to = new Wrapper(toNode, options); 1003 | 1004 | var order = compare(from._node, to._node); 1005 | 1006 | from.setOpacity(1); 1007 | to.setOpacity(0); 1008 | 1009 | // in many cases, the stacking order of `from` and `to` is 1010 | // determined by their relative location in the document – 1011 | // so we need to preserve it 1012 | if (order === 1) { 1013 | to.insert(); 1014 | from.insert(); 1015 | } else { 1016 | from.insert(); 1017 | to.insert(); 1018 | } 1019 | 1020 | return transformer(from, to, options); 1021 | } 1022 | 1023 | function hide() { 1024 | for (var _len = arguments.length, nodes = Array(_len), _key = 0; _key < _len; _key++) { 1025 | nodes[_key] = arguments[_key]; 1026 | } 1027 | 1028 | nodes.forEach(hideNode); 1029 | } 1030 | 1031 | function show() { 1032 | for (var _len2 = arguments.length, nodes = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 1033 | nodes[_key2] = arguments[_key2]; 1034 | } 1035 | 1036 | nodes.forEach(showNode); 1037 | } 1038 | 1039 | exports.transform = transform; 1040 | exports.hide = hide; 1041 | exports.show = show; 1042 | exports.linear = linear; 1043 | exports.easeIn = easeIn; 1044 | exports.easeOut = easeOut; 1045 | exports.easeInOut = easeInOut; 1046 | 1047 | })); 1048 | 1049 | }); 1050 | 1051 | const components = {}; 1052 | const getPosition = (node, addOffset = false) => { 1053 | const rect = node.getBoundingClientRect(); 1054 | const computedStyle = window.getComputedStyle(node); 1055 | const marginTop = parseInt(computedStyle.marginTop, 10); 1056 | const marginLeft = parseInt(computedStyle.marginLeft, 10); 1057 | 1058 | return { 1059 | top: `${rect.top - 1060 | marginTop + 1061 | (addOffset ? 1 : 0) * 1062 | (window.pageYOffset || document.documentElement.scrollTop)}px`, 1063 | left: `${rect.left - marginLeft}px`, 1064 | width: `${rect.width}px`, 1065 | height: `${rect.height}px`, 1066 | borderRadius: computedStyle.borderRadius, 1067 | position: "absolute" 1068 | }; 1069 | }; 1070 | var script = { 1071 | props: { 1072 | tag: { 1073 | type: String, 1074 | default: "div" 1075 | }, 1076 | id: { 1077 | type: String, 1078 | required: true 1079 | }, 1080 | duration: { 1081 | type: Number, 1082 | duration: 400 1083 | }, 1084 | easing: { 1085 | type: Function, 1086 | default: ramjet_umd.linear 1087 | } 1088 | }, 1089 | data() { 1090 | return { 1091 | animating: false, 1092 | transformer: {} 1093 | }; 1094 | }, 1095 | methods: { 1096 | cache() { 1097 | components[this.id] = { 1098 | el: this.$slots.default, 1099 | pos: getPosition(this.$el.firstChild) 1100 | }; 1101 | }, 1102 | cloneAndAppend() { 1103 | const { el, pos } = components[this.id]; 1104 | const clone = el[0].elm.cloneNode(true); 1105 | clone.setAttribute("data-clone", this.id); 1106 | Object.assign(clone.style, pos); 1107 | document.body.appendChild(clone); 1108 | }, 1109 | bustCache() { 1110 | Object.keys(components).forEach(id => { 1111 | components[id] = false; 1112 | }); 1113 | }, 1114 | animate(cb = () => {}) { 1115 | const a = document.querySelector(`[data-clone="${this.id}"]`); 1116 | const b = this.$el.firstChild; 1117 | this.animating = true; 1118 | this.transformer = ramjet_umd.transform(a, b, { 1119 | duration: this.duration, 1120 | easing: this.easing, 1121 | appendToBody: true, 1122 | done: () => { 1123 | cb(a, b); 1124 | this.animating = false; 1125 | this.$emit("animation-end"); 1126 | } 1127 | }); 1128 | ramjet_umd.hide(a, b); 1129 | }, 1130 | handleMatch() { 1131 | this.cloneAndAppend(); 1132 | const cb = (a, b) => { 1133 | ramjet_umd.show(b); 1134 | }; 1135 | this.$nextTick(() => { 1136 | this.animate(cb); 1137 | const clone = document.querySelector(`[data-clone="${this.id}"]`); 1138 | document.body.removeChild(clone); 1139 | this.cache(); 1140 | }); 1141 | } 1142 | }, 1143 | mounted() { 1144 | const match = components[this.id]; 1145 | if (match) { 1146 | this.handleMatch(); 1147 | } else { 1148 | this.cache(); 1149 | } 1150 | }, 1151 | beforeDestroy() { 1152 | if (this.animating) { 1153 | this.transformer.teardown(); 1154 | } 1155 | }, 1156 | render(h) { 1157 | return h(this.tag, [this.$slots.default]); 1158 | } 1159 | }; 1160 | 1161 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier 1162 | /* server only */ 1163 | , shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 1164 | if (typeof shadowMode !== 'boolean') { 1165 | createInjectorSSR = createInjector; 1166 | createInjector = shadowMode; 1167 | shadowMode = false; 1168 | } // Vue.extend constructor export interop. 1169 | 1170 | 1171 | var options = typeof script === 'function' ? script.options : script; // render functions 1172 | 1173 | if (template && template.render) { 1174 | options.render = template.render; 1175 | options.staticRenderFns = template.staticRenderFns; 1176 | options._compiled = true; // functional template 1177 | 1178 | if (isFunctionalTemplate) { 1179 | options.functional = true; 1180 | } 1181 | } // scopedId 1182 | 1183 | 1184 | if (scopeId) { 1185 | options._scopeId = scopeId; 1186 | } 1187 | 1188 | var hook; 1189 | 1190 | if (moduleIdentifier) { 1191 | // server build 1192 | hook = function hook(context) { 1193 | // 2.3 injection 1194 | context = context || // cached call 1195 | this.$vnode && this.$vnode.ssrContext || // stateful 1196 | this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional 1197 | // 2.2 with runInNewContext: true 1198 | 1199 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 1200 | context = __VUE_SSR_CONTEXT__; 1201 | } // inject component styles 1202 | 1203 | 1204 | if (style) { 1205 | style.call(this, createInjectorSSR(context)); 1206 | } // register component module identifier for async chunk inference 1207 | 1208 | 1209 | if (context && context._registeredComponents) { 1210 | context._registeredComponents.add(moduleIdentifier); 1211 | } 1212 | }; // used by ssr in case component is cached and beforeCreate 1213 | // never gets called 1214 | 1215 | 1216 | options._ssrRegister = hook; 1217 | } else if (style) { 1218 | hook = shadowMode ? function () { 1219 | style.call(this, createInjectorShadow(this.$root.$options.shadowRoot)); 1220 | } : function (context) { 1221 | style.call(this, createInjector(context)); 1222 | }; 1223 | } 1224 | 1225 | if (hook) { 1226 | if (options.functional) { 1227 | // register for functional component in vue file 1228 | var originalRender = options.render; 1229 | 1230 | options.render = function renderWithStyleInjection(h, context) { 1231 | hook.call(context); 1232 | return originalRender(h, context); 1233 | }; 1234 | } else { 1235 | // inject component registration as beforeCreate hook 1236 | var existing = options.beforeCreate; 1237 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 1238 | } 1239 | } 1240 | 1241 | return script; 1242 | } 1243 | 1244 | var normalizeComponent_1 = normalizeComponent; 1245 | 1246 | /* script */ 1247 | const __vue_script__ = script; 1248 | 1249 | /* template */ 1250 | 1251 | /* style */ 1252 | const __vue_inject_styles__ = undefined; 1253 | /* scoped */ 1254 | const __vue_scope_id__ = undefined; 1255 | /* module identifier */ 1256 | const __vue_module_identifier__ = undefined; 1257 | /* functional template */ 1258 | const __vue_is_functional_template__ = undefined; 1259 | /* style inject */ 1260 | 1261 | /* style inject SSR */ 1262 | 1263 | 1264 | 1265 | var Overdrive = normalizeComponent_1( 1266 | {}, 1267 | __vue_inject_styles__, 1268 | __vue_script__, 1269 | __vue_scope_id__, 1270 | __vue_is_functional_template__, 1271 | __vue_module_identifier__, 1272 | undefined, 1273 | undefined 1274 | ); 1275 | 1276 | function install(Vue) { 1277 | if (install.installed) return; 1278 | install.installed = true; 1279 | Vue.component("overdrive", Overdrive); 1280 | } 1281 | 1282 | const VOverdrive = Overdrive; 1283 | 1284 | const plugin = { 1285 | install, 1286 | 1287 | get enabled() { 1288 | return state.enabled; 1289 | }, 1290 | 1291 | set enabled(value) { 1292 | state.enabled = value; 1293 | } 1294 | }; 1295 | 1296 | // Auto-install 1297 | let GlobalVue = null; 1298 | if (typeof window !== "undefined") { 1299 | GlobalVue = window.Vue; 1300 | } else if (typeof global !== "undefined") { 1301 | GlobalVue = global.Vue; 1302 | } 1303 | if (GlobalVue) { 1304 | GlobalVue.use(plugin); 1305 | } 1306 | 1307 | exports.install = install; 1308 | exports.VOverdrive = VOverdrive; 1309 | exports.default = plugin; 1310 | 1311 | Object.defineProperty(exports, '__esModule', { value: true }); 1312 | 1313 | })); 1314 | --------------------------------------------------------------------------------