├── .npmignore ├── .babelrc ├── src ├── index.js ├── main.css └── register.js ├── tests ├── _webpack.config.js ├── _webpack.commonjs.js ├── _entry.js └── index.html ├── .gitignore ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | /tests 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import register from './register' 2 | import './main.css' 3 | 4 | export default { 5 | install(Vue, options = {}) { 6 | register(Vue, options) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/_webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | entry: [path.resolve('./tests/_entry.js')], 5 | output: { 6 | path: __dirname, 7 | filename: '_bundle.js' 8 | }, 9 | resolve: { 10 | extensions: ['', '.js'] 11 | }, 12 | module: { 13 | loaders: [ 14 | { 15 | test: /\.js$/, 16 | exclude: /node_modules/, 17 | loader: 'babel' 18 | } 19 | ] 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | .vue-preload { 2 | height: 3px; 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | opacity: 1; 8 | transition: opacity .3s ease; 9 | .vue-preload-progress { 10 | background-color: #007FFF; 11 | position: absolute; 12 | height: 100%; 13 | animation: loading 1.2s linear infinite; 14 | } 15 | } 16 | 17 | .vue-preload-enter, .vue-preload-leave { 18 | opacity: 0; 19 | } 20 | 21 | @keyframes loading { 22 | 0% { 23 | left: 0; 24 | right: 100%; 25 | opacity: 1; 26 | } 27 | 50% { 28 | left: 0; 29 | right: 0; 30 | } 31 | 100% { 32 | left: 100%; 33 | right: 0; 34 | opacity: .4; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/_webpack.commonjs.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | entry: [path.resolve('./src/index.js')], 5 | output: { 6 | path: path.join(__dirname, '../'), 7 | filename: 'vue-preload.js', 8 | libraryTarget: 'umd', 9 | library: 'vue-preload' 10 | }, 11 | resolve: { 12 | extensions: ['', '.js'] 13 | }, 14 | module: { 15 | loaders: [ 16 | { 17 | test: /\.js$/, 18 | exclude: /node_modules/, 19 | loader: 'babel' 20 | }, 21 | { 22 | test: /\.css$/, 23 | exclude: /node_modules/, 24 | loader: 'style!css!postcss' 25 | } 26 | ] 27 | }, 28 | postcss: function() { 29 | return [ 30 | require('postcss-nested'), 31 | require('cssnext')() 32 | ] 33 | }, 34 | target: 'node' 35 | } 36 | -------------------------------------------------------------------------------- /tests/_entry.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VuePreload from '../vue-preload' 3 | import fetch from 'superagent' 4 | if (location.hostname === 'localhost') { 5 | Vue.config.debug = true 6 | window._ = Vue.util 7 | } 8 | Vue.use(VuePreload, { 9 | showProgress: true, 10 | onStart() { 11 | console.log('start') 12 | }, 13 | onEnd() { 14 | console.log('end') 15 | } 16 | }) 17 | new Vue({ 18 | el: '#app', 19 | data() { 20 | return { 21 | leftTip: null, 22 | rightTip: null 23 | } 24 | }, 25 | methods: { 26 | handlePreload(pr, e) { 27 | fetch.get('http://lib.avosapps.com/tips') 28 | .end((err, res) => { 29 | pr.set({rightTip: JSON.parse(res.text)}) 30 | pr.end() 31 | }) 32 | }, 33 | handleLoad() { 34 | fetch.get('http://lib.avosapps.com/tips') 35 | .end((err, res) => { 36 | this.leftTip = JSON.parse(res.text) 37 | }) 38 | } 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # app 2 | /tests/_bundle.js 3 | /vue-preload.js 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 31 | node_modules 32 | .DS_Store 33 | .AppleDouble 34 | .LSOverride 35 | 36 | # Icon must end with two \r 37 | Icon 38 | 39 | 40 | # Thumbnails 41 | ._* 42 | 43 | # Files that might appear on external disk 44 | .Spotlight-V100 45 | .Trashes 46 | 47 | # Directories potentially created on remote AFP share 48 | .AppleDB 49 | .AppleDesktop 50 | Network Trash Folder 51 | Temporary Items 52 | .apdisk 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-preload", 3 | "version": "0.1.11", 4 | "description": "Preloading data for Vue component", 5 | "main": "./vue-preload.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "webpack-commonjs": "webpack --config tests/_webpack.commonjs.js -p", 9 | "webpack-commonjs-dev": "webpack --config tests/_webpack.commonjs.js --watch", 10 | "webpack-browser": "webpack --config tests/_webpack.config.js -p", 11 | "webpack-browser-dev": "webpack --config tests/_webpack.config.js --watch", 12 | "publish-demo": "surge -d vue-preload.surge.sh -p tests", 13 | "build": "npm run webpack-commonjs && npm run webpack-browser" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/egoist/vue-preload.git" 18 | }, 19 | "author": "EGOIST", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/egoist/vue-preload/issues" 23 | }, 24 | "homepage": "https://github.com/egoist/vue-preload#readme", 25 | "devDependencies": { 26 | "babel-loader": "^6.2.0", 27 | "babel-preset-es2015": "^6.1.18", 28 | "css-loader": "^0.23.0", 29 | "cssnext": "^1.8.4", 30 | "postcss-loader": "^0.8.0", 31 | "postcss-nested": "^1.0.0", 32 | "style-loader": "^0.13.0", 33 | "superagent": "^1.4.0", 34 | "vue": "^1.0.8" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Preload 2 | 3 | [![NPM version](https://img.shields.io/npm/v/vue-preload.svg?style=flat-square)](https://www.npmjs.com/package/vue-preload) 4 | [![NPM download](https://img.shields.io/npm/dm/vue-preload.svg?style=flat-square)](https://www.npmjs.com/package/vue-preload) 5 | [![David Status](https://img.shields.io/david/dev/egoist/vue-preload.svg?style=flat-square)](https://david-dm.org/egoist/vue-preload) 6 | 7 | ## How does it work 8 | 9 | Like what [InstantClick](http://instantclick.io/) said, before visitors click on a link, they hover over that link. Between these two events, 200 ms to 300 ms usually pass by. InstantClick makes use of that time to preload the page, so that the page is already there when you click. 10 | 11 | What the difference between InstantClick and Vue Preload is the latter preloads the data to store in state instead of replacing the HTML. 12 | 13 | ## How simple it could be 14 | 15 | First install with `npm install vue-preload` and use the plugin, pretty neat huh? 16 | 17 | Or even CDN: `https://unpkg.com/vue-preload@latest` 18 | 19 | ```javascript 20 | import Vue from 'vue' 21 | import VuePreload from 'vue-preload' 22 | Vue.use(VuePreload) 23 | // with options 24 | Vue.use(VuePreload, { 25 | // show the native progress bar 26 | // put in your root component 27 | showProgress: true, 28 | // excutes when click 29 | onStart() {}, 30 | // excutes when use .end() and after .setState() 31 | onEnd() {}, 32 | // excutes when prefetching the state 33 | onPreLoading() {}, 34 | }) 35 | ``` 36 | 37 | **Protip:** You can disable `showProgress` and define your custom progress bar with `onStart()` and `onEnd()` 38 | 39 | Then replace your `v-on:click="handleClick"` with `v-preload="handleClick"`, and make a small change: 40 | 41 | ```javascript 42 | ... 43 | handleClick(pr) { 44 | fetch.then().then(data => { 45 | // pre-set states 46 | pr.set({title: data.title}) 47 | // add the following line to tell it preLoading ends 48 | pr.end() 49 | }) 50 | } 51 | ... 52 | ``` 53 | 54 | [More detailed usage](/tests/_entry.js) 55 | 56 | ## License 57 | 58 | MIT. 59 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vue-Preload 7 | 73 | 74 | 75 |
76 | 77 |
Vue Preload
78 |
79 | 80 |
81 |

{{ leftTip.category }}

82 |

{{ leftTip.tip }}

83 |
84 |
85 |
86 | 87 |
88 |

{{ rightTip.category }}

89 |

{{ rightTip.tip }}

90 |
91 |
92 | Star me on GitHub 93 |
94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/register.js: -------------------------------------------------------------------------------- 1 | export default function (Vue, options) { 2 | const _ = Vue.util 3 | 4 | if (options.showProgress) { 5 | Vue.component('preloading', { 6 | data() { 7 | return { 8 | show: false 9 | } 10 | }, 11 | template: ` 12 |
13 |
14 |
15 | `, 16 | methods: { 17 | toggle(show) { 18 | this.show = show 19 | } 20 | }, 21 | ready() { 22 | this.$on('preloading', this.toggle) 23 | } 24 | }) 25 | } 26 | 27 | function isMobile() { 28 | return navigator.userAgent.indexOf('Windows Phone') > -1 29 | || navigator.userAgent.indexOf('Android') > -1 30 | || navigator.userAgent.indexOf('iPad') > -1 31 | || navigator.userAgent.indexOf('iPhone') > -1 32 | || navigator.userAgent.indexOf('Mobile') > -1 33 | } 34 | 35 | Vue.directive('preload', { 36 | bind() { 37 | this.preLoading = false 38 | this.tmp = null 39 | this.clickToPreload = false 40 | this.handleMouseOver = (e) => { 41 | if (this.preLoading) { 42 | if (options.onPreloading) { 43 | options.onPreLoading() 44 | } 45 | return 46 | } 47 | this.preLoading = true 48 | this.vm[this.expression].call(null, this, e) 49 | } 50 | this.handleClick = (e) => { 51 | e.preventDefault() 52 | this.showBar() 53 | if (!this.tmp && !this.preLoading) { 54 | this.clickToPreload = true 55 | return this.handleMouseOver(e) 56 | } 57 | this.setState(this.tmp) 58 | } 59 | if (isMobile()) { 60 | _.on(this.el, 'touchstart', this.handleMouseOver) 61 | } else { 62 | _.on(this.el, 'mouseover', this.handleMouseOver) 63 | _.on(this.el, 'click', this.handleClick) 64 | } 65 | }, 66 | set(obj) { 67 | this.tmp = obj 68 | }, 69 | setState(state) { 70 | for(const key in state) { 71 | this.vm.$set(key, state[key]) 72 | } 73 | this.hideBar() 74 | this.tmp = null 75 | }, 76 | end() { 77 | this.preLoading = false 78 | this.hideBar() 79 | if (this.clickToPreload || isMobile()) { 80 | this.setState(this.tmp) 81 | this.clickToPreload = false 82 | } 83 | }, 84 | showBar() { 85 | if (options.showProgress) { 86 | this.vm.$broadcast('preloading', true) 87 | } 88 | if (typeof options.onStart === 'function') { 89 | options.onStart() 90 | } 91 | }, 92 | hideBar() { 93 | if (options.showProgress && !this.preLoading) { 94 | this.vm.$broadcast('preloading', false) 95 | } 96 | if (typeof options.onStart === 'function') { 97 | options.onEnd() 98 | } 99 | }, 100 | reset() { 101 | if (isMobile()) { 102 | _.off(this.el, 'touchstart', this.handleMouseOver) 103 | } else { 104 | _.off(this.el, 'mouseover', this.handleMouseOver) 105 | _.off(this.el, 'click', this.handleClick) 106 | } 107 | }, 108 | unbind() { 109 | this.reset() 110 | } 111 | }) 112 | } 113 | --------------------------------------------------------------------------------