├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── build ├── seamscroll.js └── seamscroll.min.js ├── document └── README.md ├── index.html ├── package.json ├── script ├── build-debug.js └── build.js ├── src ├── content │ ├── create.js │ ├── event.js │ ├── getCss.js │ └── seamless.js └── index.js └── test ├── demo.css ├── reset.css └── timg.jpg /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "browsers": [ 8 | "last 2 versions", 9 | "safari 7", 10 | "not ie <= 9" 11 | ] 12 | }, 13 | "modules": false, 14 | "loose": true 15 | } 16 | ] 17 | ] 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # files 2 | *.log 3 | *.zip 4 | .DS_Store 5 | package-lock.json 6 | 7 | # folders 8 | node_modules/* 9 | 10 | # Editor directories and files 11 | .idea 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 chenxuan1993 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seamless-scroll 2 | > js seamless-scroll plugin 3 | 4 | [![Build Status](https://img.shields.io/appveyor/ci/gruntjs/grunt/master.svg) ![LICENSE MIT](https://img.shields.io/npm/l/express.svg)](https://www.npmjs.com/package/seamscroll) ![](https://img.shields.io/npm/v/seamscroll.svg) 5 | 6 |

7 | 8 |

9 | 10 |

11 | 🌾 sample demo | 12 | 📘 中文文档 13 |

