├── gulpfile.js ├── bower.json ├── package.json ├── .gitignore ├── README.md ├── dist ├── k-scroll.css └── k-scroll.js └── src ├── k-scroll.css └── k-scroll.js /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | 3 | var uglify = require('gulp-uglify'); 4 | var minifyCSS = require('gulp-minify-css'); 5 | 6 | gulp.task('default', function () { 7 | gulp.src('./src/k-scroll.js') 8 | .pipe(uglify()) 9 | .pipe(gulp.dest('./dist/')); 10 | 11 | gulp.src('./src/k-scroll.css') 12 | .pipe(minifyCSS()) 13 | .pipe(gulp.dest('./dist/')); 14 | }); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "k-scroll", 3 | "version": "0.0.1", 4 | "homepage": "https://github.com/kuroguo/k-scroll", 5 | "authors": [ 6 | "KuroGuo <413298956@qq.com>" 7 | ], 8 | "description": "High performance scroll component.", 9 | "main": "dist/k-scroll.js", 10 | "keywords": [ 11 | "k-scroll", 12 | "kuro" 13 | ], 14 | "license": "MIT", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "tests" 21 | ], 22 | "dependencies": { 23 | "k-drag": "*", 24 | "velocity": "~1.2.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "k-scroll", 3 | "version": "0.0.1", 4 | "description": "High performance scroll component.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/kuroguo/k-scroll" 8 | }, 9 | "keywords": [ 10 | "k-scroll", 11 | "kuro" 12 | ], 13 | "author": "kuro", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/kuroguo/k-scroll/issues" 17 | }, 18 | "homepage": "https://github.com/kuroguo/k-scroll", 19 | "devDependencies": { 20 | "gulp": "^3.8.10", 21 | "gulp-minify-css": "^0.3.13", 22 | "gulp-uglify": "^1.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | bower_components 31 | 32 | .sass-cache 33 | 34 | /example 35 | 36 | /static/stylesheets/temp 37 | 38 | /config.js 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## K-Scroll 2 | 3 | High performance scroll component. 4 | 5 | ### Install 6 | 7 | ```bash 8 | $ bower install k-scroll 9 | ``` 10 | 11 | ### Quick Example 12 | 13 | example 14 | 15 | ```html 16 | 17 |
18 | 25 |
26 | 27 | 28 | 29 | 30 | 43 | ``` 44 | -------------------------------------------------------------------------------- /dist/k-scroll.css: -------------------------------------------------------------------------------- 1 | .k-scroller-wrapper>.scroll-bar{-webkit-transition-property:border-right-width,opacity;-moz-transition-property:border-right-width,opacity;transition-property:border-right-width,opacity;-webkit-transition-duration:.2s,.2s;-moz-transition-duration:.2s,.2s;transition-duration:.2s,.2s;-webkit-transition-delay:.4s,1s;-moz-transition-delay:.4s,1s;transition-delay:.4s,1s;position:absolute;right:0;top:0;width:1rem;height:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;z-index:100;border-width:0 .3rem 0 .2rem;border-style:solid;border-left-color:transparent;border-right-color:rgba(0,0,0,.3);opacity:0}.k-scroller-wrapper.hover>.scroll-bar{opacity:1;-webkit-transition-duration:.2s,.2s;-moz-transition-duration:.2s,.2s;transition-duration:.2s,.2s;-webkit-transition-delay:.4s,0;-moz-transition-delay:.4s,0;transition-delay:.4s,0}.k-scroller-wrapper.dragging>.scroll-bar,.k-scroller-wrapper.scrolling>.scroll-bar,.k-scroller-wrapper.sliding>.scroll-bar{opacity:1;-webkit-transition-duration:.2s,0;-moz-transition-duration:.2s,0;transition-duration:.2s,0;-webkit-transition-delay:0,0;-moz-transition-delay:0,0;transition-delay:0,0}.k-scroller-wrapper>.scroll-bar.dragging,.k-scroller-wrapper>.scroll-bar.hover{opacity:1;border-right-width:.8rem;-webkit-transition-duration:.2s,0;-moz-transition-duration:.2s,0;transition-duration:.2s,0;-webkit-transition-delay:0,0;-moz-transition-delay:0,0;transition-delay:0,0}.k-scroller-wrapper>.scroll-bar.active,.k-scroller-wrapper>.scroll-bar.dragging{border-right-color:rgba(0,0,0,.4)} -------------------------------------------------------------------------------- /src/k-scroll.css: -------------------------------------------------------------------------------- 1 | .k-scroller-wrapper > .scroll-bar { 2 | -webkit-transition-property: border-right-width, opacity; 3 | -moz-transition-property: border-right-width, opacity; 4 | transition-property: border-right-width, opacity; 5 | -webkit-transition-duration: .2s, .2s; 6 | -moz-transition-duration: .2s, .2s; 7 | transition-duration: .2s, .2s; 8 | -webkit-transition-delay: .4s, 1s; 9 | -moz-transition-delay: .4s, 1s; 10 | transition-delay: .4s, 1s; 11 | position: absolute; 12 | right: 0; 13 | top: 0; 14 | width: 1rem; 15 | height: 0; 16 | -webkit-box-sizing:border-box; 17 | -moz-box-sizing:border-box; 18 | box-sizing:border-box; 19 | z-index: 100; 20 | border-width: 0; 21 | border-style: solid; 22 | border-left-width: .2rem; 23 | border-left-color: transparent; 24 | border-right-color: rgba(0, 0, 0, .3); 25 | border-right-width: .3rem; 26 | opacity: 0; 27 | } 28 | 29 | .k-scroller-wrapper.hover > .scroll-bar { 30 | opacity: 1; 31 | -webkit-transition-duration: .2s, .2s; 32 | -moz-transition-duration: .2s, .2s; 33 | transition-duration: .2s, .2s; 34 | -webkit-transition-delay: .4s, 0; 35 | -moz-transition-delay: .4s, 0; 36 | transition-delay: .4s, 0; 37 | } 38 | 39 | .k-scroller-wrapper.dragging > .scroll-bar, 40 | .k-scroller-wrapper.scrolling > .scroll-bar, 41 | .k-scroller-wrapper.sliding > .scroll-bar { 42 | opacity: 1; 43 | -webkit-transition-duration: .2s, 0; 44 | -moz-transition-duration: .2s, 0; 45 | transition-duration: .2s, 0; 46 | -webkit-transition-delay: 0, 0; 47 | -moz-transition-delay: 0, 0; 48 | transition-delay: 0, 0; 49 | } 50 | 51 | .k-scroller-wrapper > .scroll-bar.hover, 52 | .k-scroller-wrapper > .scroll-bar.dragging { 53 | opacity: 1; 54 | border-right-width: .8rem; 55 | -webkit-transition-duration: .2s, 0; 56 | -moz-transition-duration: .2s, 0; 57 | transition-duration: .2s, 0; 58 | -webkit-transition-delay: 0, 0; 59 | -moz-transition-delay: 0, 0; 60 | transition-delay: 0, 0; 61 | } 62 | 63 | .k-scroller-wrapper > .scroll-bar.active, 64 | .k-scroller-wrapper > .scroll-bar.dragging { 65 | border-right-color: rgba(0, 0, 0, .4); 66 | } -------------------------------------------------------------------------------- /dist/k-scroll.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"use strict";function n(e){return e.wheelDelta?e.wheelDelta/120:-(e.detail||0)/3}var r={};r.bind=function(r,a){function s(){Q.unbind(),U.unbind(),B.removeEventListener("mouseenter",T),B.removeEventListener("mousedown",T),B.removeEventListener("touchstart",T),B.removeEventListener("mouseenter",m),B.removeEventListener("mouseleave",L),B.removeEventListener("mousedown",D),B.removeEventListener("touchstart",D),e.removeEventListener("blur",D),B.removeEventListener("mousewheel",y),B.removeEventListener("touchstart",g),B.removeEventListener("mousedown",g),B.removeEventListener("k.dragstart",E),B.removeEventListener("k.drag",p),B.removeEventListener("touchend",f),B.removeEventListener("mouseup",f),B.removeEventListener("k.dragend",h),G.removeEventListener("mouseenter",o),G.removeEventListener("mouseleave",i),G.removeEventListener("touchstart",d),G.removeEventListener("mousedown",d),G.removeEventListener("touchend",c),G.removeEventListener("touchcancel",c),G.removeEventListener("k.dragstart",v),G.removeEventListener("k.drag",u),G.removeEventListener("k.dragend",l),e.removeEventListener("resize",w),t.removeEventListener("keydown",b)}function o(e){var t=e.currentTarget;t.classList.add("hover")}function i(e){var t=e.currentTarget;t.classList.remove("hover")}function d(e){e.preventDefault();var n=e.currentTarget;n.classList.add("active"),"mousedown"===e.type&&t.addEventListener("mouseup",c,!0)}function c(e){G.classList.remove("active"),"mouseup"===e.type&&t.removeEventListener("mouseup",c,!0)}function u(e){var t=A+e.deltaY*(C-F)/(F-K);t>q?t=q:H>t&&(t=H),V(t,!1,!1)}function v(e){var t=e.currentTarget;T(),A=I,t.classList.add("dragging")}function l(e){var t=e.currentTarget;t.classList.remove("dragging")}function m(e){x();var t=e.currentTarget;t.classList.add("hover")}function L(e){var t=e.currentTarget;t.classList.remove("hover")}function g(e){Z=!1,"mousedown"===e.type?Y.allowMouseDragScroll&&e.preventDefault():e.preventDefault()}function f(e){var t=e.currentTarget;Z||(R=0,j=null,t.classList.add("sliding"),M())}function E(e){if(!e.dragTarget.classList.contains("scroll-bar")){if(!Y.allowMouseDragScroll&&"mouse"===e.pointerType&&!e.ctrlKey)return void e.prevent();var t=e.currentTarget;e.state>0&&(t.classList.add("dragging"),Z=!0)}}function p(e){e.dragTarget.classList.contains("scroll-bar")||(I>q?e.vy<=0&&(e.stepY/=1+Math.abs(I-q)/14):H>I&&e.vy>=0&&(e.stepY/=1+Math.abs(I-H)/14),J=-e.vy,I-=e.stepY,V(I,!1,!1))}function h(e){if(!e.dragTarget.classList.contains("scroll-bar")){var t=e.currentTarget;t.classList.remove("dragging"),R=J=-e.vy,j=null,t.classList.add("sliding"),M()}}function y(e){if(!e.ctrlKey){T(),e.preventDefault();var t=n(e),r=I-t*Y.speed;r>q?r=q:H>r&&(r=H),J=0,V(r,!0,!0)}}function k(e){for(var t in e)void 0!==e[t]&&(Y[t]=e[t])}function b(e){if(Y.bindKey){switch(e.keyCode){case 33:I-=F-3;break;case 34:I+=F-3;break;case 38:I-=Y.speed;break;case 40:I+=Y.speed;break;default:return}T(),H>I?I=H:I>q&&(I=q),V(I,!0,!0)}}function w(){Y.checkOnResize&&"none"!==B.style.display&&(T(),I>q&&V(q))}function D(e){O&&(S(),"blur"!==e.type&&Math.abs(J)>.14&&(e.stopPropagation(),e.preventDefault())),J=0,B.classList.remove("sliding"),e.ctrlKey&&"mousedown"===e.type&&e.preventDefault()}function T(){var t=e.getComputedStyle(B),n=e.getComputedStyle(P);F=parseFloat(t.height),C=parseFloat(n.height),q=Math.max(0,C-F),z=F/C,z>=1&&(z=0),K=Math.max(F*z,56)}function M(t){var n,r,a;j&&(n=t-j,a=J*Math.pow(.95,n/(1e3/60)),r=I+(J+a)/2*n,V(r,!1,!1),J=a,I>q?0>=J?(J=(q-I)/200,0>R&&J>R?J=R:J>-.03&&(J=-.03)):(J-=3e-4*(I-q)*n,0>J&&(J=0)):H>I&&(J>=0?(J=(H-I)/200,R>0&&R>J?J=R:.03>J&&(J=.03)):(J-=3e-4*(I-H)*n,J>0&&(J=0)))),j=t,Math.abs(I-q)<.03?I=q:Math.abs(I-H)<.03&&(I=H),!B.classList.contains("dragging")&&(Math.abs(J)>.03||I>q||H>I)?O=e.requestAnimationFrame(M):(J=0,B.classList.remove("sliding"),e.cancelAnimationFrame(O),O=null)}function x(e){var t=I/(C-F);G.display=0>=z?"none":"block",Velocity.hook(G,"height",K+"px"),Velocity(G,"stop");var n=(F-K)*t+"px";e?Velocity(G,{translateY:n},{duration:Y.animationDuration,easing:Y.animationEasing}):Velocity.hook(G,"translateY",n)}function V(e,t,n,r,a){I=e,S(),t?Velocity(P,{translateY:-I+"px"},{duration:r||Y.animationDuration,easing:Y.animationEasing,begin:function(){B.classList.add("scrolling")},complete:function(){B.classList.remove("scrolling"),"function"==typeof a&&a.call(this)}}):(Velocity.hook(P,"translateY",-I+"px"),"function"==typeof a&&a.call(this)),x(n)}function S(){e.cancelAnimationFrame(O),O=null,Velocity(P,"stop")}var Y={animationDuration:200,animationEasing:[0,0,.58,1],speed:100,allowMouseDragScroll:!0,checkOnResize:!0,bindKey:!0};k(a);var C,F,K,q,z,A,j,O,R,Z,B=r,P=B.querySelector(".k-scroller"),G=t.createElement("span"),H=0,I=0,J=0;G.classList.add("scroll-bar"),B.appendChild(G);var N;"function"==typeof require?N=require("kDrag"):e.kDrag&&(N=e.kDrag);var Q=N.bind(B),U=N.bind(G);return B.addEventListener("mouseenter",T),B.addEventListener("mousedown",T),B.addEventListener("touchstart",T),B.addEventListener("mouseenter",m),B.addEventListener("mouseleave",L),B.addEventListener("mousedown",D),B.addEventListener("touchstart",D),e.addEventListener("blur",D),B.addEventListener("mousewheel",y),B.addEventListener("touchstart",g),B.addEventListener("mousedown",g),B.addEventListener("k.dragstart",E),B.addEventListener("k.drag",p),B.addEventListener("touchend",f),B.addEventListener("mouseup",f),B.addEventListener("k.dragend",h),G.addEventListener("mouseenter",o),G.addEventListener("mouseleave",i),G.addEventListener("touchstart",d),G.addEventListener("mousedown",d),G.addEventListener("touchend",c),G.addEventListener("touchcancel",c),G.addEventListener("k.dragstart",v),G.addEventListener("k.drag",u),G.addEventListener("k.dragend",l),e.addEventListener("resize",w),t.addEventListener("keydown",b),Velocity.hook(P,"translateZ","0.00001px"),Velocity.hook(G,"translateZ","0.00001px"),V(I,!1,!1),{unbind:s,configure:k,scrollTo:V,stopAnimation:S,refreshContext:T,resetscrollBarStyle:x,get maxScroll(){return q}}},"undefined"!=typeof module&&"object"==typeof exports?module.exports=r:"function"==typeof define&&define.amd?define(function(){return r}):e.kScroll=r}(window,document); -------------------------------------------------------------------------------- /src/k-scroll.js: -------------------------------------------------------------------------------- 1 | ;(function (window, document) { 'use strict'; 2 | var kScroll = {}; 3 | 4 | kScroll.bind = function (element, opts) { 5 | var options = { 6 | animationDuration: 200, 7 | animationEasing: [0, 0, 0.58, 1], 8 | speed: 100, 9 | allowMouseDragScroll: true, 10 | checkOnResize: true, 11 | bindKey: true // 绑定按键 12 | }; 13 | 14 | configure(opts); 15 | 16 | var wrapper = element; 17 | var scroller = wrapper.querySelector('.k-scroller'); 18 | var scrollBar = document.createElement('span'); 19 | var minScroll = 0; 20 | var scrollerHeight; 21 | var wrapperHeight; 22 | var scrollBarHeight; 23 | var maxScroll; 24 | var scrollBarHeightPercent; 25 | var scrollBarDragstartScrollTop; 26 | var lastFrameTime; 27 | var frameToken; 28 | var dragEndvScrollTop; 29 | var isDrag; 30 | 31 | var currentScrollTop = 0; // 当前纵向滚动值 32 | var vScrollTop = 0; // 纵向滚动速度 33 | 34 | scrollBar.classList.add('scroll-bar'); 35 | wrapper.appendChild(scrollBar); 36 | 37 | var kDrag; 38 | 39 | if (typeof require === 'function') 40 | kDrag = require('kDrag'); 41 | else if (window.kDrag) 42 | kDrag = window.kDrag; 43 | 44 | var wrapperDrag = kDrag.bind(wrapper); 45 | var scrollBarDrag = kDrag.bind(scrollBar); 46 | 47 | wrapper.addEventListener('mouseenter', refreshContext); 48 | wrapper.addEventListener('mousedown', refreshContext); 49 | wrapper.addEventListener('touchstart', refreshContext); 50 | 51 | wrapper.addEventListener('mouseenter', wrapperOnMouseenter); 52 | wrapper.addEventListener('mouseleave', wrapperOnMouseleave); 53 | 54 | wrapper.addEventListener('mousedown', _break); 55 | wrapper.addEventListener('touchstart', _break); 56 | window.addEventListener('blur', _break); 57 | 58 | wrapper.addEventListener('mousewheel', onMousewheel); 59 | 60 | wrapper.addEventListener('touchstart', wrapperOnPointerdown); 61 | wrapper.addEventListener('mousedown', wrapperOnPointerdown); 62 | 63 | wrapper.addEventListener('k.dragstart', wrapperOnDragstart); 64 | wrapper.addEventListener('k.drag', wrapperOnDrag); 65 | wrapper.addEventListener('touchend', wrapperOnPointerup); 66 | wrapper.addEventListener('mouseup', wrapperOnPointerup); 67 | wrapper.addEventListener('k.dragend', wrapperOnDragend); 68 | 69 | scrollBar.addEventListener('mouseenter', scrollBarOnMouseenter); 70 | scrollBar.addEventListener('mouseleave', scrollBarOnMouseleave); 71 | scrollBar.addEventListener('touchstart', scrollBarOnPointerdown); 72 | scrollBar.addEventListener('mousedown', scrollBarOnPointerdown); 73 | scrollBar.addEventListener('touchend', scrollBarOnPointerup); 74 | scrollBar.addEventListener('touchcancel', scrollBarOnPointerup); 75 | scrollBar.addEventListener('k.dragstart', scrollBarOnDragstart); 76 | scrollBar.addEventListener('k.drag', scrollBarOnDrag); 77 | scrollBar.addEventListener('k.dragend', scrollBarOnDragend); 78 | 79 | window.addEventListener('resize', resizeCheck); 80 | document.addEventListener('keydown', keyScroll); 81 | 82 | // 强制开启硬件加速 83 | Velocity.hook(scroller, "translateZ", '0.00001px'); 84 | Velocity.hook(scrollBar, "translateZ", '0.00001px'); 85 | scrollTo(currentScrollTop, false, false); 86 | 87 | return { 88 | unbind: unbind, 89 | configure: configure, 90 | scrollTo: scrollTo, 91 | stopAnimation: stopAnimation, 92 | refreshContext: refreshContext, 93 | resetscrollBarStyle: resetscrollBarStyle, 94 | get maxScroll() { 95 | return maxScroll; 96 | } 97 | }; 98 | 99 | function unbind() { 100 | wrapperDrag.unbind(); 101 | scrollBarDrag.unbind(); 102 | 103 | wrapper.removeEventListener('mouseenter', refreshContext); 104 | wrapper.removeEventListener('mousedown', refreshContext); 105 | wrapper.removeEventListener('touchstart', refreshContext); 106 | 107 | wrapper.removeEventListener('mouseenter', wrapperOnMouseenter); 108 | wrapper.removeEventListener('mouseleave', wrapperOnMouseleave); 109 | 110 | wrapper.removeEventListener('mousedown', _break); 111 | wrapper.removeEventListener('touchstart', _break); 112 | window.removeEventListener('blur', _break); 113 | 114 | wrapper.removeEventListener('mousewheel', onMousewheel); 115 | 116 | wrapper.removeEventListener('touchstart', wrapperOnPointerdown); 117 | wrapper.removeEventListener('mousedown', wrapperOnPointerdown); 118 | 119 | wrapper.removeEventListener('k.dragstart', wrapperOnDragstart); 120 | wrapper.removeEventListener('k.drag', wrapperOnDrag); 121 | wrapper.removeEventListener('touchend', wrapperOnPointerup); 122 | wrapper.removeEventListener('mouseup', wrapperOnPointerup); 123 | wrapper.removeEventListener('k.dragend', wrapperOnDragend); 124 | 125 | scrollBar.removeEventListener('mouseenter', scrollBarOnMouseenter); 126 | scrollBar.removeEventListener('mouseleave', scrollBarOnMouseleave); 127 | scrollBar.removeEventListener('touchstart', scrollBarOnPointerdown); 128 | scrollBar.removeEventListener('mousedown', scrollBarOnPointerdown); 129 | scrollBar.removeEventListener('touchend', scrollBarOnPointerup); 130 | scrollBar.removeEventListener('touchcancel', scrollBarOnPointerup); 131 | scrollBar.removeEventListener('k.dragstart', scrollBarOnDragstart); 132 | scrollBar.removeEventListener('k.drag', scrollBarOnDrag); 133 | scrollBar.removeEventListener('k.dragend', scrollBarOnDragend); 134 | 135 | window.removeEventListener('resize', resizeCheck); 136 | document.removeEventListener('keydown', keyScroll); 137 | } 138 | 139 | function scrollBarOnMouseenter(e) { 140 | var scrollBar = e.currentTarget; 141 | scrollBar.classList.add('hover'); 142 | } 143 | 144 | function scrollBarOnMouseleave(e) { 145 | var scrollBar = e.currentTarget; 146 | scrollBar.classList.remove('hover'); 147 | } 148 | 149 | function scrollBarOnPointerdown(e) { 150 | e.preventDefault(); 151 | var scrollBar = e.currentTarget; 152 | scrollBar.classList.add('active'); 153 | 154 | if (e.type === 'mousedown') { 155 | document.addEventListener('mouseup', scrollBarOnPointerup, true); 156 | } 157 | } 158 | 159 | function scrollBarOnPointerup(e) { 160 | scrollBar.classList.remove('active'); 161 | 162 | if (e.type === 'mouseup') { 163 | document.removeEventListener('mouseup', scrollBarOnPointerup, true); 164 | } 165 | } 166 | 167 | function scrollBarOnDrag(e) { 168 | var destScrollTop = scrollBarDragstartScrollTop + e.deltaY * (scrollerHeight - wrapperHeight) / (wrapperHeight - scrollBarHeight) 169 | if (destScrollTop > maxScroll) 170 | destScrollTop = maxScroll; 171 | else if (destScrollTop < minScroll) 172 | destScrollTop = minScroll; 173 | scrollTo(destScrollTop, false, false); 174 | } 175 | 176 | function scrollBarOnDragstart(e) { 177 | var scrollBar = e.currentTarget; 178 | refreshContext(); 179 | scrollBarDragstartScrollTop = currentScrollTop; 180 | scrollBar.classList.add('dragging'); 181 | } 182 | 183 | function scrollBarOnDragend(e) { 184 | var scrollBar = e.currentTarget; 185 | scrollBar.classList.remove('dragging'); 186 | } 187 | 188 | function wrapperOnMouseenter(e) { 189 | resetscrollBarStyle(); 190 | 191 | var wrapper = e.currentTarget; 192 | 193 | wrapper.classList.add('hover'); 194 | } 195 | 196 | function wrapperOnMouseleave(e) { 197 | var wrapper = e.currentTarget; 198 | 199 | wrapper.classList.remove('hover'); 200 | } 201 | 202 | function wrapperOnPointerdown(e) { 203 | isDrag = false; 204 | 205 | if (e.type === 'mousedown') { 206 | if (options.allowMouseDragScroll) { 207 | e.preventDefault(); 208 | } 209 | } 210 | else { 211 | e.preventDefault(); 212 | } 213 | } 214 | 215 | function wrapperOnPointerup(e) { 216 | var wrapper = e.currentTarget; 217 | 218 | if (!isDrag) { 219 | dragEndvScrollTop = 0; 220 | lastFrameTime = null; 221 | wrapper.classList.add('sliding'); 222 | slide(); 223 | } 224 | } 225 | 226 | function wrapperOnDragstart(e) { 227 | if (e.dragTarget.classList.contains('scroll-bar')) // 如果拖拽的是滚动条就返回 228 | return; 229 | else if (!options.allowMouseDragScroll && e.pointerType === 'mouse' && !e.ctrlKey) { 230 | e.prevent(); 231 | return; 232 | } 233 | 234 | var wrapper = e.currentTarget; 235 | 236 | if (e.state > 0) { 237 | wrapper.classList.add('dragging'); 238 | isDrag = true; 239 | } 240 | } 241 | 242 | function wrapperOnDrag(e) { 243 | if (e.dragTarget.classList.contains('scroll-bar')) 244 | return; 245 | 246 | if (currentScrollTop > maxScroll) { 247 | if (e.vy <= 0) { 248 | e.stepY /= 1 + Math.abs(currentScrollTop - maxScroll) / 14; 249 | } 250 | } else if (currentScrollTop < minScroll) { 251 | if (e.vy >= 0) { 252 | e.stepY /= 1 + Math.abs(currentScrollTop - minScroll) / 14; 253 | } 254 | } 255 | 256 | vScrollTop = -e.vy; 257 | currentScrollTop -= e.stepY; 258 | scrollTo(currentScrollTop, false, false); 259 | } 260 | 261 | function wrapperOnDragend(e) { 262 | if (e.dragTarget.classList.contains('scroll-bar')) 263 | return; 264 | 265 | var wrapper = e.currentTarget; 266 | 267 | wrapper.classList.remove('dragging'); 268 | 269 | dragEndvScrollTop = vScrollTop = -e.vy; 270 | 271 | lastFrameTime = null; 272 | wrapper.classList.add('sliding'); 273 | slide(); 274 | } 275 | 276 | function onMousewheel(e) { 277 | if (e.ctrlKey) 278 | return; 279 | refreshContext(); 280 | e.preventDefault(); 281 | var delta = computeMouseWheelDelta(e); 282 | var destScrollTop = currentScrollTop - delta * options.speed; 283 | 284 | if (destScrollTop > maxScroll) 285 | destScrollTop = maxScroll; 286 | else if (destScrollTop < minScroll) 287 | destScrollTop = minScroll; 288 | vScrollTop = 0; 289 | scrollTo(destScrollTop, true, true); 290 | } 291 | 292 | function configure(opts) { 293 | for (var key in opts) { 294 | if (opts[key] !== undefined) { 295 | options[key] = opts[key]; 296 | } 297 | } 298 | } 299 | 300 | function keyScroll(e) { 301 | if (!options.bindKey) 302 | return; 303 | 304 | // ←:37 ↑:38 →:39 ↓:40 pgup:33 pgdn:34 305 | switch (e.keyCode) { 306 | case 33: 307 | currentScrollTop -= wrapperHeight - 3; 308 | break; 309 | case 34: 310 | currentScrollTop += wrapperHeight - 3; 311 | break; 312 | case 38: 313 | currentScrollTop -= options.speed; 314 | break; 315 | case 40: 316 | currentScrollTop += options.speed; 317 | break; 318 | default: 319 | return; 320 | } 321 | 322 | refreshContext(); 323 | 324 | if (currentScrollTop < minScroll) 325 | currentScrollTop = minScroll; 326 | else if (currentScrollTop > maxScroll) 327 | currentScrollTop = maxScroll; 328 | 329 | scrollTo(currentScrollTop, true, true); 330 | } 331 | 332 | function resizeCheck() { 333 | if (!options.checkOnResize) 334 | return; 335 | 336 | if (wrapper.style.display === 'none') 337 | return; 338 | 339 | refreshContext(); 340 | if (currentScrollTop > maxScroll) { 341 | scrollTo(maxScroll); 342 | } 343 | } 344 | 345 | function _break(e, preventTap) { 346 | if (frameToken) { 347 | stopAnimation(); 348 | if (e.type !== 'blur' && Math.abs(vScrollTop) > 0.14) { 349 | e.stopPropagation(); 350 | e.preventDefault(); 351 | } 352 | } 353 | vScrollTop = 0; 354 | wrapper.classList.remove('sliding'); 355 | 356 | if (e.ctrlKey && e.type === 'mousedown') { 357 | e.preventDefault(); 358 | } 359 | } 360 | 361 | function refreshContext() { 362 | var wrapperStyle = window.getComputedStyle(wrapper); 363 | var scrollerStyle = window.getComputedStyle(scroller); 364 | 365 | wrapperHeight = parseFloat(wrapperStyle.height); 366 | scrollerHeight = parseFloat(scrollerStyle.height); 367 | maxScroll = Math.max(0, scrollerHeight - wrapperHeight); 368 | scrollBarHeightPercent = wrapperHeight / scrollerHeight; 369 | if (scrollBarHeightPercent >= 1) { 370 | scrollBarHeightPercent = 0; 371 | } 372 | 373 | scrollBarHeight = Math.max(wrapperHeight * scrollBarHeightPercent, 56); 374 | } 375 | 376 | function slide(time) { 377 | var timeSpan, destScrollTop, vScrollTopCurrent; 378 | 379 | if (lastFrameTime) { 380 | timeSpan = time - lastFrameTime; 381 | 382 | vScrollTopCurrent = vScrollTop * Math.pow(0.95, timeSpan / (1000 / 60)); 383 | 384 | destScrollTop = currentScrollTop + (vScrollTop + vScrollTopCurrent) / 2 * timeSpan; 385 | 386 | scrollTo(destScrollTop, false, false); 387 | 388 | vScrollTop = vScrollTopCurrent; 389 | 390 | if (currentScrollTop > maxScroll) { 391 | if (vScrollTop <= 0) { 392 | vScrollTop = (maxScroll - currentScrollTop) / 200; 393 | if (dragEndvScrollTop < 0 && vScrollTop > dragEndvScrollTop) { 394 | vScrollTop = dragEndvScrollTop; 395 | } else if (vScrollTop > -0.03) { 396 | vScrollTop = -0.03; 397 | } 398 | } else { 399 | vScrollTop -= (currentScrollTop - maxScroll) * 0.0003 * timeSpan; 400 | if (vScrollTop < 0) { 401 | vScrollTop = 0; 402 | } 403 | } 404 | } else if (currentScrollTop < minScroll) { 405 | if (vScrollTop >= 0) { 406 | vScrollTop = (minScroll - currentScrollTop) / 200; 407 | if (dragEndvScrollTop > 0 && vScrollTop < dragEndvScrollTop) { 408 | vScrollTop = dragEndvScrollTop; 409 | } else if (vScrollTop < 0.03) { 410 | vScrollTop = 0.03; 411 | } 412 | } else { 413 | vScrollTop -= (currentScrollTop - minScroll) * 0.0003 * timeSpan; 414 | if (vScrollTop > 0) { 415 | vScrollTop = 0 416 | } 417 | } 418 | } 419 | } 420 | 421 | lastFrameTime = time; 422 | 423 | if (Math.abs(currentScrollTop - maxScroll) < 0.03) { 424 | currentScrollTop = maxScroll; 425 | } else if (Math.abs(currentScrollTop - minScroll) < 0.03) { 426 | currentScrollTop = minScroll; 427 | } 428 | 429 | if (!wrapper.classList.contains('dragging') 430 | && (Math.abs(vScrollTop) > 0.03 431 | || currentScrollTop > maxScroll 432 | || currentScrollTop < minScroll)) { 433 | frameToken = window.requestAnimationFrame(slide); 434 | } else { 435 | vScrollTop = 0; 436 | wrapper.classList.remove('sliding'); 437 | window.cancelAnimationFrame(frameToken); 438 | frameToken = null; 439 | } 440 | } 441 | 442 | function resetscrollBarStyle(doAnimation) { 443 | var scrollPercent = currentScrollTop / (scrollerHeight - wrapperHeight); 444 | 445 | if (scrollBarHeightPercent <= 0) 446 | scrollBar.display = 'none'; 447 | else 448 | scrollBar.display = 'block'; 449 | 450 | Velocity.hook(scrollBar, 'height', scrollBarHeight + 'px'); 451 | 452 | Velocity(scrollBar, 'stop'); 453 | 454 | var translateY = ((wrapperHeight - scrollBarHeight) * scrollPercent) + 'px'; 455 | 456 | if (doAnimation) { 457 | Velocity(scrollBar, {translateY: translateY}, { 458 | duration: options.animationDuration, 459 | easing: options.animationEasing 460 | }); 461 | } else { 462 | Velocity.hook(scrollBar, 'translateY', translateY); 463 | } 464 | } 465 | 466 | function scrollTo(destScrollTop, doAnimation, scrollBarDoAnimation, duration, callback) { 467 | currentScrollTop = destScrollTop; 468 | 469 | stopAnimation(); 470 | 471 | if (doAnimation) { 472 | Velocity(scroller, { 473 | translateY: (-currentScrollTop) + 'px' 474 | }, { 475 | duration: duration || options.animationDuration, 476 | easing: options.animationEasing, 477 | begin: function () { 478 | wrapper.classList.add('scrolling'); 479 | }, 480 | complete: function () { 481 | wrapper.classList.remove('scrolling'); 482 | if (typeof callback === 'function') 483 | callback.call(this); 484 | } 485 | }); 486 | } else { 487 | Velocity.hook(scroller, "translateY", -currentScrollTop + 'px'); 488 | if (typeof callback === 'function') 489 | callback.call(this); 490 | } 491 | 492 | resetscrollBarStyle(scrollBarDoAnimation); 493 | } 494 | 495 | function stopAnimation() { 496 | window.cancelAnimationFrame(frameToken); 497 | frameToken = null; 498 | Velocity(scroller, 'stop'); 499 | } 500 | }; 501 | 502 | function computeMouseWheelDelta(eventArg) { 503 | return (eventArg.wheelDelta) ? eventArg.wheelDelta / 120 : -(eventArg.detail || 0) / 3; 504 | } 505 | 506 | if (typeof module !== 'undefined' && typeof exports === 'object') { 507 | module.exports = kScroll; 508 | } else if (typeof define === 'function' && define.amd) { 509 | define(function() { return kScroll; }); 510 | } else { 511 | window.kScroll = kScroll; 512 | } 513 | })(window, document); 514 | --------------------------------------------------------------------------------