├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── babel.config.js ├── docs ├── .vuepress │ ├── components │ │ ├── DemoBox.vue │ │ ├── DemoContainer.vue │ │ └── DemoModeBox.vue │ ├── config.js │ └── enhanceApp.js ├── README.md └── examples.md ├── jest.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── AnimatedGeneric.js └── index.js ├── tests └── unit │ ├── .eslintrc.js │ └── AnimatedTransition.spec.js └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /docs/.vuepress/dist 5 | coverage 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw* 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "lts/*" 4 | after_script: 'cat ./coverage/lcov.info | coveralls' 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Filippo Conti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-animated 2 | 3 | > Functional animation components using Animate.css 4 | 5 | [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url] 6 | 7 | 8 | ### [Documentation](https://codekraft-studio.github.io/vue-animated/) 9 | 10 | --- 11 | 12 | ## License 13 | 14 | This project is released under the [MIT License](./LICENSE) by [codekraft-studio](https://codekraft.it/). 15 | 16 | 17 | [npm-image]: https://badge.fury.io/js/%40codekraft-studio%2Fvue-animated.svg 18 | 19 | [npm-url]: https://npmjs.org/package/@codekraft-studio/vue-animated 20 | 21 | [daviddm-image]: https://david-dm.org/codekraft-studio/vue-animated.svg?theme=shields.io 22 | 23 | [daviddm-url]: https://david-dm.org/codekraft-studio/vue-animated 24 | 25 | [travis-image]: https://travis-ci.org/codekraft-studio/vue-animated.svg?branch=master 26 | 27 | [travis-url]: https://travis-ci.org/codekraft-studio/vue-animated 28 | 29 | [coveralls-image]: https://coveralls.io/repos/codekraft-studio/vue-animated/badge.svg 30 | 31 | [coveralls-url]: https://coveralls.io/r/codekraft-studio/vue-animated 32 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/.vuepress/components/DemoBox.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /docs/.vuepress/components/DemoContainer.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{show ? 'Hide' : 'Show'}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 29 | 30 | 51 | -------------------------------------------------------------------------------- /docs/.vuepress/components/DemoModeBox.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Out in 6 | 7 | In out 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 30 | 31 | 36 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | base: "/vue-animated/", 3 | title: "Vue Animated", 4 | description: "Functional animation components based on Animate.css", 5 | themeConfig: { 6 | repo: "codekraft-studio/vue-animated", 7 | editLinks: true, 8 | editLinkText: "Want to improve the documentation?", 9 | displayAllHeaders: true, 10 | smoothScroll: true, 11 | sidebar: [ 12 | "", 13 | "examples" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/.vuepress/enhanceApp.js: -------------------------------------------------------------------------------- 1 | import pageComponents from '@internal/page-components' 2 | import VueAnimated, {AnimatedGeneric} from "../../src" 3 | 4 | export default ({ 5 | Vue, 6 | options, 7 | router, 8 | siteData 9 | }) => { 10 | 11 | // fix for Unknown Custom Element - 12 | // @see https://github.com/vuejs/vuepress/issues/1173 13 | for (const [name, component] of Object.entries(pageComponents)) { 14 | Vue.component(name, component) 15 | } 16 | 17 | Vue.use(VueAnimated, { 18 | functional: true 19 | }); 20 | 21 | Vue.component("HingeAnimation", new AnimatedGeneric("hinge", { 22 | defaultDuration: { 23 | enter: 0, 24 | leave: 2500 25 | } 26 | })); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | ## Installation 4 | 5 | Download the project using your favorite package manager: 6 | 7 | ``` 8 | npm install @codekraft-studio/vue-animated 9 | ``` 10 | 11 | Than load the plugin into Vue to register its global components. 12 | 13 | ```js 14 | import Vue from 'vue' 15 | import VueAnimated from '@codekraft-studio/vue-animated' 16 | 17 | Vue.use(VueAnimated) 18 | ``` 19 | 20 | The plugin can also accept options to customize the default animation behavior and to choose which animation transitions should be created and globally registered. 21 | 22 | ### Customization 23 | 24 | When adding the plugin to the Vue instance you can pass it default properties and customize what is loaded. 25 | 26 | #### defaultDuration 27 | 28 | An integer that represents the default animation duration when not specified as property. 29 | 30 | ```js 31 | Vue.use(VueAnimated, { 32 | defaultDuration: 750 33 | }) 34 | ``` 35 | 36 | #### functional 37 | 38 | A boolean to indicate if built-in functional components of the common animations (such as _attention seekers_ and _special animation_) should be loaded or not, by default they are not loaded. 39 | 40 | ```js 41 | Vue.use(VueAnimated, { 42 | functional: true 43 | }) 44 | ``` 45 | 46 | You can also pass an array of __existing animation names__ to register only the transition components you need. 47 | 48 | ```js 49 | Vue.use(VueAnimated, { 50 | functional: ['shake', 'pulse'] 51 | }) 52 | ``` 53 | 54 | ```html 55 | 56 | 57 | ``` 58 | 59 | --- 60 | 61 | ## Usage 62 | 63 | ### Generic component 64 | 65 | The plugin expose an extended functional component called `Animated` that is designed to work with __animate.css__ classes out of the box. It works like a normal transition component but allow you to specify the animation enter and leave names directly from properties. 66 | 67 | ```html 68 | 69 | 70 | 71 | ``` 72 | 73 | The example above will use `slideInRight` animation while entering and `slideOutLeft` animation while leaving, all the others transition component properties such as [duration](https://vuejs.org/v2/guide/transitions.html#Explicit-Transition-Durations) and [hooks](https://vuejs.org/v2/guide/transitions.html#JavaScript-Hooks) can be used normally. 74 | 75 | When using __enter__ and __leave__ properties you should specify the name of an existing [animate.css](https://github.com/daneden/animate.css/) class as shown in their documentation. 76 | 77 | You can also use the same animation for both enter and leave states using the __name__ property as you would do in a normal transition element. 78 | 79 | ```html 80 | 81 | 82 | 83 | ``` 84 | 85 | The `Animated` base component for generic transitions and the built-in functional components based on existing classes can be used also in __kebab-case__, the following example show codes that will produce the same result. 86 | 87 | ```html 88 | 89 | 90 | 91 | 92 | 93 | ``` 94 | 95 | ### Named components 96 | 97 | When registering the plugin to your Vue application you can enable the generated built-in animation components registration. 98 | 99 | ```js 100 | Vue.use(VueAnimated, { 101 | functional: true 102 | }) 103 | ``` 104 | 105 | This will register globally a set of pre-defined transitions based on the _attention seekers_ and _special animation_ groups of [animate.css](https://github.com/daneden/animate.css/). 106 | 107 | ```html 108 | 109 | 110 | 111 | ``` 112 | 113 | ### Custom component 114 | 115 | The plugin provides also a generic component class to let you create any functional component based on the __animate.css__ animations class names. This allow you to customize what is registered as component and loaded in your application. Also is useful if you want to add any custom animation to __animate.css__ and have it as functional component. 116 | 117 | ```js 118 | import Vue from 'vue' 119 | import {AnimatedGeneric} from '@codekraft-studio/vue-animated' 120 | 121 | Vue.component("HingeAnimation", new AnimatedGeneric("hinge", { 122 | defaultDuration: { 123 | enter: 0, 124 | leave: 2500 125 | } 126 | })) 127 | ``` 128 | 129 | Now you will be able to use `HingeAnimation` component everywhere in your app. 130 | 131 | ```html 132 | 133 | 134 | 135 | ``` 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | In this section there are some usage examples to show the plugin functionalities. In all the examples is used the generic animation component because is always registered but you can also use the generated named functional components based on __animate.css__ animation names. 4 | 5 | ## Simple animation 6 | 7 | Animate both enter and leave state of any element. 8 | 9 | ```html 10 | 11 | 12 | 13 | ``` 14 | 15 | 16 | 17 | ## Enter and Leave 18 | 19 | Use a different animation for enter and leave states. 20 | 21 | ```html 22 | 23 | 24 | 25 | ``` 26 | 27 | 28 | 29 | ## Animation duration 30 | 31 | You can also customize the duration as you would with the transition component. 32 | 33 | ```html 34 | 35 | 36 | 37 | ``` 38 | 39 | You can also specify separate values for enter and leave durations. 40 | 41 | ```html 42 | 43 | 44 | 45 | ``` 46 | 47 | 48 | 49 | ## Transition modes 50 | 51 | The mode property is supported as in a normal transition component, by default is "out-in" 52 | 53 | ```html 54 | 55 | 56 | 57 | ``` 58 | 59 | 60 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': '/node_modules/babel-jest' 12 | }, 13 | moduleNameMapper: { 14 | '^@/(.*)$': '/src/$1' 15 | }, 16 | snapshotSerializers: [ 17 | 'jest-serializer-vue' 18 | ], 19 | testMatch: [ 20 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 21 | ], 22 | testURL: 'http://localhost/', 23 | collectCoverage: true 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@codekraft-studio/vue-animated", 3 | "description": "Functional animation components using Animate.css", 4 | "version": "0.1.5", 5 | "main": "dist/VueAnimated.umd.js", 6 | "homepage": "https://codekraft-studio.github.io/vue-animated/", 7 | "repository": "codekraft-studio/vue-animated", 8 | "files": [ 9 | "dist" 10 | ], 11 | "scripts": { 12 | "prepublishOnly": "npm run build", 13 | "pretest": "npm run lint", 14 | "build": "vue-cli-service build --target lib --name VueAnimated src/index.js", 15 | "lint": "vue-cli-service lint", 16 | "test": "vue-cli-service test:unit", 17 | "docs:dev": "vuepress dev docs", 18 | "docs:build": "vuepress build docs" 19 | }, 20 | "keywords": [ 21 | "vue", 22 | "animate.css", 23 | "vue-components", 24 | "vue-plugin", 25 | "vue-transitions", 26 | "vue-animations", 27 | "vue-animated", 28 | "animations", 29 | "transitions" 30 | ], 31 | "dependencies": { 32 | "animate.css": "^3.4.0", 33 | "vue": "^2.2.0" 34 | }, 35 | "devDependencies": { 36 | "@vue/cli-plugin-babel": "^3.11.0", 37 | "@vue/cli-plugin-eslint": "^3.11.0", 38 | "@vue/cli-plugin-unit-jest": "^3.11.0", 39 | "@vue/cli-service": "^3.11.0", 40 | "@vue/test-utils": "^1.0.0-beta.29", 41 | "babel-core": "~7.0.0-bridge.0", 42 | "babel-eslint": "^10.0.1", 43 | "babel-jest": "^24.9.0", 44 | "coveralls": "^3.0.3", 45 | "eslint": "^5.8.0", 46 | "eslint-plugin-vue": "^5.2.3", 47 | "node-sass": "^4.10.0", 48 | "sass-loader": "^7.1.0", 49 | "vue-template-compiler": "^2.5.17", 50 | "vuepress": "^1.1.0" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/AnimatedGeneric.js: -------------------------------------------------------------------------------- 1 | function setAnimationDuration(el, duration, state) { 2 | const d = isNaN(duration[state]) ? duration : duration[state] 3 | el.style.animationDuration = `${d}ms` 4 | } 5 | 6 | function unsetAnimationDuration(el) { 7 | el.style.animationDuration = "unset" 8 | } 9 | 10 | const noop = () => {}; 11 | 12 | /** 13 | * Generic animation bindings for animate.css 14 | * when a empty or undefined name is passed 15 | * the animation state classes are built using the 16 | * context properties, otherwise are forced to use the 17 | * component generated properties based on the name itself 18 | */ 19 | export default class AnimatedGeneric { 20 | constructor(name, opts = { 21 | defaultDuration: 1000 22 | }) { 23 | this.name = name 24 | this.functional = true 25 | 26 | // Set static animation class properties based on 27 | // given animation name or leave it blank to use 28 | // user provided animation name in props 29 | this.enterClass = name && `${name} ${name}-enter-active` 30 | this.leaveClass = name && `${name} ${name}-leave-active` 31 | 32 | this.render = (createEl, { 33 | data, 34 | children, 35 | props = {}, 36 | }) => { 37 | const { 38 | attrs = {}, 39 | on: listeners = {}, 40 | } = data 41 | 42 | // TODO: Add support for transition-group components 43 | const elName = 'transition' 44 | 45 | // Ensure all listeners exists so redirect won't fail 46 | const elListeners = [ 47 | 'before-enter', 48 | 'before-leave', 49 | 'after-enter', 50 | 'after-leave', 51 | 'enter-cancelled', 52 | 'leave-cancelled', 53 | ].reduce((obj, l) => ({ 54 | ...obj, 55 | [l]: listeners[l] || noop 56 | }), {}) 57 | 58 | const animName = name || props.name 59 | 60 | // Get custom animation duration or fallback to default 61 | const duration = props.duration || opts.defaultDuration 62 | 63 | // Prepare the transition context 64 | const elData = { 65 | attrs, 66 | props: { 67 | name: animName, 68 | enterActiveClass: this.enterClass || props.enter || animName, 69 | leaveActiveClass: this.leaveClass || props.leave || animName 70 | }, 71 | on: { 72 | beforeEnter: (el) => { 73 | setAnimationDuration(el, duration, 'enter') 74 | elListeners['before-enter'](el) 75 | }, 76 | beforeLeave: (el) => { 77 | setAnimationDuration(el, duration, 'leave') 78 | elListeners['before-leave'](el) 79 | }, 80 | afterEnter: (el) => { 81 | unsetAnimationDuration(el) 82 | elListeners['after-enter'](el) 83 | }, 84 | afterLeave: (el) => { 85 | unsetAnimationDuration(el) 86 | elListeners['after-leave'](el) 87 | }, 88 | enterCancelled: (el) => { 89 | unsetAnimationDuration(el) 90 | elListeners['enter-cancelled'](el) 91 | }, 92 | leaveCancelled: (el) => { 93 | unsetAnimationDuration(el) 94 | elListeners['leave-cancelled'](el) 95 | } 96 | } 97 | } 98 | 99 | return createEl(elName, elData, children) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'animate.css/animate.css' 2 | import AnimatedGeneric from './AnimatedGeneric' 3 | 4 | let animations = [ 5 | 'bounce', 6 | 'flash', 7 | 'pulse', 8 | 'rubber-band', 9 | 'shake', 10 | 'head-shake', 11 | 'swing', 12 | 'tada', 13 | 'wobble', 14 | 'jello', 15 | 'heart-beat', 16 | 'hinge', 17 | 'flip' 18 | ] 19 | 20 | function componentName(input) { 21 | return `animated-${input}` 22 | .match(/[a-z]+/gi) 23 | .map(w => w.charAt(0).toUpperCase() + w.substr(1).toLowerCase()) 24 | .join('') 25 | } 26 | 27 | export { 28 | AnimatedGeneric 29 | } 30 | 31 | export default function install(Vue, opts = {}) { 32 | opts = { 33 | functional: false, 34 | defaultDuration: 1000, 35 | ...opts 36 | } 37 | 38 | // Register the generic animation component 39 | Vue.component('Animated', new AnimatedGeneric(null, opts)) 40 | 41 | if (opts.functional) { 42 | if (Array.isArray(opts.functional)) { 43 | animations = opts.functional 44 | } 45 | 46 | animations.map(animation => { 47 | const name = componentName(animation) 48 | Vue.component(name, new AnimatedGeneric(animation, opts)) 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } -------------------------------------------------------------------------------- /tests/unit/AnimatedTransition.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue, mount } from '@vue/test-utils' 2 | import AnimatedGeneric from '@/AnimatedGeneric' 3 | 4 | const localVue = createLocalVue() 5 | localVue.component('animated', new AnimatedGeneric(null, { 6 | duration: 500 7 | })) 8 | 9 | describe('AnimatedGeneric class', () => { 10 | const TestComponent = { 11 | name: 'test-component', 12 | template: ` 13 | 14 | 15 | TEST 16 | 17 | 18 | ` 19 | } 20 | 21 | it("set the animation name", () => { 22 | const animationName = 'test' 23 | const TestAnimation = new AnimatedGeneric(animationName, { 24 | duration: 500 25 | }) 26 | expect(TestAnimation.name).toBe(animationName) 27 | }) 28 | 29 | it('render the default slot', () => { 30 | const wrapper = mount(TestComponent, {localVue}) 31 | expect(wrapper.text()).toBe('TEST') 32 | expect(wrapper.html()).toMatch('enter="bounce"') 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: process.env.NODE_ENV === 'production' 3 | ? '/vue-animated/' 4 | : '/', 5 | css: { 6 | extract: false 7 | } 8 | } 9 | --------------------------------------------------------------------------------