├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── main.esm.js ├── main.js ├── main.umd.min.js └── main.umd.min.js.map ├── package-lock.json ├── package.json ├── src ├── Textra.vue └── main.js └── test ├── .setup.js └── test.js /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | yarn-error.log 5 | 6 | # Editor directories and files 7 | .idea 8 | *.suo 9 | *.ntvs* 10 | *.njsproj 11 | *.sln 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | node_js: 6 | - "10" 7 | services: 8 | - xvfb 9 | before_script: 10 | - npm install -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Hosein Barzegaran 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Textra [![Build Status](https://travis-ci.org/hosein2398/Textra.svg?branch=master)](https://travis-ci.org/hosein2398/Textra) 3 | 4 | > A Vue.js add-on to slide your text. [demo](https://hosein2398.github.io/textra/) 5 | 6 | ## Installing 7 | ``` 8 | npm i -D vue-textra 9 | ``` 10 | Then inside your project, start using it: 11 | ```js 12 | import Vue from 'vue' 13 | import App from './App.vue' 14 | import Textra from 'vue-textra' 15 | 16 | Vue.use(Textra); 17 | new Vue({ 18 | el: '#app', 19 | render: h => h(App) 20 | }) 21 | ``` 22 | To use inside browser: 23 | ```html 24 | 25 | ``` 26 | Then: 27 | ```js 28 | Vue.use(Textra); 29 | ``` 30 | ## Usage 31 | Inside any of your components: 32 | ```html 33 | 34 | ``` 35 | And in your instance data: 36 | ```js 37 | //... 38 | data () { 39 | return { 40 | words: ["My text to show", "Great news here!", "Vue is great", "Sample Text"] 41 | } 42 | } 43 | //... 44 | ``` 45 | 46 | Another example : 47 | ```html 48 | 49 | ``` 50 | This one will loop around for ever. 51 | ## Props 52 | #### `data` 53 | Type : Array 54 | Description : Should be array of things you want to slide. 55 | 56 | --- 57 | 58 | #### `timer` 59 | Type : Number 60 | Description : Defines gap between each slide as second. 61 | Default : `2` 62 | 63 | --- 64 | 65 | #### `filter` 66 | Type : String 67 | Description : Defines type of filter you want to use when sliding. 68 | Default : `simple` 69 | 70 | --- 71 | 72 | #### `infinite` 73 | Type : Boolean 74 | Description : Defines whether your slider should keep looping or not. 75 | Default : `false` 76 | 77 | 78 | ## Filters 79 | There are 9 types of filters available for now: 80 | + `simple` 81 | + `bottom-top` 82 | + `top-bottom` 83 | + `right-left` 84 | + `left-right` 85 | + `press` 86 | + `scale` 87 | + `flash` 88 | + `flip` 89 | -------------------------------------------------------------------------------- /dist/main.esm.js: -------------------------------------------------------------------------------- 1 | import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js'; 2 | import __vue_create_injector__ from 'vue-runtime-helpers/dist/inject-style/browser.js'; 3 | 4 | // 5 | // 6 | // 7 | // 8 | // 9 | // 10 | var script = { 11 | name: "Textra", 12 | props: { 13 | data: { 14 | type: Array, 15 | required: true 16 | }, 17 | filter: { 18 | type: String, 19 | "default": "simple" 20 | }, 21 | timer: { 22 | type: Number, 23 | "default": 2 24 | }, 25 | infinite: { 26 | type: Boolean, 27 | "default": false 28 | } 29 | }, 30 | data: function data() { 31 | return { 32 | defaultStyle: "transition: all 0.5s;", 33 | currentWord: this.data[0], 34 | liStl: null, 35 | dataCounter: 0, 36 | animationID: null, 37 | filters: { 38 | simple: ["opacity:0", "opacity:1"], 39 | "bottom-top": ["transform:translateY(10px);opacity:0;", "transform:translateY(0px);opacity:1;"], 40 | "top-bottom": ["transform:translateY(-10px);opacity:0;", "transform:translateY(0px);opacity:1;"], 41 | "right-left": ["transform:translateX(10px);opacity:0;", "transform:translateX(0px);opacity:1;"], 42 | "left-right": ["transform:translateX(-10px);opacity:0;", "transform:translateX(0px);opacity:1;"], 43 | press: ["letter-spacing: 4px;opacity:0;", "opacity:1;"], 44 | scale: ["transform:scaleY(1.4);opacity:0;", "opacity:1;"], 45 | flash: ["transform:skewX(-70deg);opacity:0;", "transform:skewX(0deg);opacity:1;"], 46 | flip: ["transform:rotateX(-180deg);opacity:0;", "transform:rotate(0deg);opacity:1;"] 47 | } 48 | }; 49 | }, 50 | computed: { 51 | mainStyleComputed: function mainStyleComputed() { 52 | return this.defaultStyle + this.liStl; 53 | } 54 | }, 55 | created: function created() { 56 | var previousTime = 0; 57 | var that = this; 58 | 59 | function run(currentTime) { 60 | if (previousTime + this.timer * 1000 < currentTime) { 61 | //hiding 62 | this.liStl = this.filters[this.filter][0]; 63 | } 64 | 65 | if (previousTime + this.timer * 1000 + 1000 < currentTime) { 66 | //showing 67 | previousTime = currentTime; 68 | this.currentWord = this.data[++this.dataCounter]; 69 | this.liStl = this.filters[this.filter][1]; 70 | 71 | if (this.dataCounter === this.data.length) { 72 | if (this.infinite) { 73 | this.dataCounter = 0; 74 | this.currentWord = this.data[this.dataCounter]; 75 | } else { 76 | window.cancelAnimationFrame(this.animationID); 77 | return; 78 | } 79 | } 80 | } 81 | 82 | this.animationID = window.requestAnimationFrame(run.bind(that)); 83 | } 84 | 85 | this.animationID = window.requestAnimationFrame(run.bind(that)); 86 | }, 87 | beforeDestroy: function beforeDestroy() { 88 | window.cancelAnimationFrame(this.animationID); 89 | } 90 | }; 91 | 92 | /* script */ 93 | var __vue_script__ = script; 94 | /* template */ 95 | 96 | var __vue_render__ = function __vue_render__() { 97 | var _vm = this; 98 | 99 | var _h = _vm.$createElement; 100 | 101 | var _c = _vm._self._c || _h; 102 | 103 | return _c('div', { 104 | staticClass: "textra" 105 | }, [_c('div', { 106 | staticClass: "mainTextra", 107 | style: _vm.mainStyleComputed, 108 | domProps: { 109 | "innerHTML": _vm._s(_vm.currentWord) 110 | } 111 | })]); 112 | }; 113 | 114 | var __vue_staticRenderFns__ = []; 115 | /* style */ 116 | 117 | var __vue_inject_styles__ = function __vue_inject_styles__(inject) { 118 | if (!inject) return; 119 | inject("data-v-d9d0826c_0", { 120 | source: ".textra[data-v-d9d0826c]{height:auto;width:auto;display:block}", 121 | map: undefined, 122 | media: undefined 123 | }); 124 | }; 125 | /* scoped */ 126 | 127 | 128 | var __vue_scope_id__ = "data-v-d9d0826c"; 129 | /* module identifier */ 130 | 131 | var __vue_module_identifier__ = undefined; 132 | /* functional template */ 133 | 134 | var __vue_is_functional_template__ = false; 135 | /* style inject SSR */ 136 | 137 | var TextraPlugin = __vue_normalize__({ 138 | render: __vue_render__, 139 | staticRenderFns: __vue_staticRenderFns__ 140 | }, __vue_inject_styles__, __vue_script__, __vue_scope_id__, __vue_is_functional_template__, __vue_module_identifier__, __vue_create_injector__, undefined); 141 | 142 | var Textra = { 143 | install: function install(Vue) { 144 | Vue.component("textra", TextraPlugin); 145 | } 146 | }; 147 | 148 | export default Textra; 149 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } 4 | 5 | var __vue_normalize__ = _interopDefault(require('vue-runtime-helpers/dist/normalize-component.js')); 6 | var __vue_create_injector__ = _interopDefault(require('vue-runtime-helpers/dist/inject-style/browser.js')); 7 | 8 | // 9 | // 10 | // 11 | // 12 | // 13 | // 14 | var script = { 15 | name: "Textra", 16 | props: { 17 | data: { 18 | type: Array, 19 | required: true 20 | }, 21 | filter: { 22 | type: String, 23 | "default": "simple" 24 | }, 25 | timer: { 26 | type: Number, 27 | "default": 2 28 | }, 29 | infinite: { 30 | type: Boolean, 31 | "default": false 32 | } 33 | }, 34 | data: function data() { 35 | return { 36 | defaultStyle: "transition: all 0.5s;", 37 | currentWord: this.data[0], 38 | liStl: null, 39 | dataCounter: 0, 40 | animationID: null, 41 | filters: { 42 | simple: ["opacity:0", "opacity:1"], 43 | "bottom-top": ["transform:translateY(10px);opacity:0;", "transform:translateY(0px);opacity:1;"], 44 | "top-bottom": ["transform:translateY(-10px);opacity:0;", "transform:translateY(0px);opacity:1;"], 45 | "right-left": ["transform:translateX(10px);opacity:0;", "transform:translateX(0px);opacity:1;"], 46 | "left-right": ["transform:translateX(-10px);opacity:0;", "transform:translateX(0px);opacity:1;"], 47 | press: ["letter-spacing: 4px;opacity:0;", "opacity:1;"], 48 | scale: ["transform:scaleY(1.4);opacity:0;", "opacity:1;"], 49 | flash: ["transform:skewX(-70deg);opacity:0;", "transform:skewX(0deg);opacity:1;"], 50 | flip: ["transform:rotateX(-180deg);opacity:0;", "transform:rotate(0deg);opacity:1;"] 51 | } 52 | }; 53 | }, 54 | computed: { 55 | mainStyleComputed: function mainStyleComputed() { 56 | return this.defaultStyle + this.liStl; 57 | } 58 | }, 59 | created: function created() { 60 | var previousTime = 0; 61 | var that = this; 62 | 63 | function run(currentTime) { 64 | if (previousTime + this.timer * 1000 < currentTime) { 65 | //hiding 66 | this.liStl = this.filters[this.filter][0]; 67 | } 68 | 69 | if (previousTime + this.timer * 1000 + 1000 < currentTime) { 70 | //showing 71 | previousTime = currentTime; 72 | this.currentWord = this.data[++this.dataCounter]; 73 | this.liStl = this.filters[this.filter][1]; 74 | 75 | if (this.dataCounter === this.data.length) { 76 | if (this.infinite) { 77 | this.dataCounter = 0; 78 | this.currentWord = this.data[this.dataCounter]; 79 | } else { 80 | window.cancelAnimationFrame(this.animationID); 81 | return; 82 | } 83 | } 84 | } 85 | 86 | this.animationID = window.requestAnimationFrame(run.bind(that)); 87 | } 88 | 89 | this.animationID = window.requestAnimationFrame(run.bind(that)); 90 | }, 91 | beforeDestroy: function beforeDestroy() { 92 | window.cancelAnimationFrame(this.animationID); 93 | } 94 | }; 95 | 96 | /* script */ 97 | var __vue_script__ = script; 98 | /* template */ 99 | 100 | var __vue_render__ = function __vue_render__() { 101 | var _vm = this; 102 | 103 | var _h = _vm.$createElement; 104 | 105 | var _c = _vm._self._c || _h; 106 | 107 | return _c('div', { 108 | staticClass: "textra" 109 | }, [_c('div', { 110 | staticClass: "mainTextra", 111 | style: _vm.mainStyleComputed, 112 | domProps: { 113 | "innerHTML": _vm._s(_vm.currentWord) 114 | } 115 | })]); 116 | }; 117 | 118 | var __vue_staticRenderFns__ = []; 119 | /* style */ 120 | 121 | var __vue_inject_styles__ = function __vue_inject_styles__(inject) { 122 | if (!inject) return; 123 | inject("data-v-d9d0826c_0", { 124 | source: ".textra[data-v-d9d0826c]{height:auto;width:auto;display:block}", 125 | map: undefined, 126 | media: undefined 127 | }); 128 | }; 129 | /* scoped */ 130 | 131 | 132 | var __vue_scope_id__ = "data-v-d9d0826c"; 133 | /* module identifier */ 134 | 135 | var __vue_module_identifier__ = undefined; 136 | /* functional template */ 137 | 138 | var __vue_is_functional_template__ = false; 139 | /* style inject SSR */ 140 | 141 | var TextraPlugin = __vue_normalize__({ 142 | render: __vue_render__, 143 | staticRenderFns: __vue_staticRenderFns__ 144 | }, __vue_inject_styles__, __vue_script__, __vue_scope_id__, __vue_is_functional_template__, __vue_module_identifier__, __vue_create_injector__, undefined); 145 | 146 | var Textra = { 147 | install: function install(Vue) { 148 | Vue.component("textra", TextraPlugin); 149 | } 150 | }; 151 | 152 | module.exports = Textra; 153 | -------------------------------------------------------------------------------- /dist/main.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Textra=e()}(this,function(){"use strict";var t={name:"Textra",props:{data:{type:Array,required:!0},filter:{type:String,default:"simple"},timer:{type:Number,default:2},infinite:{type:Boolean,default:!1}},data:function(){return{defaultStyle:"transition: all 0.5s;",currentWord:this.data[0],liStl:null,dataCounter:0,animationID:null,filters:{simple:["opacity:0","opacity:1"],"bottom-top":["transform:translateY(10px);opacity:0;","transform:translateY(0px);opacity:1;"],"top-bottom":["transform:translateY(-10px);opacity:0;","transform:translateY(0px);opacity:1;"],"right-left":["transform:translateX(10px);opacity:0;","transform:translateX(0px);opacity:1;"],"left-right":["transform:translateX(-10px);opacity:0;","transform:translateX(0px);opacity:1;"],press:["letter-spacing: 4px;opacity:0;","opacity:1;"],scale:["transform:scaleY(1.4);opacity:0;","opacity:1;"],flash:["transform:skewX(-70deg);opacity:0;","transform:skewX(0deg);opacity:1;"],flip:["transform:rotateX(-180deg);opacity:0;","transform:rotate(0deg);opacity:1;"]}}},computed:{mainStyleComputed:function(){return this.defaultStyle+this.liStl}},created:function(){var t=0,e=this;this.animationID=window.requestAnimationFrame(function n(i){if(t+1e3*this.timer", 6 | "license": "MIT", 7 | "homepage": "https://github.com/hosein2398/Textra", 8 | "bugs": { 9 | "url": "https://github.com/hosein2398/Textra/issues" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/hosein2398/Textra" 14 | }, 15 | "keywords": [ 16 | "vue", 17 | "slider", 18 | "slide text", 19 | "text animation" 20 | ], 21 | "main": "dist/main.js", 22 | "module": "dist/main.esm.js", 23 | "browser": "dist/main.umd.min.js", 24 | "scripts": { 25 | "test": "./node_modules/.bin/ava test/test.js", 26 | "build": "bili ./src/main.js --format umd-min,cjs,esm --module-name Textra --plugins.vue.css false" 27 | }, 28 | "devDependencies": { 29 | "@babel/preset-stage-3": "^7.0.0", 30 | "ava": "^0.25.0", 31 | "avoriaz": "^6.3.0", 32 | "babel-loader": "^7.1.2", 33 | "bili": "^4.8.1", 34 | "browser-env": "^3.2.5", 35 | "cross-env": "^5.0.5", 36 | "css-loader": "^0.28.7", 37 | "file-loader": "^1.1.4", 38 | "require-extension-hooks": "^0.3.2", 39 | "require-extension-hooks-babel": "^0.1.1", 40 | "require-extension-hooks-vue": "^0.4.1", 41 | "rollup-plugin-vue": "^5.0.1", 42 | "vue": "^2.6.10", 43 | "vue-loader": "^13.0.5", 44 | "vue-template-compiler": "^2.6.10", 45 | "webpack": "^3.6.0", 46 | "webpack-dev-server": "^2.9.1" 47 | }, 48 | "dependencies": {}, 49 | "ava": { 50 | "require": [ 51 | "./test/.setup.js" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Textra.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 103 | 104 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import TextraPlugin from "./Textra.vue"; 2 | 3 | const Textra = { 4 | install(Vue) { 5 | Vue.component("textra", TextraPlugin); 6 | } 7 | }; 8 | 9 | export default Textra; 10 | -------------------------------------------------------------------------------- /test/.setup.js: -------------------------------------------------------------------------------- 1 | 2 | require('browser-env')() 3 | const Vue = require('vue'); 4 | var hooks = require('require-extension-hooks') 5 | 6 | Vue.config.productionTip = false; 7 | // Setup vue files to be processed by `require-extension-hooks-vue` 8 | hooks('vue').plugin('vue').push() 9 | 10 | // Setup vue and js files to be processed by `require-extension-hooks-babel` 11 | hooks(['vue', 'js']).plugin('babel').push() 12 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import { 2 | mount 3 | } from 'avoriaz'; 4 | import test from 'ava'; 5 | import Textra from '../src/Textra.vue'; 6 | import { 7 | setTimeout 8 | } from 'timers'; 9 | 10 | const wrapper = mount(Textra); 11 | wrapper.setProps({ 12 | data: ['first', 'sec', 'third'] 13 | }); 14 | 15 | 16 | 17 | test('There should be tag with class of textra', (t) => { 18 | t.plan(2); 19 | const tagClass = wrapper.find('.textra').length; 20 | t.is(tagClass, 1); 21 | t.not(tagClass, 2); 22 | }); 23 | 24 | test('There should be tag with class of mainTextra', (t) => { 25 | t.plan(2); 26 | const tagClass = wrapper.find('.mainTextra').length; 27 | t.is(tagClass, 1); 28 | t.not(tagClass, 2); 29 | }); 30 | 31 | test('Checks if mainStyleComputed exists', (t) => { 32 | const meth = typeof wrapper.computed().mainStyleComputed; 33 | t.is(meth, 'function'); 34 | }); 35 | 36 | /* 37 | Test below is paused due to a bug in avoriaz 38 | **/ 39 | 40 | // test('Checks existence some of datas', (t) => { 41 | // t.plan(2); 42 | // const datas = wrapper.data(); 43 | // console.log(datas) 44 | // t.is(datas.dataCounter , 0); 45 | // t.is(datas.displayState , 'shown'); 46 | // }); 47 | 48 | 49 | 50 | /* 51 | 52 | unfortunately since AVA does not support tests with settimeout and setinterval, 53 | we can not test this functionality of plugin. 54 | 55 | 56 | **/ --------------------------------------------------------------------------------