├── .babelrc ├── .gitignore ├── .npmignore ├── .storybook ├── addons.js ├── config.js └── preview-head.html ├── .vscode └── settings.json ├── README.md ├── package.json ├── rollup.config.js ├── src ├── Flipped.vue ├── Flipper.vue └── index.js ├── stories ├── Accordion.vue ├── Android.vue ├── Double.vue ├── IconsDetail.vue ├── IconsHome.vue ├── IconsRouterWrap.vue ├── List.vue ├── Material.vue ├── Scale.vue ├── Simple.vue ├── icon-data.js └── index.stories.js ├── storybook-assets ├── background.jpg ├── flow.woff └── topography.svg └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/env", { "modules": false }]] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | storybook-static/ 3 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dist 2 | .babelrc 3 | .storybook 4 | src 5 | stories 6 | storybook-assets 7 | rollup.config.js -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import "@storybook/addon-actions/register"; 2 | import "@storybook/addon-links/register"; 3 | import "@storybook/addon-backgrounds/register"; 4 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from "@storybook/vue"; 2 | 3 | // automatically import all files ending in *.stories.js 4 | const req = require.context("../stories", true, /\.stories\.js$/); 5 | function loadStories() { 6 | req.keys().forEach(filename => req(filename)); 7 | } 8 | 9 | configure(loadStories, module); 10 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## vue-flip-toolkit 2 | 3 | [![Vue Flip Toolkit Version Badge](https://img.shields.io/npm/v/vue-flip-toolkit.svg?style=for-the-badge&color=ff4f66)](https://www.npmjs.com/package/vue-flip-toolkit) 4 | 5 | A Vue.js port of the wonderful [`react-flip-toolkit`](https://github.com/aholachek/react-flip-toolkit), developed by [@aholachek](https://github.com/aholachek) (to whom all credit is due here) 6 | 7 | ## Quick Start 8 | 9 | ```bash 10 | yarn add vue-flip-toolkit 11 | ``` 12 | 13 | Wrap the components you wish to animate with a _single_ `Flipper` component that has a `flipKey` prop. This prop must change every time you want an animation to happen. 14 | 15 | Wrap elements that should be animated with `Flipped` components that have a `flipId` prop matching them across renders. 16 | 17 | A basic example can be found here: https://codesandbox.io/s/m354w1mmp9 18 | 19 | ## Why even port this to Vue.js? 20 | 21 | Fair question. In developing my own library, [`vue-overdrive`](https://github.com/mattrothenberg/vue-overdrive), I've felt the pain of not being able to find a declarative library for animating a given DOM element between two states. Upon discovering `react-flip-toolkit`, which has a first-class "core" API that can be used outside of React, I wanted to take a crack at using it to re-implement `vue-overdrive`. The fruit of my attempt is the following library, `vue-flip-toolkit`. 22 | 23 | ## What's in this library? 24 | 25 | This library strives to imitate its parent, `react-flip-toolkit`, as closely as possible. It thus exports the following two components that you can use in your Vue applications. For the sake of brevity, I've lifted descriptions/verbiage from the README of `react-flip-toolkit`, indicated via blockquotes. 26 | 27 | ### Flipper.vue 28 | 29 | > The parent wrapper component that contains all the elements to be animated. You'll most typically need only one of these per page. [Read more –>](https://github.com/aholachek/react-flip-toolkit/blob/master/README.md#basic-props) 30 | 31 | **Props** 32 | 33 | | prop | default | type | details | 34 | | ------------------------ | ------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 35 | | `className` | – | `string` | A class that will apply to the div rendered by `Flipper` | 36 | | `flipKey` **(required)** | – | `string, number, boolean` | Changing this tells `vue-flip-toolkit` to transition child elements wrapped in Flipped components. | 37 | | `spring` | `"noWobble"` | `string, object` | Provide a string or referencing one of the spring presets — `noWobble`, `veryGentle`, `gentle`, `wobbly`, or `stiff`. Otherwise, pass a [custom spring object](https://codepen.io/aholachek/full/bKmZbV/) | 38 | | `staggerConfig` | `{}` | `object` | Provide configuration for staggered Flipped children. | 39 | 40 | ### Flipped.vue 41 | 42 | > Wraps an element that should be animated. 43 | 44 | **Props** 45 | 46 | | prop | default | type | details | 47 | | ----------------------- | ------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 48 | | `flipId` **(required)** | – | `string` | Use this to tell `vue-flip-toolkit` how elements should be matched across renders so they can be animated. | 49 | | `inverseFlipId` | – | `string` | Refer to the id of the parent `Flipped` container whose transform you want to cancel out. If this prop is provided, the `Flipped` component will become a limited version of itself that is only responsible for cancelling out its parent transform. It will read from any provided transform props and will ignore all other props (besides `inverseFlipId`.) | 50 | | `stagger` | – | `string` | Provide a natural, spring-based staggering effect in which the spring easing of each item is pinned to the previous one's movement. If you want to get more granular, you can provide a string key and the element will be staggered with other elements with the same key. | 51 | | `delayUntil` | – | `string` | Delay an animation by providing a reference to another Flipped component that it should wait for before animating (the other Flipped component should have a stagger delay as that is the only use case in which this prop is necessary.) | 52 | | `shouldInvert` | – | `function` | A function provided with the current and previous `decisionData` props passed down by the Flipper component. Returns a boolean indicating whether to apply inverted transforms to all `Flipped` children that request it via an `inverseFlipId`. | 53 | | `shouldFlip` | – | `function` | A function provided with the current and previous decisionData props passed down by the `Flipper` component. Returns a `boolean` to indicate whether a `Flipped` component should animate at that particular moment or not. | 54 | | `opacity` | false | `boolean` | | 55 | | `scale` | false | `boolean` | Tween `scaleX` and `scaleY` | 56 | | `translate` | false | `boolean` | Tween `translateX` and `translateY` | 57 | 58 | **Events** 59 | 60 | | eventName | args | details | 61 | | ------------ | ------------------------------ | -------------------------------------------- | 62 | | @on-start | `{el: DOMElement, id: String}` | Emitted when the `flipped` animation begins. | 63 | | @on-complete | `{el: DOMElement, id: String}` | Emitted when the `flipped` animation begins. | 64 | 65 | ## Cool, so how do I use it? 66 | 67 | Install the library 68 | 69 | ```bash 70 | yarn add vue-flip-toolkit 71 | ``` 72 | 73 | Import the respective components. 74 | 75 | ```js 76 | import { Flipper, Flipped } from "vue-flip-toolkit"; 77 | ``` 78 | 79 | Register the components. 80 | 81 | ```vue 82 | // Example.vue 83 | 91 | ``` 92 | 93 | ## OK, time for some examples. 94 | 95 | You got it. 96 | 97 | ### 1) Simple, Expanding Div Animation 98 | 99 | [Source](stories/Simple.vue) 100 | 101 | 102 | 103 | ### 2) Two Divs 104 | 105 | [Source](stories/Double.vue) 106 | 107 | 108 | 109 | ### 3) List Shuffle Animation 110 | 111 | [Source](stories/List.vue) 112 | 113 | 114 | 115 | ### 4) List Shuffle Animation (Staggered) 116 | 117 | [Source](stories/List.vue) 118 | 119 | 120 | 121 | 122 | ### 5) Accordion (Staggered) 123 | 124 | [Source](stories/Accordion.vue) 125 | 126 | 127 | 128 | 129 | ### 6) Scale Animation + Anime.js 130 | 131 | [Source](stories/Scale.vue) 132 | 133 | 134 | 135 | ### 7) Material Design inspired animation 136 | 137 | [Source](stories/Material.vue) 138 | 139 | 140 | 141 | ### 8) Vue Router Example 142 | 143 | This example is very much a WIP. Nonetheless, it illustrates at a high-level how to use `vue-flip-toolkit` with `vue-router`, as well as hook into the `@on-complete` and `@on-start` events. 144 | 145 | [Source](stories/IconsHome.vue) 146 | 147 | 148 | 149 | ## What's next? 150 | 151 | A lot. 152 | 153 | Primarily, I want to make sure that this basic set of functionality works for Vue developers, and that the API makes sense. 154 | 155 | Once we check that box, I'd like to add support for additional props for both `Flipped` and `Flipper`. Right now, you can't do _too too_ much with them. 156 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-flip-toolkit", 3 | "version": "1.6.0", 4 | "description": "A Vue.js port of the inimitable react-flip-toolkit", 5 | "main": "dist/vue-flip-toolkit.umd.js", 6 | "repository": "https://github.com/mattrothenberg/vue-flip-toolkit", 7 | "author": "Matt Rothenberg", 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "rollup -c rollup.config.js", 11 | "storybook": "start-storybook -p 6006 -s ./storybook-assets", 12 | "build-storybook": "build-storybook -s ./storybook-assets", 13 | "prepublishOnly": "npm run build" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.6.0", 17 | "@babel/preset-env": "^7.6.0", 18 | "@storybook/addon-actions": "^5.0.6", 19 | "@storybook/addon-backgrounds": "^5.0.6", 20 | "@storybook/addon-links": "^5.0.6", 21 | "@storybook/addons": "^5.0.6", 22 | "@storybook/vue": "^5.0.6", 23 | "animejs": "^3.0.1", 24 | "babel-loader": "^8.0.5", 25 | "babel-preset-vue": "^2.0.2", 26 | "rollup": "^1.9.3", 27 | "rollup-plugin-babel": "^4.3.3", 28 | "rollup-plugin-commonjs": "^9.3.4", 29 | "rollup-plugin-node-resolve": "^4.2.2", 30 | "rollup-plugin-vue": "^4.7.2", 31 | "storybook-vue-router": "^1.0.3", 32 | "vue": "^2.6.10", 33 | "vue-loader": "^15.7.0", 34 | "vue-router": "^3.0.3", 35 | "vue-template-compiler": "^2.6.10" 36 | }, 37 | "dependencies": { 38 | "flip-toolkit": "^7.0.7" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 | import babel from "rollup-plugin-babel"; 5 | 6 | export default { 7 | input: "src/index.js", 8 | plugins: [ 9 | commonjs(), 10 | VuePlugin(), 11 | resolve(), 12 | babel({ exclude: "node_modules/**" }) 13 | ], 14 | output: [ 15 | { 16 | file: "dist/vue-flip-toolkit.umd.js", 17 | exports: "named", 18 | format: "umd", 19 | name: "VueFlipToolkit" 20 | }, 21 | { 22 | file: "dist/vue-flip-toolkit.esm.js", 23 | format: "esm" 24 | }, 25 | { 26 | file: "dist/vue-flip-toolkit.min.js", 27 | format: "iife", 28 | exports: "named", 29 | name: "VueFlipToolkit" 30 | } 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /src/Flipped.vue: -------------------------------------------------------------------------------- 1 | 55 | -------------------------------------------------------------------------------- /src/Flipper.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 99 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Flipped from "./Flipped.vue"; 2 | import Flipper from "./Flipper.vue"; 3 | 4 | export default { Flipped, Flipper }; 5 | export { default as Flipped } from "./Flipped.vue"; 6 | export { default as Flipper } from "./Flipper.vue"; 7 | -------------------------------------------------------------------------------- /stories/Accordion.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 96 | 97 | -------------------------------------------------------------------------------- /stories/Android.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 115 | 116 | 181 | -------------------------------------------------------------------------------- /stories/Double.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 48 | 49 | 73 | -------------------------------------------------------------------------------- /stories/IconsDetail.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 111 | 112 | 145 | -------------------------------------------------------------------------------- /stories/IconsHome.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 50 | 51 | 81 | -------------------------------------------------------------------------------- /stories/IconsRouterWrap.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | -------------------------------------------------------------------------------- /stories/List.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 53 | -------------------------------------------------------------------------------- /stories/Material.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 104 | 105 | 106 | 130 | -------------------------------------------------------------------------------- /stories/Scale.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 89 | 90 | 131 | -------------------------------------------------------------------------------- /stories/Simple.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 45 | 46 | 65 | -------------------------------------------------------------------------------- /stories/icon-data.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | slug: "sports", 4 | label: "Sports Icons", 5 | icons: [ 6 | { 7 | flipped: true, 8 | key: "a" 9 | }, 10 | { 11 | flipped: false, 12 | key: "b" 13 | }, 14 | { 15 | flipped: true, 16 | key: "c" 17 | }, 18 | { 19 | flipped: false, 20 | key: "d" 21 | }, 22 | { 23 | flipped: true, 24 | key: "e" 25 | }, 26 | { 27 | flipped: false, 28 | key: "f" 29 | }, 30 | { 31 | flipped: false, 32 | key: "g" 33 | }, 34 | { 35 | flipped: false, 36 | key: "h" 37 | }, 38 | { 39 | flipped: true, 40 | key: "i" 41 | }, 42 | { 43 | flipped: false, 44 | key: "j" 45 | } 46 | ], 47 | count: 24 48 | }, 49 | { 50 | slug: "science", 51 | label: "Science Icons", 52 | icons: [ 53 | { 54 | flipped: false, 55 | key: "a" 56 | }, 57 | { 58 | flipped: false, 59 | key: "b" 60 | }, 61 | { 62 | flipped: true, 63 | key: "c" 64 | }, 65 | { 66 | flipped: false, 67 | key: "d" 68 | }, 69 | { 70 | flipped: false, 71 | key: "e" 72 | }, 73 | { 74 | flipped: true, 75 | key: "f" 76 | }, 77 | { 78 | flipped: false, 79 | key: "g" 80 | }, 81 | { 82 | flipped: false, 83 | key: "h" 84 | }, 85 | { 86 | flipped: true, 87 | key: "i" 88 | }, 89 | { 90 | flipped: false, 91 | key: "j" 92 | } 93 | ], 94 | count: 24 95 | } 96 | ]; 97 | -------------------------------------------------------------------------------- /stories/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/vue"; 2 | import { action } from "@storybook/addon-actions"; 3 | import { linkTo } from "@storybook/addon-links"; 4 | import StoryRouter from "storybook-vue-router"; 5 | 6 | import Simple from "./Simple.vue"; 7 | import Double from "./Double.vue"; 8 | import List from "./List.vue"; 9 | import Accordion from "./Accordion.vue"; 10 | import Scale from "./Scale.vue"; 11 | import Material from "./Material.vue"; 12 | import Android from "./Android.vue"; 13 | 14 | import IconsHome from "./IconsHome.vue"; 15 | import IconsDetail from "./IconsDetail.vue"; 16 | import IconsRouterWrap from "./IconsRouterWrap.vue"; 17 | 18 | storiesOf("Routing", module) 19 | .addParameters({ 20 | backgrounds: [{ name: "dark", value: "#e6e6e6", default: true }] 21 | }) 22 | .addDecorator( 23 | StoryRouter( 24 | {}, 25 | { 26 | initialEntry: "/", 27 | routes: [ 28 | { path: "/", component: IconsHome }, 29 | { path: "/icons/:set", component: IconsDetail } 30 | ] 31 | } 32 | ) 33 | ) 34 | .add("Example", () => IconsRouterWrap); 35 | 36 | storiesOf("Examples", module) 37 | .addParameters({ 38 | backgrounds: [{ name: "grey", value: "#e6e6e6" }] 39 | }) 40 | .add("Expanding Div", () => { 41 | return { 42 | components: { 43 | Simple 44 | }, 45 | template: `` 46 | }; 47 | }) 48 | .add("Two Divs", () => { 49 | return { 50 | components: { 51 | Double 52 | }, 53 | template: `` 54 | }; 55 | }) 56 | .add("List Shuffle", () => { 57 | return { 58 | components: { 59 | List 60 | }, 61 | template: `` 62 | }; 63 | }) 64 | .add("List Shuffle (Stagger)", () => { 65 | return { 66 | components: { 67 | List 68 | }, 69 | template: `` 70 | }; 71 | }) 72 | .add("Scale", () => { 73 | return { 74 | components: { 75 | Scale 76 | }, 77 | template: `` 78 | }; 79 | }) 80 | .add("Accordion", () => { 81 | return { 82 | components: { 83 | Accordion 84 | }, 85 | template: `` 86 | }; 87 | }) 88 | .add("Material Design", () => { 89 | return { 90 | components: { 91 | Material 92 | }, 93 | template: `` 94 | }; 95 | }) 96 | .add("Android Home Screen", () => { 97 | return { 98 | components: { 99 | Android 100 | }, 101 | template: `` 102 | }; 103 | }); 104 | -------------------------------------------------------------------------------- /storybook-assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattrothenberg/vue-flip-toolkit/e70b0f515c1e7ebf1b2cb4cab577e86afd424c93/storybook-assets/background.jpg -------------------------------------------------------------------------------- /storybook-assets/flow.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattrothenberg/vue-flip-toolkit/e70b0f515c1e7ebf1b2cb4cab577e86afd424c93/storybook-assets/flow.woff -------------------------------------------------------------------------------- /storybook-assets/topography.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------