├── vue.config.js ├── demo ├── vue.config.js ├── after.jpg ├── before.jpg ├── babel.config.js ├── index.js ├── DemoApp.vue └── styles.css ├── .gitignore ├── src ├── index.js └── TwentyTwenty.vue ├── .github └── workflows │ └── npm-publish.yml ├── LICENSE ├── index.html ├── package.json └── README.md /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transpileDependencies: [], 3 | } -------------------------------------------------------------------------------- /demo/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transpileDependencies: [], 3 | } -------------------------------------------------------------------------------- /demo/after.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhayes/vue-twentytwenty/HEAD/demo/after.jpg -------------------------------------------------------------------------------- /demo/before.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhayes/vue-twentytwenty/HEAD/demo/before.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | /node_modules/* 3 | npm-debug.log 4 | /dist/* 5 | yarn-error.log 6 | -------------------------------------------------------------------------------- /demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import DemoApp from './DemoApp.vue' 3 | 4 | createApp(DemoApp).mount('#app') -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import TwentyTwenty from './TwentyTwenty.vue'; 3 | 4 | Vue.component('TwentyTwenty', TwentyTwenty); 5 | 6 | export default TwentyTwenty; 7 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '*' 5 | 6 | jobs: 7 | publish-tag: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: 16 14 | - run: yarn install 15 | - run: yarn build 16 | - uses: JS-DevTools/npm-publish@v1 17 | with: 18 | token: ${{ secrets.NPM_TOKEN }} 19 | -------------------------------------------------------------------------------- /demo/DemoApp.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mark Hayes 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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |

vue-twentytwenty

15 |

A nifty image comparison utility.

16 |
17 |
18 |
19 |
20 |
21 | 27 |
28 |

tl;dr — A picture is worth a thousand words

29 |
30 |
31 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-twentytwenty", 3 | "version": "1.0.0-1", 4 | "author": "Mark Hayes ", 5 | "scripts": { 6 | "serve": "vue-cli-service serve demo/index.js", 7 | "build": "vue-cli-service build --target lib --name vue-twentytwenty ./src/index.js", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "main": "./dist/vue-twentytwenty.common.js", 11 | "files": [ 12 | "dist/*", 13 | "src/*", 14 | "public/*", 15 | "*.json" 16 | ], 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "~5.0.0-rc.1", 19 | "@vue/cli-plugin-eslint": "~5.0.0-rc.1", 20 | "@vue/cli-service": "^5.0.0-rc.1", 21 | "@vue/compiler-sfc": "^3.2.24", 22 | "babel-eslint": "^10.1.0", 23 | "eslint": "^7.32.0", 24 | "eslint-plugin-vue": "^8.0.3", 25 | "vue": "^3.2.24" 26 | }, 27 | "eslintConfig": { 28 | "root": true, 29 | "env": { 30 | "node": true 31 | }, 32 | "extends": [ 33 | "plugin:vue/essential", 34 | "eslint:recommended" 35 | ], 36 | "parserOptions": { 37 | "parser": "babel-eslint" 38 | }, 39 | "rules": {} 40 | }, 41 | "browserslist": [ 42 | "> 1%", 43 | "last 2 versions", 44 | "not dead" 45 | ], 46 | "contributors": [ 47 | "Mattia Astorino (https://equinsuocha.io)", 48 | "Michele Riva (https://micheleriva.it)", 49 | "Estee Tey (https://esteeytey.dev)" 50 | ], 51 | "license": "MIT", 52 | "repository": { 53 | "type": "git", 54 | "url": "https://github.com/mhayes/vue-twentytwenty" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TwentyTwenty 2 | 3 | [![npm](https://img.shields.io/npm/v/vue-twentytwenty.svg) 4 | ![npm](https://img.shields.io/npm/dm/vue-twentytwenty.svg)](https://www.npmjs.com/package/vue-twentytwenty) 5 | [![vue3](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://vuejs.org/) 6 | 7 | A small component to quickly let users see the differences between 2 images. Based on the work I did for ZURB's [TwentyTwenty plugin](http://zurb.com/playground/twentytwenty). 8 | 9 | Live Demo 10 | 11 | Looking for Vue 2 support? Use the [v2](https://github.com/mhayes/vue-twentytwenty/tree/v2) branch. 12 | 13 | ## Installation 14 | 15 | ``` 16 | $ npm install vue-twentytwenty --save 17 | ``` 18 | 19 | Or download the latest release using: 20 | 21 | * https://unpkg.com/vue-twentytwenty/dist/vue-twentytwenty.umd.min.js 22 | * https://unpkg.com/vue-twentytwenty/dist/vue-twentytwenty.css 23 | 24 | ## Usage 25 | 26 | `TwentyTwenty` is a UMD module, which can be used as a module in both CommonJS and AMD modular environments. When in a non-modular environment, `TwentyTwenty` will be registered as a global variable. 27 | 28 | ### ES6 29 | 30 | ```js 31 | import 'vue-twentytwenty/dist/vue-twentytwenty.css'; 32 | import TwentyTwenty from 'vue-twentytwenty'; 33 | 34 | export default { 35 | // ... 36 | components: { 37 | TwentyTwenty 38 | } 39 | // ... 40 | }; 41 | ``` 42 | 43 | It can then be used like so: 44 | 45 | ```html 46 | 49 | ``` 50 | 51 | ### CommonJS 52 | 53 | ```js 54 | var Vue = require('vue') 55 | var TwentyTwenty = require('vue-twentytwenty') 56 | 57 | var YourComponent = Vue.extend({ 58 | // ... 59 | components: { 60 | 'twentytwenty': TwentyTwenty 61 | }, 62 | // ... 63 | }) 64 | ``` 65 | 66 | ### Browser 67 | 68 | ```html 69 | 70 | 71 | 72 | 73 | 74 | 75 |
76 | 79 |
80 | 81 | 82 | 87 | 88 | 89 | ``` 90 | 91 | ## Props 92 | | Props | Description | Required | Type | Default | 93 | | ------------ | ----------------------------------------------------------------------- | -------- | ------ | ------- | 94 | | before | URL of before image | true | String | - | 95 | | beforeLabel | When hovering over image what label should show up over before image | false | String | - | 96 | | after | URL of after image | true | String | - | 97 | | afterLabel | When hovering over image what label should show up over after image | false | String | - | 98 | | offset | How far from the left the slider should be on load (between 0 and 1) | false | Number | 0.5 | 99 | | keyboardStep | How far the slider should be moved on arrow key press (between 0 and 1) | false | Number | 0.2 | 100 | 101 | ## Usage 102 | 103 | ### Simple 104 | 105 | ```vue 106 | 111 | 112 | 122 | ``` 123 | 124 | ### Advanced 125 | 126 | ```vue 127 | 135 | 136 | 146 | ``` 147 | 148 | ### Publish 149 | 150 | From a clean repository (no pending changes) run the following: 151 | 152 | ``` 153 | yarn version --patch 154 | git push --follow-tags 155 | ``` 156 | -------------------------------------------------------------------------------- /demo/styles.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}body{background-color:transparent;color:#555;font-family:'Roboto',sans-serif;max-width:40.625em;margin:0 auto;padding:1.25em;font-weight:300;line-height:1.6;font-size:1.1em}a{text-decoration:none;color:#428bca}a:hover{color:rgba(66,139,202,0.85)}footer{text-align:center}p{white-space:normal;word-wrap:break-word}code{font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace}h1,h2,h3,h4,h5,h6{line-height:1.1}.dark-links a,.header .name a,.header .menu a,.social-icons a{color:#555;font-weight:300;text-decoration:none}.dark-links a:hover,.header .name a:hover,.header .menu a:hover,.social-icons a:hover{color:rgba(85,85,85,0.8)}.dark-links a:visited,.header .name a:visited,.header .menu a:visited,.social-icons a:visited{color:#555}.inline-code code,.post blockquote code,.post li code,.post p code{background-color:rgba(0,0,0,0.04);border-radius:.18rem;color:#555;font-size:85%;padding:.2em 0;margin:0}.inline-code code:before,.post blockquote code:before,.post li code:before,.post p code:before{letter-spacing:-.2em;content:" "}.inline-code code:after,.post blockquote code:after,.post li code:after,.post p code:after{letter-spacing:-.2em;content:" "}.header{text-align:center}.header .name{display:block;text-transform:lowercase;font-size:2.4em}.header .menu{list-style:none;padding-left:0;margin-bottom:3em}.header .menu li{display:inline;margin-right:0.8em}.header .menu li:last-child{margin-right:0}.posts-list{padding-left:0;line-height:1.4;list-style:none}.posts-list li{padding:.315em;border-bottom:1px solid #ebebeb}.posts-list li:last-child{margin-bottom:2em}.posts-list li span{color:#636363;float:right;font-size:.8em;margin-left:1.4em}.social-icons{padding-left:0;list-style:none;font-size:1.6em}.social-icons li{display:inline;margin-right:0.4em}.social-icons li:last-child{margin-right:0}.active{text-decoration:underline}.active a{color:rgba(85,85,85,0.8) !important}.date{color:#ccc;text-transform:uppercase}.gravatar{margin:.3em 1.2em 1em 0;border-radius:0.3rem}.post img{display:block;background:transparent;max-width:100%;height:auto;margin:0 auto;border:.0625em solid #ebebeb;padding:0.1875em}.post blockquote{border-left:.25em solid #ebebeb;padding:0 1em;margin:1em 0;color:#999}.post .video{display:block;background:transparent;max-width:100%;height:auto;width:16em;height:9em;margin:0 auto;border:.0625em solid #ebebeb;padding:0.1875em}.center-element{display:block;text-align:center;margin:0 auto}.highlight{margin:1em 0}@media (min-width: 48em){.header .name{font-size:3em}.post .video{width:26.625em;height:15em}}@media (min-width: 64em){.header .name{font-size:3.6em}.post .video{width:40em;height:22.5em}h1{font-size:2.6em}h2{font-size:2em}h3{font-size:1.5em}h4{font-size:1.125em}h5{font-size:0.85em}h6{font-size:0.64em}}.highlight{background-color:rgba(0,0,0,0.04);border-radius:.18rem;color:#555;padding:.4em .6em;margin:1.1em 0;overflow:hidden}.highlight .c{color:#586E75}.highlight .err{color:#93A1A1}.highlight .g{color:#93A1A1}.highlight .k{color:#859900}.highlight .l{color:#93A1A1}.highlight .n{color:#93A1A1}.highlight .o{color:#859900}.highlight .x{color:#CB4B16}.highlight .p{color:#93A1A1}.highlight .cm{color:#586E75}.highlight .cp{color:#859900}.highlight .c1{color:#586E75}.highlight .cs{color:#859900}.highlight .gd{color:#2AA198}.highlight .ge{color:#93A1A1;font-style:italic}.highlight .gr{color:#DC322F}.highlight .gh{color:#CB4B16}.highlight .gi{color:#859900}.highlight .go{color:#93A1A1}.highlight .gp{color:#93A1A1}.highlight .gs{color:#93A1A1;font-weight:700}.highlight .gu{color:#CB4B16}.highlight .gt{color:#93A1A1}.highlight .kc{color:#CB4B16}.highlight .kd{color:#268BD2}.highlight .kn{color:#859900}.highlight .kp{color:#859900}.highlight .kr{color:#268BD2}.highlight .kt{color:#DC322F}.highlight .ld{color:#93A1A1}.highlight .m{color:#2AA198}.highlight .s{color:#2AA198}.highlight .na{color:#93A1A1}.highlight .nb{color:#B58900}.highlight .nc{color:#268BD2}.highlight .no{color:#CB4B16}.highlight .nd{color:#268BD2}.highlight .ni{color:#CB4B16}.highlight .ne{color:#CB4B16}.highlight .nf{color:#268BD2}.highlight .nl{color:#93A1A1}.highlight .nn{color:#93A1A1}.highlight .nx{color:#555}.highlight .py{color:#93A1A1}.highlight .nt{color:#268BD2}.highlight .nv{color:#268BD2}.highlight .ow{color:#859900}.highlight .w{color:#93A1A1}.highlight .mf{color:#2AA198}.highlight .mh{color:#2AA198}.highlight .mi{color:#2AA198}.highlight .mo{color:#2AA198}.highlight .sb{color:#586E75}.highlight .sc{color:#2AA198}.highlight .sd{color:#93A1A1}.highlight .s2{color:#2AA198}.highlight .se{color:#CB4B16}.highlight .sh{color:#93A1A1}.highlight .si{color:#2AA198}.highlight .sx{color:#2AA198}.highlight .sr{color:#DC322F}.highlight .s1{color:#2AA198}.highlight .ss{color:#2AA198}.highlight .bp{color:#268BD2}.highlight .vc{color:#268BD2}.highlight .vg{color:#268BD2}.highlight .vi{color:#268BD2}.highlight .il{color:#2AA198}.carousel{clear:both}.carousel img{display:inline-block;width:32.333%}h1{margin-bottom:0}.subheader{font-size:1.1em;font-weight:100;color:#ccc}span.draft{background:#ccc;border-radius:3px;padding:3px 5px} 2 | -------------------------------------------------------------------------------- /src/TwentyTwenty.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 153 | 154 | 250 | --------------------------------------------------------------------------------