14 | 15 | 16 | ## Browser support 17 | | [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 | 18 | |:---------:|:---------:|:---------:|:---------:|:---------:|:---------:| 19 | | IE7+ | ✓| ✓ | ✓ | ✓ | ✓ | ✓ 20 | 21 | * mobile gestures are not supported. 22 | 23 | 24 | ## Installation 25 | 26 | ### NPM 27 | 28 | ```bash 29 | npm install seamscroll --save 30 | ``` 31 | ### CDN 32 | `https://cdn.jsdelivr.net/npm/seamscroll@0.0.11/build/seamscroll.min.js` 33 | 34 | ## Usage 35 | 36 | ```js 37 | const seamless = require('seamscroll') 38 | `or` 39 | import seamless from 'seamscroll' 40 | 41 | seamless.init({ 42 | dom: document.getElementById('demo1') 43 | }) 44 | 45 | //script tag 46 | 47 | 52 | ``` 53 | 54 | ## Demo 55 | ```css 56 | .demo2 { 57 | width: 600px; 58 | height: 100px; 59 | position: relative; 60 | overflow: hidden; 61 | margin-top: 100px; 62 | } 63 | .list2 li { 64 | float: left; 65 | width: 100px; 66 | height: 100px; 67 | margin-right: 20px; 68 | text-align: center; 69 | font-size: 20px; 70 | color: #fff; 71 | line-height:100px; 72 | background-color: #ccc; 73 | } 74 | ``` 75 | ```html 76 |
77 | 85 |
86 | ``` 87 | ```javascript 88 | 89 | seamscroll.init({ 90 | dom: document.getElementById('demo2'), 91 | direction: 2 92 | }) 93 | ``` 94 | 95 | ## Configure 96 | *Required parameters(dom) 97 | 98 | |key|description|default|val| 99 | |:---|---|---|---| 100 | |`*dom`|the role of the element|`null`|`dom`| 101 | |`step`|step,the faster the rolling speed is faster|`1`|`Number`| 102 | |`hoverStop`|mouse hover control is enabled|`true`|`Boolean`| 103 | |`direction`|0 down 1 up 2 left 3 right|`1`|`Number`| 104 | |`singleHeight`|one single stop height(default zero is seamless) => direction 0/1|`0`|`Number`| 105 | |`singleWidth`|one single stop width(default zero is seamless) => direction 2/3|`0`|`Number`| 106 | |`waitTime`|one single data stop wait time(1s)|`1000`|`Number`| 107 | 108 | ## Changelog 109 | See the GitHub [release history](https://github.com/chenxuan0000/seamless-scroll/releases). 110 | 111 | ## License 112 | seamless-scroll is open source and released under the [MIT License](LICENSE). -------------------------------------------------------------------------------- /build/seamscroll.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["seamscroll"] = factory(); 8 | else 9 | root["seamscroll"] = factory(); 10 | })(typeof self !== 'undefined' ? self : this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) { 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ } 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ i: moduleId, 25 | /******/ l: false, 26 | /******/ exports: {} 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.l = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // define getter function for harmony exports 47 | /******/ __webpack_require__.d = function(exports, name, getter) { 48 | /******/ if(!__webpack_require__.o(exports, name)) { 49 | /******/ Object.defineProperty(exports, name, { 50 | /******/ configurable: false, 51 | /******/ enumerable: true, 52 | /******/ get: getter 53 | /******/ }); 54 | /******/ } 55 | /******/ }; 56 | /******/ 57 | /******/ // getDefaultExport function for compatibility with non-harmony modules 58 | /******/ __webpack_require__.n = function(module) { 59 | /******/ var getter = module && module.__esModule ? 60 | /******/ function getDefault() { return module['default']; } : 61 | /******/ function getModuleExports() { return module; }; 62 | /******/ __webpack_require__.d(getter, 'a', getter); 63 | /******/ return getter; 64 | /******/ }; 65 | /******/ 66 | /******/ // Object.prototype.hasOwnProperty.call 67 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 68 | /******/ 69 | /******/ // __webpack_public_path__ 70 | /******/ __webpack_require__.p = ""; 71 | /******/ 72 | /******/ // Load entry module and return exports 73 | /******/ return __webpack_require__(__webpack_require__.s = 0); 74 | /******/ }) 75 | /************************************************************************/ 76 | /******/ ([ 77 | /* 0 */ 78 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 79 | 80 | "use strict"; 81 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); 82 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__content_create__ = __webpack_require__(1); 83 | /* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "init", function() { return __WEBPACK_IMPORTED_MODULE_0__content_create__["a"]; }); 84 | /** 85 | * @desc webpack打包入口 86 | */ 87 | 88 | 89 | 90 | 91 | 92 | /***/ }), 93 | /* 1 */ 94 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 95 | 96 | "use strict"; 97 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__seamless__ = __webpack_require__(2); 98 | 99 | 100 | var create = function create(options) { 101 | return new __WEBPACK_IMPORTED_MODULE_0__seamless__["a" /* default */](options); 102 | }; 103 | 104 | /* harmony default export */ __webpack_exports__["a"] = (create); 105 | 106 | /***/ }), 107 | /* 2 */ 108 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 109 | 110 | "use strict"; 111 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__event__ = __webpack_require__(5); 112 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__getCss__ = __webpack_require__(6); 113 | __webpack_require__(3)(); 114 | var copyObj = __webpack_require__(4); 115 | 116 | 117 | 118 | var defaultOptions = { 119 | step: 1, //步长 120 | hoverStop: true, //是否启用鼠标hover控制 121 | direction: 1, // 0 往下 1 往上 2向左 3向右 122 | singleHeight: 0, //单条数据高度有值hoverStop关闭 123 | singleWidth: 0, //单条数据宽度有值hoverStop关闭 124 | waitTime: 1000 //单步停止等待时间 125 | }; 126 | 127 | var seamless = function seamless(options) { 128 | this.options = copyObj({}, defaultOptions, options); 129 | var dom = this.options.dom; 130 | if (!dom) throw new Error('you must set a dom'); 131 | dom.style.position = 'relative'; 132 | dom.style.overflow = 'hidden'; 133 | this.reqFrame = null; 134 | this.singleWaitTime = null; // single 单步滚动的定时器 135 | this._top = 0; 136 | this._left = 0; 137 | this.isHover = false; //_move()方法的开关 138 | dom.innerHTML += dom.innerHTML; 139 | if (this.options.direction > 1) { 140 | //水平向滚动 141 | var child = dom.children, 142 | childFirst = child[0], 143 | len = child.length; 144 | this._width = (childFirst.offsetWidth + this._getInt(childFirst, 'margin-left') + this._getInt(childFirst, 'margin-right')) * len; 145 | dom.style.width = this._width + 'px'; 146 | } 147 | this._move(); 148 | this._bindEvent(); 149 | }; 150 | 151 | seamless.prototype = { 152 | _getInt: function _getInt(dom, name) { 153 | return parseInt(Object(__WEBPACK_IMPORTED_MODULE_1__getCss__["a" /* default */])(dom, name)); 154 | }, 155 | _cancle: function _cancle() { 156 | cancelAnimationFrame(this.reqFrame || ''); 157 | }, 158 | _bindEvent: function _bindEvent() { 159 | if (!this.options.hoverStop) return; 160 | var that = this; 161 | var dom = this.options.dom; 162 | Object(__WEBPACK_IMPORTED_MODULE_0__event__["a" /* default */])(dom, 'mouseenter', function () { 163 | that.isHover = true; // 关闭_move 164 | // 防止蛋疼的人频频hover进出单步滚动 导致定时器乱掉 165 | if (that.singleWaitTime) clearTimeout(that.singleWaitTime); 166 | that._cancle(); 167 | }); 168 | Object(__WEBPACK_IMPORTED_MODULE_0__event__["a" /* default */])(dom, 'mouseleave', function () { 169 | that.isHover = false; // 开启_move 170 | that._move(); 171 | }); 172 | }, 173 | _move: function _move() { 174 | if (this.isHover) return; 175 | this._cancle(); 176 | var that = this; 177 | var dom = this.options.dom; 178 | this.reqFrame = requestAnimationFrame(function () { 179 | var h = dom.offsetHeight / 2; //实际高度 180 | var direction = that.options.direction; //滚动方向 181 | if (direction === 1) { 182 | // 上 183 | if (Math.abs(that._top) >= h) that._top = 0; 184 | that._top -= that.options.step; 185 | dom.style.top = that._top + 'px'; 186 | } else if (direction === 0) { 187 | // 下 188 | if (that._top >= 0) that._top = h * -1; 189 | that._top += that.options.step; 190 | dom.style.top = that._top + 'px'; 191 | } else if (direction === 2) { 192 | // 左 193 | if (Math.abs(that._left) >= that._width / 2) that._left = 0; 194 | that._left -= that.options.step; 195 | dom.style.left = that._left + 'px'; 196 | } else if (direction === 3) { 197 | // 右 198 | if (that._left >= 0) that._left = that._width / 2 * -1; 199 | that._left += that.options.step; 200 | dom.style.left = that._left + 'px'; 201 | } 202 | that._judgeSingle(); 203 | }); 204 | }, 205 | _judgeSingle: function _judgeSingle() { 206 | var _this = this; 207 | 208 | var singleH = this.options.singleHeight; 209 | var singleW = this.options.singleWidth; 210 | if (this.singleWaitTime) clearTimeout(this.singleWaitTime); 211 | if (!!singleH) { 212 | //是否启动了单行暂停配置 213 | if (Math.abs(this._top) % singleH === 0) { 214 | // 符合条件暂停waitTime 215 | this.singleWaitTime = setTimeout(function () { 216 | _this._move(); 217 | }, this.options.waitTime); 218 | } else { 219 | this._move(); 220 | } 221 | } else if (!!singleW) { 222 | if (Math.abs(this._left) % singleW === 0) { 223 | // 符合条件暂停waitTime 224 | this.singleWaitTime = setTimeout(function () { 225 | _this._move(); 226 | }, this.options.waitTime); 227 | } else { 228 | this._move(); 229 | } 230 | } else { 231 | this._move(); 232 | } 233 | } 234 | }; 235 | 236 | /* harmony default export */ __webpack_exports__["a"] = (seamless); 237 | 238 | /***/ }), 239 | /* 3 */ 240 | /***/ (function(module, exports) { 241 | 242 | /** 243 | * @desc AnimationFrame简单兼容hack 244 | */ 245 | var animationFrame = function animationFrame() { 246 | window.cancelAnimationFrame = function () { 247 | return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame || function (id) { 248 | return window.clearTimeout(id); 249 | }; 250 | }(); 251 | window.requestAnimationFrame = function () { 252 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { 253 | return window.setTimeout(callback, 1000 / 60); 254 | }; 255 | }(); 256 | }; 257 | 258 | module.exports = animationFrame; 259 | 260 | /***/ }), 261 | /* 4 */ 262 | /***/ (function(module, exports) { 263 | 264 | var _typeof2 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 265 | 266 | var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol" ? function (obj) { 267 | return typeof obj === "undefined" ? "undefined" : _typeof2(obj); 268 | } : function (obj) { 269 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === "undefined" ? "undefined" : _typeof2(obj); 270 | }; 271 | 272 | /** 273 | * @desc 深浅合并拷贝 274 | */ 275 | function copyObj() { 276 | if (!Array.isArray) { 277 | Array.isArray = function (arg) { 278 | return Object.prototype.toString.call(arg) === '[object Array]'; 279 | }; 280 | } 281 | var name = void 0, 282 | options = void 0, 283 | src = void 0, 284 | copy = void 0, 285 | copyIsArray = void 0, 286 | clone = void 0, 287 | i = 1, 288 | target = arguments[0] || {}, 289 | 290 | // 使用||运算符,排除隐式强制类型转换为false的数据类型 291 | deep = false, 292 | len = arguments.length; 293 | if (typeof target === 'boolean') { 294 | deep = target; 295 | target = arguments[1] || {}; 296 | i++; 297 | } 298 | if ((typeof target === 'undefined' ? 'undefined' : _typeof(target)) !== 'object' && typeof target !== 'function') { 299 | target = {}; 300 | } 301 | // 如果arguments.length === 1 或typeof arguments[0] === 'boolean',且存在arguments[1],则直接返回target对象 302 | if (i === len) { 303 | return target; 304 | } 305 | for (; i < len; i++) { 306 | //所以如果源对象中数据类型为Undefined或Null那么就会跳过本次循环,接着循环下一个源对象 307 | if ((options = arguments[i]) != null) { 308 | // 如果遇到源对象的数据类型为Boolean, Number for in循环会被跳过,不执行for in循环// src用于判断target对象是否存在name属性 309 | for (name in options) { 310 | // src用于判断target对象是否存在name属性 311 | src = target[name]; 312 | // 需要复制的属性当前源对象的name属性 313 | copy = options[name]; 314 | // 判断copy是否是数组 315 | copyIsArray = Array.isArray(copy); 316 | // 如果是深复制且copy是一个对象或数组则需要递归直到copy成为一个基本数据类型为止 317 | if (deep && copy && ((typeof copy === 'undefined' ? 'undefined' : _typeof(copy)) === 'object' || copyIsArray)) { 318 | if (copyIsArray) { 319 | copyIsArray = false; 320 | // 如果目标对象存在name属性且是一个数组 321 | // 则使用目标对象的name属性,否则重新创建一个数组,用于复制 322 | clone = src && Array.isArray(src) ? src : []; 323 | } else { 324 | // 如果目标对象存在name属性且是一个对象则使用目标对象的name属性,否则重新创建一个对象,用于复制 325 | clone = src && (typeof src === 'undefined' ? 'undefined' : _typeof(src)) === 'object' ? src : {}; 326 | } 327 | // 深复制,所以递归调用copyObject函数 328 | // 返回值为target对象,即clone对象 329 | // copy是一个源对象 330 | target[name] = copyObj(deep, clone, copy); 331 | } else if (copy !== undefined) { 332 | // 浅复制,直接复制到target对象上 333 | target[name] = copy; 334 | } 335 | } 336 | } 337 | } 338 | return target; 339 | } 340 | 341 | module.exports = copyObj; 342 | 343 | /***/ }), 344 | /* 5 */ 345 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 346 | 347 | "use strict"; 348 | var addEventListener = function addEventListener(element, type, callback) { 349 | if (element.addEventListener) { 350 | element.addEventListener(type, callback, false); 351 | } else if (element.attachEvent) { 352 | element.attachEvent('on' + type, callback); 353 | } else { 354 | element['on' + type] = callback; 355 | } 356 | }; 357 | 358 | /* harmony default export */ __webpack_exports__["a"] = (addEventListener); 359 | 360 | /***/ }), 361 | /* 6 */ 362 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 363 | 364 | "use strict"; 365 | var getStyle = function getStyle(dom, name) { 366 | var elem = dom.currentStyle ? dom.currentStyle : window.getComputedStyle(dom, null); 367 | return elem[name]; 368 | }; 369 | 370 | /* harmony default export */ __webpack_exports__["a"] = (getStyle); 371 | 372 | /***/ }) 373 | /******/ ]); 374 | }); -------------------------------------------------------------------------------- /build/seamscroll.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.seamscroll=e():t.seamscroll=e()}("undefined"!=typeof self?self:this,function(){return function(t){function e(i){if(n[i])return n[i].exports;var o=n[i]={i:i,l:!1,exports:{}};return t[i].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,i){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:i})},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 i=n(1);n.d(e,"init",function(){return i.a})},function(t,e,n){"use strict";var i=n(2),o=function(t){return new i.a(t)};e.a=o},function(t,e,n){"use strict";var i=n(5),o=n(6);n(3)();var r=n(4),s={step:1,hoverStop:!0,direction:1,singleHeight:0,singleWidth:0,waitTime:1e3},a=function(t){this.options=r({},s,t);var e=this.options.dom;if(!e)throw new Error("you must set a dom");if(e.style.position="relative",e.style.overflow="hidden",this.reqFrame=null,this.singleWaitTime=null,this._top=0,this._left=0,this.isHover=!1,e.innerHTML+=e.innerHTML,this.options.direction>1){var n=e.children,i=n[0],o=n.length;this._width=(i.offsetWidth+this._getInt(i,"margin-left")+this._getInt(i,"margin-right"))*o,e.style.width=this._width+"px"}this._move(),this._bindEvent()};a.prototype={_getInt:function(t,e){return parseInt(Object(o.a)(t,e))},_cancle:function(){cancelAnimationFrame(this.reqFrame||"")},_bindEvent:function(){if(this.options.hoverStop){var t=this,e=this.options.dom;Object(i.a)(e,"mouseenter",function(){t.isHover=!0,t.singleWaitTime&&clearTimeout(t.singleWaitTime),t._cancle()}),Object(i.a)(e,"mouseleave",function(){t.isHover=!1,t._move()})}},_move:function(){if(!this.isHover){this._cancle();var t=this,e=this.options.dom;this.reqFrame=requestAnimationFrame(function(){var n=e.offsetHeight/2,i=t.options.direction;1===i?(Math.abs(t._top)>=n&&(t._top=0),t._top-=t.options.step,e.style.top=t._top+"px"):0===i?(t._top>=0&&(t._top=-1*n),t._top+=t.options.step,e.style.top=t._top+"px"):2===i?(Math.abs(t._left)>=t._width/2&&(t._left=0),t._left-=t.options.step,e.style.left=t._left+"px"):3===i&&(t._left>=0&&(t._left=t._width/2*-1),t._left+=t.options.step,e.style.left=t._left+"px"),t._judgeSingle()})}},_judgeSingle:function(){var t=this,e=this.options.singleHeight,n=this.options.singleWidth;this.singleWaitTime&&clearTimeout(this.singleWaitTime),e?Math.abs(this._top)%e==0?this.singleWaitTime=setTimeout(function(){t._move()},this.options.waitTime):this._move():n&&Math.abs(this._left)%n==0?this.singleWaitTime=setTimeout(function(){t._move()},this.options.waitTime):this._move()}},e.a=a},function(t,e){var n=function(){window.cancelAnimationFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t)}}(),window.requestAnimationFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}()};t.exports=n},function(t,e){function n(){Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)});var t=void 0,e=void 0,i=void 0,r=void 0,s=void 0,a=void 0,u=1,c=arguments[0]||{},f=!1,l=arguments.length;if("boolean"==typeof c&&(f=c,c=arguments[1]||{},u++),"object"!==(void 0===c?"undefined":o(c))&&"function"!=typeof c&&(c={}),u===l)return c;for(;u js无缝滚动插件 3 | 4 | [![Build Status](https://img.shields.io/appveyor/ci/gruntjs/grunt/master.svg) ![LICENSE MIT](https://img.shields.io/npm/l/express.svg)](https://www.npmjs.com/package/seamscroll) ![](https://img.shields.io/npm/v/seamscroll.svg) 5 | 6 | 7 |

8 | 9 |

10 | 11 |

12 | 🌾 简单demo | 13 | 📘 English Document 14 |

15 | 16 | ## 兼容性 17 | | [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 | 18 | |:---------:|:---------:|:---------:|:---------:|:---------:|:---------:| 19 | | IE7+ | ✓| ✓ | ✓ | ✓ | ✓ | ✓ 20 | 21 | * 不支持移动端手势。 22 | 23 | 24 | ## 安装 25 | 26 | ### NPM 27 | 28 | ```bash 29 | npm install seamscroll --save 30 | ``` 31 | ### CDN 32 | `https://cdn.jsdelivr.net/npm/seamscroll@0.0.11/build/seamscroll.min.js` 33 | 34 | ## 使用 35 | 36 | ```js 37 | const seamless = require('seamscroll') 38 | `or` 39 | import seamless from 'seamscroll' 40 | 41 | seamless.init({ 42 | dom: document.getElementById('demo1') 43 | }) 44 | //script tag 45 | 46 | 51 | ``` 52 | 53 | ## Demo 54 | ```css 55 | .demo2 { 56 | width: 600px; 57 | height: 100px; 58 | position: relative; 59 | overflow: hidden; 60 | margin-top: 100px; 61 | } 62 | .list2 li { 63 | float: left; 64 | width: 100px; 65 | height: 100px; 66 | margin-right: 20px; 67 | text-align: center; 68 | font-size: 20px; 69 | color: #fff; 70 | line-height:100px; 71 | background-color: #ccc; 72 | } 73 | ``` 74 | ```html 75 |
76 | 84 |
85 | ``` 86 | ```javascript 87 | 88 | seamscroll.init({ 89 | dom: document.getElementById('demo2'), 90 | direction: 2 91 | }) 92 | ``` 93 | 94 | ## 配置参数 95 | *必填参数(dom) 96 | 97 | |key|description|default|val| 98 | |:---|---|---|---| 99 | |`*dom`|作用的dom|`null`|`dom`| 100 | |`step`|步长,越大越快|`1`|`Number`| 101 | |`hoverStop`|是否启用鼠标hover控制|`true`|`Boolean`| 102 | |`direction`|方向 0 往下 1 往上 2向左 3向右|`1`|`Number`| 103 | |`singleHeight`|单步运动停止的高度(默认值0是无缝不停止的滚动) direction => 0/1|`0`|`Number`| 104 | |`singleWidth`|单步运动停止的宽度(默认值0是无缝不停止的滚动) direction => 2/3|`0`|`Number`| 105 | |`waitTime`|单步停止等待时间(default 1s)|`1000`|`Number`| 106 | 107 | ## 历史版本 108 | See the GitHub [历史版本](https://github.com/chenxuan0000/seamless-scroll/releases). 109 | 110 | ## License 111 | seamless-scroll is open source and released under the [MIT License](LICENSE). -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 无缝滚动demo 11 | 12 | 13 |
14 |
15 |
    16 |
  • 17 | 无缝滚动第一行无缝滚动第一行2017-12-16 18 |
  • 19 |
  • 20 | 无缝滚动第二行无缝滚动第一行2017-12-16 21 |
  • 22 |
  • 23 | 无缝滚动第三行无缝滚动第一行2017-12-16 24 |
  • 25 |
  • 26 | 无缝滚动第四行无缝滚动第一行2017-12-16 27 |
  • 28 |
  • 29 | 无缝滚动第五行无缝滚动第一行2017-12-16 30 |
  • 31 |
  • 32 | 无缝滚动第六行无缝滚动第一行2017-12-16 33 |
  • 34 |
  • 35 | 无缝滚动第七行无缝滚动第一行2017-12-16 36 |
  • 37 |
  • 38 | 无缝滚动第八行无缝滚动第一行2017-12-16 39 |
  • 40 |
41 |
42 |
43 |
    44 |
  • 1
  • 45 |
  • 2
  • 46 |
  • 3
  • 47 |
  • 4
  • 48 |
  • 5
  • 49 |
  • 6
  • 50 |
51 |
52 |
53 |
    54 |
  • 1
  • 55 |
  • 2
  • 56 |
  • 3
  • 57 |
  • 4
  • 58 |
  • 5
  • 59 |
  • 6
  • 60 |
61 |
62 |
63 |
    64 |
  • 65 | 无缝滚动第一行无缝滚动第一行2017-12-16 66 |
  • 67 |
  • 68 | 无缝滚动第二行无缝滚动第一行2017-12-16 69 |
  • 70 |
  • 71 | 无缝滚动第三行无缝滚动第一行2017-12-16 72 |
  • 73 |
  • 74 | 无缝滚动第四行无缝滚动第一行2017-12-16 75 |
  • 76 |
  • 77 | 无缝滚动第五行无缝滚动第一行2017-12-16 78 |
  • 79 |
  • 80 | 无缝滚动第六行无缝滚动第一行2017-12-16 81 |
  • 82 |
  • 83 | 无缝滚动第七行无缝滚动第一行2017-12-16 84 |
  • 85 |
  • 86 | 无缝滚动第八行无缝滚动第一行2017-12-16 87 |
  • 88 |
89 |
90 |
91 | 92 | 111 | 112 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seamscroll", 3 | "version": "0.0.12", 4 | "description": "js seamless scroll", 5 | "main": "build/seamscroll.min.js", 6 | "scripts": { 7 | "build": "node script/build.js", 8 | "build:debug": "node script/build-debug.js", 9 | "build:all": "npm run build &&npm run build:debug" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/chenxuan0000/seamless-scroll" 14 | }, 15 | "keywords": [ 16 | "without relying on", 17 | "js", 18 | "seamless-scroll" 19 | ], 20 | "author": "chenxuan", 21 | "license": "MIT", 22 | "dependencies": { 23 | "comutils": "^1.1.10" 24 | }, 25 | "devDependencies": { 26 | "babel-core": "^6.26.0", 27 | "babel-loader": "^7.1.2", 28 | "babel-preset-env": "^1.6.1", 29 | "chalk": "^2.3.0", 30 | "mocha": "^4.0.1", 31 | "rimraf": "^2.6.2", 32 | "ora": "^1.3.0", 33 | "webpack": "^3.8.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /script/build-debug.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ora = require('ora') 3 | const rm = require('rimraf') 4 | const chalk = require('chalk') 5 | const webpack = require('webpack') 6 | const pkg = require('../package.json') 7 | const rootPath = path.resolve(__dirname, '../') 8 | 9 | 10 | const config = { 11 | entry: path.resolve(rootPath, 'src', 'index.js'), 12 | output: { 13 | filename: `${pkg.name}.js`, 14 | path: path.resolve(rootPath, 'build'), 15 | library: `${pkg.name}`, 16 | libraryTarget: 'umd' 17 | }, 18 | module: { 19 | rules: [{ 20 | test: /\.js$/, 21 | loader: 'babel-loader' 22 | }] 23 | } 24 | } 25 | 26 | new Promise(() => { 27 | // 构建全量压缩包 28 | let building = ora('building...') 29 | building.start() 30 | rm(path.resolve(rootPath, 'build', `${pkg.name}.js`), err => { 31 | if (err) throw (err) 32 | webpack(config, function (err, stats) { 33 | if (err) throw (err) 34 | building.stop() 35 | process.stdout.write(stats.toString({ 36 | colors: true, 37 | modules: false, 38 | children: false, 39 | chunks: false, 40 | chunkModules: false 41 | }) + '\n\n') 42 | console.log(chalk.cyan(' Build complete.\n')) 43 | }) 44 | }) 45 | }) -------------------------------------------------------------------------------- /script/build.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ora = require('ora') 3 | const rm = require('rimraf') 4 | const chalk = require('chalk') 5 | const webpack = require('webpack') 6 | const pkg = require('../package.json') 7 | const rootPath = path.resolve(__dirname, '../') 8 | 9 | 10 | const config = { 11 | entry: path.resolve(rootPath, 'src', 'index.js'), 12 | output: { 13 | filename: `${pkg.name}.min.js`, 14 | path: path.resolve(rootPath, 'build'), 15 | library: `${pkg.name}`, 16 | libraryTarget: 'umd' 17 | }, 18 | module: { 19 | rules: [{ 20 | test: /\.js$/, 21 | loader: 'babel-loader' 22 | }] 23 | }, 24 | plugins: [ 25 | new webpack.optimize.UglifyJsPlugin({ 26 | compress: {screw_ie8: false}, 27 | mangle: {except: ['$']}, 28 | support_ie8: true, 29 | warnings: false 30 | }) 31 | ] 32 | } 33 | 34 | new Promise(() => { 35 | // 构建全量压缩包 36 | let building = ora('building...') 37 | building.start() 38 | rm(path.resolve(rootPath, 'build', `${pkg.name}.min.js`), err => { 39 | if (err) throw (err) 40 | webpack(config, function (err, stats) { 41 | if (err) throw (err) 42 | building.stop() 43 | process.stdout.write(stats.toString({ 44 | colors: true, 45 | modules: false, 46 | children: false, 47 | chunks: false, 48 | chunkModules: false 49 | }) + '\n\n') 50 | console.log(chalk.cyan(' Build complete.\n')) 51 | }) 52 | }) 53 | }) -------------------------------------------------------------------------------- /src/content/create.js: -------------------------------------------------------------------------------- 1 | import seamless from './seamless' 2 | 3 | const create = options => { 4 | return new seamless(options) 5 | } 6 | 7 | export default create 8 | -------------------------------------------------------------------------------- /src/content/event.js: -------------------------------------------------------------------------------- 1 | const addEventListener = (element, type, callback) => { 2 | if (element.addEventListener) { 3 | element.addEventListener(type, callback, false) 4 | } else if (element.attachEvent) { 5 | element.attachEvent('on' + type, callback) 6 | } else { 7 | element['on' + type] = callback 8 | } 9 | } 10 | 11 | export default addEventListener 12 | -------------------------------------------------------------------------------- /src/content/getCss.js: -------------------------------------------------------------------------------- 1 | const getStyle = (dom, name) => { 2 | let elem = dom.currentStyle 3 | ? dom.currentStyle 4 | : window.getComputedStyle(dom, null) 5 | return elem[name] 6 | } 7 | 8 | export default getStyle 9 | -------------------------------------------------------------------------------- /src/content/seamless.js: -------------------------------------------------------------------------------- 1 | require('comutils/animationFrame')() 2 | const copyObj = require('comutils/copyObj') 3 | import addEventListener from './event' 4 | import getStyle from './getCss' 5 | 6 | const defaultOptions = { 7 | step: 1, //步长 8 | hoverStop: true, //是否启用鼠标hover控制 9 | direction: 1, // 0 往下 1 往上 2向左 3向右 10 | singleHeight: 0, //单条数据高度有值hoverStop关闭 11 | singleWidth: 0, //单条数据宽度有值hoverStop关闭 12 | waitTime: 1000 //单步停止等待时间 13 | } 14 | 15 | const seamless = function(options) { 16 | this.options = copyObj({}, defaultOptions, options) 17 | let dom = this.options.dom 18 | if (!dom) throw new Error('you must set a dom') 19 | dom.style.position = 'relative' 20 | dom.style.overflow = 'hidden' 21 | this.reqFrame = null 22 | this.singleWaitTime = null // single 单步滚动的定时器 23 | this._top = 0 24 | this._left = 0 25 | this.isHover = false //_move()方法的开关 26 | dom.innerHTML += dom.innerHTML 27 | if (this.options.direction > 1) { 28 | //水平向滚动 29 | let child = dom.children, 30 | childFirst = child[0], 31 | len = child.length 32 | this._width = 33 | (childFirst.offsetWidth + 34 | this._getInt(childFirst, 'margin-left') + 35 | this._getInt(childFirst, 'margin-right')) * 36 | len 37 | dom.style.width = this._width + 'px' 38 | } 39 | this._move() 40 | this._bindEvent() 41 | } 42 | 43 | seamless.prototype = { 44 | _getInt(dom, name) { 45 | return parseInt(getStyle(dom, name)) 46 | }, 47 | _cancle() { 48 | cancelAnimationFrame(this.reqFrame || '') 49 | }, 50 | _bindEvent() { 51 | if (!this.options.hoverStop) return 52 | let that = this 53 | let dom = this.options.dom 54 | addEventListener(dom, 'mouseenter', function() { 55 | that.isHover = true // 关闭_move 56 | // 防止蛋疼的人频频hover进出单步滚动 导致定时器乱掉 57 | if (that.singleWaitTime) clearTimeout(that.singleWaitTime) 58 | that._cancle() 59 | }) 60 | addEventListener(dom, 'mouseleave', function() { 61 | that.isHover = false // 开启_move 62 | that._move() 63 | }) 64 | }, 65 | _move() { 66 | if (this.isHover) return 67 | this._cancle() 68 | let that = this 69 | let dom = this.options.dom 70 | this.reqFrame = requestAnimationFrame(function() { 71 | let h = dom.offsetHeight / 2 //实际高度 72 | let direction = that.options.direction //滚动方向 73 | if (direction === 1) { 74 | // 上 75 | if (Math.abs(that._top) >= h) that._top = 0 76 | that._top -= that.options.step 77 | dom.style.top = that._top + 'px' 78 | } else if (direction === 0) { 79 | // 下 80 | if (that._top >= 0) that._top = h * -1 81 | that._top += that.options.step 82 | dom.style.top = that._top + 'px' 83 | } else if (direction === 2) { 84 | // 左 85 | if (Math.abs(that._left) >= that._width / 2) that._left = 0 86 | that._left -= that.options.step 87 | dom.style.left = that._left + 'px' 88 | } else if (direction === 3) { 89 | // 右 90 | if (that._left >= 0) that._left = that._width / 2 * -1 91 | that._left += that.options.step 92 | dom.style.left = that._left + 'px' 93 | } 94 | that._judgeSingle() 95 | }) 96 | }, 97 | _judgeSingle() { 98 | let singleH = this.options.singleHeight 99 | let singleW = this.options.singleWidth 100 | if (this.singleWaitTime) clearTimeout(this.singleWaitTime) 101 | if (!!singleH) { 102 | //是否启动了单行暂停配置 103 | if (Math.abs(this._top) % singleH === 0) { 104 | // 符合条件暂停waitTime 105 | this.singleWaitTime = setTimeout(() => { 106 | this._move() 107 | }, this.options.waitTime) 108 | } else { 109 | this._move() 110 | } 111 | } else if (!!singleW) { 112 | if (Math.abs(this._left) % singleW === 0) { 113 | // 符合条件暂停waitTime 114 | this.singleWaitTime = setTimeout(() => { 115 | this._move() 116 | }, this.options.waitTime) 117 | } else { 118 | this._move() 119 | } 120 | } else { 121 | this._move() 122 | } 123 | } 124 | } 125 | 126 | export default seamless 127 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @desc webpack打包入口 3 | */ 4 | 5 | import init from './content/create' 6 | 7 | export { init } 8 | -------------------------------------------------------------------------------- /test/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Microsoft YaHei"; 3 | font-size: 16px; 4 | } 5 | .clearfix { 6 | zoom: 1; 7 | } 8 | 9 | .clearfix:after { 10 | display: block; 11 | height: 0; 12 | clear: both; 13 | content: '.'; 14 | visibillity: hidden; 15 | } 16 | .list li { 17 | line-height: 30px; 18 | margin-top: 10px; 19 | } 20 | .list .title { 21 | float: left; 22 | } 23 | .list .date { 24 | float: right; 25 | } 26 | /*demo1*/ 27 | .demo1 { 28 | position: relative; /*兼容ie7*/ 29 | width: 400px; 30 | height: 240px; 31 | overflow: hidden; 32 | } 33 | 34 | /*demo2*/ 35 | .demo2 { 36 | width: 600px; 37 | height: 100px; 38 | position: relative; 39 | overflow: hidden; 40 | margin-top: 100px; 41 | } 42 | .list2 li { 43 | float: left; 44 | width: 100px; 45 | height: 100px; 46 | margin-right: 20px; 47 | text-align: center; 48 | font-size: 20px; 49 | color: #fff; 50 | line-height:100px; 51 | background-color: #ccc; 52 | } -------------------------------------------------------------------------------- /test/reset.css: -------------------------------------------------------------------------------- 1 | /*! minireset.css v0.0.3 | MIT License | github.com/jgthms/minireset.css */ 2 | html, body, p, ol, ul, li, dl, dt, dd, blockquote, figure, fieldset, legend, textarea, pre, iframe, hr, h1, h2, h3, h4, h5, h6 { 3 | margin: 0; 4 | padding: 0 5 | } 6 | 7 | h1, h2, h3, h4, h5, h6 { 8 | font-size: 100%; 9 | font-weight: normal 10 | } 11 | 12 | ul { 13 | list-style: none 14 | } 15 | 16 | button, input, select, textarea { 17 | margin: 0 18 | } 19 | 20 | html { 21 | box-sizing: border-box 22 | } 23 | 24 | *, *:before, *:after { 25 | box-sizing: inherit 26 | } 27 | 28 | img, embed, iframe, object, audio, video { 29 | height: auto; 30 | max-width: 100% 31 | } 32 | 33 | iframe { 34 | border: 0 35 | } 36 | 37 | table { 38 | border-collapse: collapse; 39 | border-spacing: 0 40 | } 41 | 42 | td, th { 43 | padding: 0; 44 | text-align: left 45 | } -------------------------------------------------------------------------------- /test/timg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuan0000/seamless-scroll/1d89d55e4d671a8c591eb4f104d7d803a457cf98/test/timg.jpg --------------------------------------------------------------------------------