├── .babelrc ├── .gitignore ├── README.md ├── build ├── dev-server.js ├── utils.js ├── webpack.config.base.js ├── webpack.config.dev.js ├── webpack.config.example.js └── webpack.config.prod.js ├── dist └── vue-infinite-auto-scroll.min.js ├── example-src ├── App.vue ├── components │ └── index.vue ├── index.js ├── router.js └── template.html ├── example ├── example.js └── index.html ├── package-lock.json ├── package.json └── src ├── components └── vue-infinite-auto-scroll.vue └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env"] 4 | ], 5 | "comments": false 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | test 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-infinite-auto-scroll 2 | > 基于vue.js的无缝自动滚动的插件,可添加无限数据而不卡顿 3 | 4 | ## 内容 5 | 6 | - [**`浏览器兼容性`**](#浏览器兼容性) 7 | - [**`功能特性`**](#功能特性) 8 | - [**`安装`**](#安装) 9 | - [**`使用`**](#使用) 10 | - [**`案例`**](#使用) 11 | - [**`配置项默认值`**](#配置项默认值) 12 | - [**`个别特殊配置项说明`**](#个别特殊配置项说明) 13 | - [**`历史版本`**](#历史版本) 14 | - [**`注意`**](#注意) 15 | - [**`贡献`**](#贡献) 16 | 17 | ## 浏览器兼容性 18 | | [IE](http://godban.github.io/browsers-support-badges/)
IE | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [iOS Safari](http://godban.github.io/browsers-support-badges/)
iOS | [Chrome for Android](http://godban.github.io/browsers-support-badges/)
Android | 19 | |:---------:|:---------:|:---------:|:---------:|:---------:|:---------:| 20 | | IE9+ | ✓| ✓ | ✓ | ✓ | ✓ | ✓ 21 | 22 | ## 功能特性 23 | * [x] 基于requestAnimationFrame实现 24 | * [x] 配置多满足多样需求 25 | * [x] 目前支持上下无缝滚动 26 | * [x] 持续维护迭代 27 | * [x] 可暂停或重新开始 28 | 29 | ## 安装 30 | 31 | ### NPM 32 | 33 | ```bash 34 | npm install vue-infinite-auto-scroll --save 35 | ``` 36 | 37 | ## 使用 38 | 39 | ```js 40 | // **main.js** 41 | // 1.全局 install 42 | import Vue from 'vue' 43 | import scroll from 'vue-infinite-auto-scroll' 44 | Vue.use(scroll) 45 | 46 | // 或者你可以自己设置全局注册的组件名 默认注册的组件名是 vue-infinite-auto-scroll 47 | Vue.use(scroll,{componentName: 'vue-infinite-auto-scroll'}) 48 | 49 | // 2.单个.vue文件局部注册 50 | 58 | ``` 59 | 60 | ## 案例 61 | 62 | 请查看[**`example`**](https://github.com/wanls4583/vue-infinite-auto-scroll/tree/master/example-src) 63 | 64 | [**`online demo`**](https://blog.lisong.hn.cn/code/example/vue-infinite-auto-scroll/index.html#/index) 65 | 66 | ## 配置项 67 | 68 | |key|description|default|val| 69 | |:---|---|---|---| 70 | |`speed`|数值越大速度滚动越快|`1`|`Number`| 71 | |`force`|是否强制滚动,如果为ture,则即使列表小于容器高度,也将循环滚动|`false`|`Number`| 72 | |`once`|是否只滚动一次|`false`|`Boolean`| 73 | |`newFirst`|动态新增的数据是否优先显示,为true时将打乱显示的顺序|`false`|`Boolean`| 74 | 75 | 76 | ## 个别特殊配置项说明 77 | 78 | > 1.最外层容器需要手动设置`width height overflow:hidden` 79 | 80 | 81 | ## 历史版本 82 | 83 | See the GitHub [历史版本](https://github.com/wanls4583/vue-infinite-auto-scroll/releases). 84 | 85 | 86 | ## 贡献 87 | 88 | 欢迎给出一些意见和优化,期待你的 `Pull Request` -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var webpackConfig = require('./webpack.config.dev.js'); 5 | 6 | // 创建一个express实例 7 | var app = express() 8 | 9 | // 动态向入口配置中注入 webpack-hot-middleware/client 10 | var devClient = 'webpack-hot-middleware/client'; 11 | Object.keys(webpackConfig.entry).forEach(function (name, i) { 12 | var extras = [devClient] 13 | webpackConfig.entry[name] = extras.concat(webpackConfig.entry[name]) 14 | }) 15 | 16 | // 调用webpack并把配置传递过去 17 | var compiler = webpack(webpackConfig); 18 | 19 | // 使用 webpack-dev-middleware 中间件 20 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 21 | publicPath: '/', 22 | stats: { 23 | colors: true, 24 | chunks: false 25 | } 26 | }) 27 | 28 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 29 | 30 | // webpack插件,监听html文件改变事件 31 | compiler.plugin('compilation', function (compilation) { 32 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 33 | // 发布事件 34 | hotMiddleware.publish({ action: 'reload' }) 35 | cb() 36 | }) 37 | }) 38 | 39 | // 注册中间件 40 | app.use(devMiddleware) 41 | // 注册中间件 42 | app.use(hotMiddleware) 43 | 44 | // app.use(express.static(path.join(__dirname, '/dist'))); 45 | 46 | // 监听 8888端口,开启服务器 47 | app.listen(8888, function (err) { 48 | if (err) { 49 | console.log(err) 50 | return 51 | } 52 | console.log('Listening at http://localhost:8888') 53 | }) -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | let path = require('path'); 2 | module.exports={ 3 | resolve: function(dir) { 4 | return path.join(__dirname, '..', dir); 5 | } 6 | } -------------------------------------------------------------------------------- /build/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | let utils = require('./utils'); 2 | 3 | module.exports = { 4 | resolve: { 5 | modules: [ 6 | utils.resolve('src'), 7 | utils.resolve('node_modules') 8 | ], 9 | alias: { 10 | src: 'src' 11 | }, 12 | extensions: ['.js', '.json', '.vue'] 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.vue$/, 17 | loader: 'vue-loader', 18 | options: { 19 | loaders: { 20 | 'scss': 'style-loader!css-loader!sass-loader', 21 | 'sass': 'style-loader!css-loader!sass-loader' 22 | } 23 | } 24 | }, 25 | { 26 | test: /\.js$/, 27 | loader: 'babel-loader', 28 | exclude: /node_modules/ 29 | }, 30 | { 31 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 32 | loader: 'url-loader', 33 | options: { 34 | limit: 10000, 35 | name: '[name].[ext]' 36 | } 37 | } 38 | ] 39 | } 40 | } -------------------------------------------------------------------------------- /build/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | var merge = require('webpack-merge'); 4 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | var webpackBaseConfig = require('./webpack.config.base'); 6 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); 7 | var utils = require('./utils'); 8 | 9 | webpackBaseConfig = merge(webpackBaseConfig,{ 10 | devtool: '#cheap-module-eval-source-map', 11 | entry: {example: utils.resolve('example-src/index.js')}, 12 | output: { 13 | path: utils.resolve('example'), 14 | filename: '[name].js' 15 | }, 16 | plugins: [ 17 | //HotModule 插件在页面进行变更的时候只会重回对应的页面模块,不会重绘整个 html 文件 18 | //需要配合webpack-hot-middleware一起使用 19 | new webpack.HotModuleReplacementPlugin(), 20 | //在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段。这样可以确保输出资源不会包含错误 21 | new webpack.NoEmitOnErrorsPlugin(), 22 | // HMR shows correct file names in console on update. 23 | new webpack.NamedModulesPlugin(), 24 | //用于更友好地输出webpack的警告、错误等信息 25 | new FriendlyErrorsPlugin(), 26 | new HtmlWebpackPlugin({ 27 | template: utils.resolve('example-src/template.html') 28 | }) 29 | ] 30 | }) 31 | 32 | module.exports = webpackBaseConfig; -------------------------------------------------------------------------------- /build/webpack.config.example.js: -------------------------------------------------------------------------------- 1 | let webpackBase = require('./webpack.config.base'); 2 | let utils = require('./utils'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const merge = require('webpack-merge'); 5 | 6 | const config = merge(webpackBase,{ 7 | entry: utils.resolve('example-src/index.js'), 8 | output: { 9 | path: utils.resolve('example'), 10 | filename: 'example.js', 11 | }, 12 | plugins: [ 13 | new HtmlWebpackPlugin({ 14 | template: utils.resolve('example-src/template.html') 15 | }) 16 | ] 17 | }) 18 | module.exports = config; -------------------------------------------------------------------------------- /build/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | let webpackBase = require('./webpack.config.base'); 2 | let utils = require('./utils'); 3 | const merge = require('webpack-merge'); 4 | const webpack = require('webpack') 5 | const version = require('../package.json').version 6 | 7 | const config = merge(webpackBase,{ 8 | entry: utils.resolve('src/index.js'), 9 | output: { 10 | path: utils.resolve('dist'), 11 | filename: 'vue-infinite-auto-scroll.min.js', 12 | library: 'vueInfiniteAutoScroll', 13 | // libraryExport和libraryTarget必须,否则vue导入时报template undefined错误 14 | libraryExport: 'default', 15 | libraryTarget: 'umd' 16 | }, 17 | resolve: { 18 | alias: { 19 | vue$: 'vue/dist/vue.common.js' 20 | } 21 | }, 22 | plugins: [ 23 | new webpack.DefinePlugin({ 24 | 'process.env': { 25 | NODE_ENV: '"production"' 26 | }, 27 | VERSION: JSON.stringify(version) 28 | }), 29 | new webpack.optimize.UglifyJsPlugin({ 30 | sourceMap: false, 31 | compress: { 32 | warnings: false, 33 | drop_debugger: true, 34 | drop_console: true 35 | } 36 | }) 37 | ] 38 | }) 39 | 40 | module.exports = config; -------------------------------------------------------------------------------- /dist/vue-infinite-auto-scroll.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.vueInfiniteAutoScroll=e():t.vueInfiniteAutoScroll=e()}("undefined"!=typeof self?self:this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};return e.m=t,e.c=n,e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=0)}([function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(1),o=function(t){return t&&t.__esModule?t:{default:t}}(r);o.default.install=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t.component(e.componentName||o.default.name,o.default)},"undefined"!=typeof window&&window.Vue&&Vue.component(o.default.name,o.default),e.default=o.default},function(t,e,n){n(2);var r=n(7)(n(8),n(9),null,null);r.options.__file="D:\\github\\vue-infinite-auto-scroll\\src\\components\\vue-infinite-auto-scroll.vue",r.esModule&&Object.keys(r.esModule).some(function(t){return"default"!==t&&"__esModule"!==t}),r.options.functional,t.exports=r.exports},function(t,e,n){var r=n(3);"string"==typeof r&&(r=[[t.i,r,""]]);var o={hmr:!0};o.transform=void 0,o.insertInto=void 0;n(5)(r,o);r.locals&&(t.exports=r.locals)},function(t,e,n){e=t.exports=n(4)(!1),e.push([t.i,"\nul,\nli {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.infinite-warp {\n overflow: hidden;\n}\n",""])},function(t,e){function n(t,e){var n=t[1]||"",o=t[3];if(!o)return n;if(e&&"function"==typeof btoa){var i=r(o);return[n].concat(o.sources.map(function(t){return"/*# sourceURL="+o.sourceRoot+t+" */"})).concat([i]).join("\n")}return[n].join("\n")}function r(t){return"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(t))))+" */"}t.exports=function(t){var e=[];return e.toString=function(){return this.map(function(e){var r=n(e,t);return e[2]?"@media "+e[2]+"{"+r+"}":r}).join("")},e.i=function(t,n){"string"==typeof t&&(t=[[null,t,""]]);for(var r={},o=0;o=0&&w.splice(e,1)}function a(t){var e=document.createElement("style");return void 0===t.attrs.type&&(t.attrs.type="text/css"),c(e,t.attrs),i(t,e),e}function l(t){var e=document.createElement("link");return void 0===t.attrs.type&&(t.attrs.type="text/css"),t.attrs.rel="stylesheet",c(e,t.attrs),i(t,e),e}function c(t,e){Object.keys(e).forEach(function(n){t.setAttribute(n,e[n])})}function u(t,e){var n,r,o,i;if(e.transform&&t.css){if(!(i=e.transform(t.css)))return function(){};t.css=i}if(e.singleton){var c=g++;n=b||(b=a(e)),r=f.bind(null,n,c,!1),o=f.bind(null,n,c,!0)}else t.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=l(e),r=d.bind(null,n,e),o=function(){s(n),n.href&&URL.revokeObjectURL(n.href)}):(n=a(e),r=p.bind(null,n),o=function(){s(n)});return r(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;r(t=e)}else o()}}function f(t,e,n,r){var o=n?"":r.css;if(t.styleSheet)t.styleSheet.cssText=C(e,o);else{var i=document.createTextNode(o),s=t.childNodes;s[e]&&t.removeChild(s[e]),s.length?t.insertBefore(i,s[e]):t.appendChild(i)}}function p(t,e){var n=e.css,r=e.media;if(r&&t.setAttribute("media",r),t.styleSheet)t.styleSheet.cssText=n;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(n))}}function d(t,e,n){var r=n.css,o=n.sourceMap,i=void 0===e.convertToAbsoluteUrls&&o;(e.convertToAbsoluteUrls||i)&&(r=x(r)),o&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var s=new Blob([r],{type:"text/css"}),a=t.href;t.href=URL.createObjectURL(s),a&&URL.revokeObjectURL(a)}var h={},v=function(t){var e;return function(){return void 0===e&&(e=t.apply(this,arguments)),e}}(function(){return window&&document&&document.all&&!window.atob}),m=function(t){return document.querySelector(t)},y=function(t){var e={};return function(t){if("function"==typeof t)return t();if(void 0===e[t]){var n=m.call(this,t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(t){n=null}e[t]=n}return e[t]}}(),b=null,g=0,w=[],x=n(6);t.exports=function(t,e){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");e=e||{},e.attrs="object"==typeof e.attrs?e.attrs:{},e.singleton||"boolean"==typeof e.singleton||(e.singleton=v()),e.insertInto||(e.insertInto="head"),e.insertAt||(e.insertAt="bottom");var n=o(t,e);return r(n,e),function(t){for(var i=[],s=0;sMath.abs(r)+e.clientHeight)n.style="transform: translate3d(0px, "+(r-t.speed)+"px, 0px);";else{var i=[],s=0;i=t.copyData.splice(0,1),s=i.length,t.newFirst?t.store=i.concat(t.store):i.length&&t.store.splice(0,0,i[0]),s&&(i=t.store.splice(0,1),t.store=t.store.concat(i)),s||t.once||!(t.force||!t.force&&o.length&&t.data.length*o[0].clientHeight>e.clientHeight)||(i=t.store.splice(0,1),t.store=t.store.concat(i)),i.forEach(function(e){t.$set(t.listData,t.listData.length,e)})}if(n&&o.length){var a=o[0].clientHeight,l=Math.abs(r)/a>>0;l>5&&(t.listData.splice(0,l),n.style="transform: translate3d(0px, "+r%a+"px, 0px);")}window.cancelAnimationFrame(this.timer),this.timer=window.requestAnimationFrame(function(){t.scroll()}),this.stoped=!1}},getComputedTranslateY:function(t){var e=0,n=window.getComputedStyle?window.getComputedStyle(t,null):t.currentStyle,r=n.transform;return r&&"none"!=r&&(e=Number(r.replace(/matrix\(|\)/g,"").split(",")[5])),e},stop:function(){window.cancelAnimationFrame(this.timer),this.stoped=!0},start:function(){var t=this;this.stop(),this.timer=window.requestAnimationFrame(function(){t.scroll()})},refresh:function(t){t=this.data||t,this.copyData=t.concat([]),this.store=[],this.listData=[],this.scroller.style="transform: translate3d(0px, 0px, 0px);",this.copyData.length&&this.scroll()}},watch:{data:{handler:function(t,e){t!=e?this.refresh(t):t.length>this.dataLength?this.copyData=this.copyData.concat(t.slice(this.dataLength,t.length)):0==t.length&&this.refresh([]),this.dataLength=t.length},deep:!0}}}},function(t,e,n){t.exports={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"infinite-warp",class:t.wrapClass},[n("ul",{staticClass:"scroller",class:t.scrollerClass},t._l(t.listData,function(e){return n("li",[t._t("default",null,{item:e})],2)}),0)])},staticRenderFns:[]},t.exports.render._withStripped=!0}]).default}); -------------------------------------------------------------------------------- /example-src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /example-src/components/index.vue: -------------------------------------------------------------------------------- 1 | 49 | 121 | -------------------------------------------------------------------------------- /example-src/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | const app = new Vue({ 5 | el: '#app', 6 | router, 7 | render: h => h(App) 8 | }) -------------------------------------------------------------------------------- /example-src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from './components/index' 4 | 5 | Vue.use(Router) 6 | 7 | export default new Router({ 8 | routes: [ 9 | { 10 | path: '/', 11 | redirect: '/index' 12 | }, 13 | { 14 | path: '/index', 15 | component: Home 16 | } 17 | ] 18 | }) -------------------------------------------------------------------------------- /example-src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | demo 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | demo 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-infinite-auto-scroll", 3 | "version": "1.2.1", 4 | "description": "基于vue.js的无缝自动滚动的插件,可添加无限数据而不卡顿", 5 | "main": "dist/vue-infinite-auto-scroll.min.js", 6 | "scripts": { 7 | "dev": "node build/dev-server.js", 8 | "build:prod": "webpack --config build/webpack.config.prod.js", 9 | "build:example": "webpack --config build/webpack.config.example.js", 10 | "build:all": "npm run build:prod && npm run build:example" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/wanls4583/vue-infinite-auto-scroll.git" 15 | }, 16 | "keywords": [ 17 | "vue", 18 | "vuejs", 19 | "ui", 20 | "components", 21 | "infinite", 22 | "auto-scroll", 23 | "scroll" 24 | ], 25 | "author": "lisong", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/wanls4583/vue-infinite-auto-scroll/issues" 29 | }, 30 | "homepage": "https://github.com/wanls4583/vue-infinite-scroll#readme", 31 | "devDependencies": { 32 | "babel-core": "^6.0.0", 33 | "babel-loader": "^7.1.2", 34 | "babel-polyfill": "^6.26.0", 35 | "babel-preset-env": "^1.6.1", 36 | "css-loader": "^0.28.11", 37 | "express": "^4.16.3", 38 | "friendly-errors-webpack-plugin": "^1.7.0", 39 | "html-webpack-plugin": "^3.2.0", 40 | "node-sass": "^4.5.2", 41 | "path": "^0.12.7", 42 | "sass-loader": "^6.0.6", 43 | "style-loader": "^0.21.0", 44 | "url-loader": "^1.0.1", 45 | "vue": "^2.5.13", 46 | "vue-loader": "^11.3.3", 47 | "vue-router": "^3.0.1", 48 | "vue-template-compiler": "^2.5.13", 49 | "webpack": "^3.10.0", 50 | "webpack-dev-middleware": "^2.0.4", 51 | "webpack-hot-middleware": "^2.21.0", 52 | "webpack-merge": "^4.1.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/vue-infinite-auto-scroll.vue: -------------------------------------------------------------------------------- 1 | 10 | 166 | 177 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import vueMyCLass from './components/vue-infinite-auto-scroll.vue' 2 | 3 | vueMyCLass.install = function (Vue, options = {}) { 4 | Vue.component(options.componentName || vueMyCLass.name, vueMyCLass) 5 | } 6 | 7 | // auto install 8 | if (typeof window !== 'undefined' && window.Vue) { 9 | Vue.component(vueMyCLass.name, vueMyCLass) 10 | } 11 | 12 | export default vueMyCLass 13 | --------------------------------------------------------------------------------