├── docs
├── index.html
├── css
│ └── index.css
└── js
│ └── scrollToTop.js
├── README.md
└── scrollToTop.js
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 回到顶部
7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/docs/css/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | height: 20000px;
3 | background-color: #eee;
4 | margin: 0;
5 | }
6 |
7 | #toTop {
8 | position: fixed;
9 | right: 20px;
10 | bottom: 20px;
11 | margin-bottom: 20px;
12 | cursor: pointer;
13 | width: 0;
14 | height: 0;
15 | border-top: 30px solid transparent;
16 | border-bottom: 60px solid #3498db;
17 | border-left: 30px solid transparent;
18 | border-right: 30px solid transparent;
19 | }
20 |
21 | #toTop.backing {
22 | border-bottom: 60px solid #e74c3c;
23 | }
24 |
25 | .rule {
26 | height: 100px;
27 | width: 10%;
28 | line-height: 100px;
29 | text-align: center;
30 | color: #fff;
31 | background-color: #1abc9c;
32 | }
33 |
34 | .rule-list {
35 | list-style: none;
36 | margin: 0;
37 | padding: 0;
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scrollTop
2 |
3 | ## 介绍
4 |
5 | 原生 JavaScript 实现的回到顶部库,适应 PC 和移动,兼容到 IE7+。
6 |
7 | 效果演示:[https://mqyqingfeng.github.io/ScrollToTop/](https://mqyqingfeng.github.io/ScrollToTop/)
8 |
9 | ## 兼容性
10 |
11 | 移动和 PC ,其中 PC 端支持 IE7+
12 |
13 | ## 依赖
14 |
15 | 原生 JavaScript 实现,无依赖。
16 |
17 | ## 下载
18 |
19 | ```js
20 | git clone git@github.com:mqyqingfeng/ScrollToTop.git
21 | ```
22 |
23 | ## 使用
24 |
25 | ```html
26 |
27 | ```
28 |
29 | 或者
30 |
31 | ```js
32 | import ScrollToTop from 'path/ScrollToTop.js'
33 | ```
34 |
35 | ## 示例
36 |
37 | ```html
38 |
39 | ```
40 |
41 | ```css
42 | #toTop {
43 | position: fixed; right: 20px; bottom: 20px; margin-bottom: 20px; cursor: pointer; width: 0; height: 0; border-top: 30px solid transparent; border-bottom: 60px solid #3498db; border-left: 30px solid transparent; border-right: 30px solid transparent;
44 | }
45 | ```
46 |
47 | ```js
48 | var toTop = new ScrollToTop("#top");
49 | ```
50 |
51 | 你也可以传入参数:
52 |
53 | ```js
54 | var toTop = new ScrollToTop("#top", {
55 | // 滚动条向下滑动多少时,出现回到顶部按钮
56 | showWhen: 100,
57 | // 回到顶部的速度。
58 | speed: 100,
59 | // 元素淡入和淡出的速度
60 | fadeSpeed: 10
61 | });
62 | ```
63 |
64 | 注意:当要兼容 IE7 的时候,不能传入选择符字符串,直接传入元素:
65 |
66 | ```js
67 | var topElement = document.getElementById("toTop");
68 | var toTop = new ScrollToTop(topElement)
69 | ```
70 |
71 | ## API
72 |
73 | ### 初始化
74 |
75 | ```js
76 | var toTop = new ScrollToTop("selector", options);
77 | ```
78 |
79 | ### options
80 |
81 | **1.showWhen**
82 |
83 | 默认值为 100,表示滚动条向下滑动 100px 时,出现回到顶部按钮
84 |
85 | **2.speed**
86 |
87 | 回到顶部的速度。默认值为 100,数值越大,速度越快。
88 |
89 | 100 表示浏览器每次重绘,scrollTop 就减去 100px。
90 |
91 | **3.fadeSpeed**
92 |
93 | 元素淡入和淡出的速度。默认值为 10,数值越大,速度越快。
94 |
95 | 10 表示浏览器每次重绘,元素透明度以 10% 递增或者递减。
96 |
97 | ### backing
98 |
99 | 当回到顶部时,按钮元素会添加一个名为 backing 的类,当按钮元素隐藏时,该类会被删除。
100 |
101 | 可以使用此类名实现正在回到顶部时的效果。
102 |
--------------------------------------------------------------------------------
/scrollToTop.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var root = (typeof self == 'object' && self.self == self && self) ||
3 | (typeof global == 'object' && global.global == global && global) ||
4 | this || {};
5 |
6 | // requestAnimationFrame 兼容到 IE6
7 | var lastTime = 0;
8 | var vendors = ['webkit', 'moz'];
9 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
10 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
11 | window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || // Webkit中此取消方法的名字变了
12 | window[vendors[x] + 'CancelRequestAnimationFrame'];
13 | }
14 |
15 | if (!window.requestAnimationFrame) {
16 | window.requestAnimationFrame = function(callback, element) {
17 | var currTime = new Date().getTime();
18 | var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));
19 | var id = window.setTimeout(function() {
20 | callback(currTime + timeToCall);
21 | }, timeToCall);
22 | lastTime = currTime + timeToCall;
23 | return id;
24 | };
25 | }
26 | if (!window.cancelAnimationFrame) {
27 | window.cancelAnimationFrame = function(id) {
28 | clearTimeout(id);
29 | };
30 | }
31 |
32 | // bind 函数在 IE7-8 不能使用
33 | Function.prototype.bind = Function.prototype.bind || function (context) {
34 | if (typeof this !== "function") {
35 | throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
36 | }
37 |
38 | var self = this;
39 | var args = Array.prototype.slice.call(arguments, 1);
40 |
41 | var fNOP = function () {};
42 |
43 | var fBound = function () {
44 | var bindArgs = Array.prototype.slice.call(arguments);
45 | self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
46 | }
47 |
48 | fNOP.prototype = this.prototype;
49 | fBound.prototype = new fNOP();
50 | return fBound;
51 | }
52 |
53 |
54 | var util = {
55 | extend: function(target) {
56 | for (var i = 1, len = arguments.length; i < len; i++) {
57 | for (var prop in arguments[i]) {
58 | if (arguments[i].hasOwnProperty(prop)) {
59 | target[prop] = arguments[i][prop]
60 | }
61 | }
62 | }
63 |
64 | return target
65 | },
66 | getStyle: function(element, prop) {
67 | return element.currentStyle ? element.currentStyle[prop] : document.defaultView.getComputedStyle(element)[prop]
68 | },
69 | getScrollOffsets: function() {
70 | var w = window;
71 | if (w.pageXOffset != null) return { x: w.pageXOffset, y: w.pageYOffset };
72 | var d = w.document;
73 | if (document.compatMode == "CSS1Compat") {
74 | return {
75 | x: d.documentElement.scrollLeft,
76 | y: d.documentElement.scrollTop
77 | }
78 | }
79 | return { x: d.body.scrollLeft, y: d.body.scrollTop }
80 | },
81 | setOpacity: function(ele, opacity) {
82 | if (ele.style.opacity != undefined) {
83 | ele.style.opacity = opacity / 100;
84 | } else {
85 | // 兼容低版本 IE 浏览器
86 | ele.style.filter = "alpha(opacity=" + opacity + ")";
87 | }
88 | },
89 | fadeIn: function(element, speed) {
90 | var opacity = 0;
91 | util.setOpacity(element, 0)
92 | var timer;
93 |
94 | function step() {
95 | util.setOpacity(element, opacity += speed)
96 | if (opacity < 100) {
97 | timer = requestAnimationFrame(step);
98 | } else {
99 | cancelAnimationFrame(timer)
100 | }
101 | }
102 | requestAnimationFrame(step)
103 | },
104 | fadeOut: function(element, speed) {
105 | var opacity = 100;
106 | util.setOpacity(element, 100)
107 | var timer;
108 |
109 | function step() {
110 | util.setOpacity(element, opacity -= speed)
111 | if (opacity > 0) {
112 | timer = requestAnimationFrame(step);
113 | } else {
114 | cancelAnimationFrame(timer)
115 | }
116 | }
117 | requestAnimationFrame(step)
118 | },
119 | addEvent: function(element, type, fn) {
120 | if (document.addEventListener) {
121 | element.addEventListener(type, fn, false);
122 | return fn;
123 | } else if (document.attachEvent) {
124 | var bound = function() {
125 | return fn.apply(element, arguments)
126 | }
127 | element.attachEvent('on' + type, bound);
128 | return bound;
129 | }
130 | },
131 | indexOf: function(array, item) {
132 | var result = -1;
133 | for (var i = 0, len = array.length; i < len; i++) {
134 | if (array[i] === item) {
135 | result = i;
136 | break;
137 | }
138 | }
139 | return result;
140 | },
141 | addClass: function(element, className) {
142 | var classNames = element.className.split(/\s+/);
143 | if (util.indexOf(classNames, className) == -1) {
144 | classNames.push(className);
145 | }
146 | element.className = classNames.join(' ')
147 | },
148 | removeClass: function(element, className) {
149 | var classNames = element.className.split(/\s+/);
150 | var index = util.indexOf(classNames, className)
151 | if (index !== -1) {
152 | classNames.splice(index, 1);
153 | }
154 | element.className = classNames.join(' ')
155 | },
156 | supportTouch: function() {
157 | return 'ontouchstart' in window ||
158 | window.DocumentTouch && document instanceof window.DocumentTouch;
159 |
160 | },
161 | getTime: function() {
162 | return new Date().getTime();
163 | }
164 | }
165 |
166 | function ScrollToTop(element, options) {
167 | this.element = typeof element === 'string' ? document.querySelector(element) : element;
168 |
169 | this.options = util.extend({}, this.constructor.defaultOptions, options)
170 |
171 | this.init();
172 | }
173 |
174 | ScrollToTop.VERSION = '1.0.0';
175 |
176 | ScrollToTop.defaultOptions = {
177 | // 默认值为 100,表示滚动条向下滑动 100px 时,出现回到顶部按钮
178 | showWhen: 100,
179 | // 回到顶部的速度。默认值为 100,数值越大,速度越快。 100 表示浏览器每次重绘,scrollTop 就减去 100px。
180 | speed: 100,
181 | // 元素淡入和淡出的速度。默认值为 10,数值越大,速度越快。 10 表示浏览器每次重绘,元素透明度以 10% 递增或者递减。
182 | fadeSpeed: 10
183 | }
184 |
185 | var proto = ScrollToTop.prototype;
186 |
187 | proto.init = function() {
188 | this.hideElement();
189 | this.bindScrollEvent();
190 | this.bindToTopEvent();
191 | }
192 |
193 | proto.hideElement = function() {
194 | util.setOpacity(this.element, 0);
195 | this.status = "hide";
196 | }
197 |
198 | proto.bindScrollEvent = function() {
199 | var self = this;
200 | util.addEvent(window, "scroll", function() {
201 | if (util.getScrollOffsets().y > self.options.showWhen) {
202 | if (self.status == 'hide') {
203 | util.fadeIn(self.element, self.options.fadeSpeed)
204 | self.status = 'show';
205 | }
206 | } else {
207 | if (self.status == 'show') {
208 | util.fadeOut(self.element, self.options.fadeSpeed);
209 | self.status = 'hide';
210 | util.removeClass(self.element, 'backing')
211 | }
212 | }
213 | })
214 |
215 | }
216 |
217 | proto.handleBack = function() {
218 | var timer, self = this;
219 | util.addClass(self.element, 'backing');
220 | cancelAnimationFrame(timer);
221 | timer = requestAnimationFrame(function fn() {
222 | var oTop = document.body.scrollTop || document.documentElement.scrollTop;
223 | if (oTop > 0) {
224 | document.body.scrollTop = document.documentElement.scrollTop = oTop - self.options.speed;
225 | timer = requestAnimationFrame(fn);
226 | } else {
227 | cancelAnimationFrame(timer);
228 | }
229 | })
230 | }
231 |
232 | proto.bindToTopEvent = function() {
233 | var self = this;
234 |
235 | util.addEvent(self.element, "click", self.handleBack.bind(self))
236 |
237 | if (util.supportTouch()) {
238 | util.addEvent(self.element, "touchstart", function(e) {
239 | self._startX = e.touches[0].pageX;
240 | self._startY = e.touches[0].pageY;
241 | self._startTime = util.getTime();
242 | })
243 |
244 | util.addEvent(self.element, "touchmove", function(e) {
245 | self._moveX = e.touches[0].pageX;
246 | self._moveY = e.touches[0].pageY;
247 | })
248 |
249 | util.addEvent(self.element, "touchend", function(e) {
250 |
251 | var endTime = util.getTime();
252 | if (self._moveX !== null && Math.abs(self._moveX - self.startX) > 10 ||
253 | self._moveY !== null && Math.abs(self._moveY - self.startY) > 10) {
254 |
255 | } else {
256 | // 手指移动的距离小于 10 像素并且手指和屏幕的接触时间要小于 500 毫秒
257 | if (endTime - self._startTime < 500) {
258 | self.handleBack()
259 | }
260 | }
261 | })
262 |
263 | }
264 |
265 | }
266 |
267 | if (typeof exports != 'undefined' && !exports.nodeType) {
268 | if (typeof module != 'undefined' && !module.nodeType && module.exports) {
269 | exports = module.exports = ScrollToTop;
270 | }
271 | exports.ScrollToTop = ScrollToTop;
272 | } else {
273 | root.ScrollToTop = ScrollToTop;
274 | }
275 | }());
--------------------------------------------------------------------------------
/docs/js/scrollToTop.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var root = (typeof self == 'object' && self.self == self && self) ||
3 | (typeof global == 'object' && global.global == global && global) ||
4 | this || {};
5 |
6 | // requestAnimationFrame 兼容到 IE6
7 | var lastTime = 0;
8 | var vendors = ['webkit', 'moz'];
9 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
10 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
11 | window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || // Webkit中此取消方法的名字变了
12 | window[vendors[x] + 'CancelRequestAnimationFrame'];
13 | }
14 |
15 | if (!window.requestAnimationFrame) {
16 | window.requestAnimationFrame = function(callback, element) {
17 | var currTime = new Date().getTime();
18 | var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));
19 | var id = window.setTimeout(function() {
20 | callback(currTime + timeToCall);
21 | }, timeToCall);
22 | lastTime = currTime + timeToCall;
23 | return id;
24 | };
25 | }
26 | if (!window.cancelAnimationFrame) {
27 | window.cancelAnimationFrame = function(id) {
28 | clearTimeout(id);
29 | };
30 | }
31 |
32 | // bind 函数在 IE7-8 不能使用
33 | Function.prototype.bind = Function.prototype.bind || function (context) {
34 | if (typeof this !== "function") {
35 | throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
36 | }
37 |
38 | var self = this;
39 | var args = Array.prototype.slice.call(arguments, 1);
40 |
41 | var fNOP = function () {};
42 |
43 | var fBound = function () {
44 | var bindArgs = Array.prototype.slice.call(arguments);
45 | self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
46 | }
47 |
48 | fNOP.prototype = this.prototype;
49 | fBound.prototype = new fNOP();
50 | return fBound;
51 | }
52 |
53 |
54 | var util = {
55 | extend: function(target) {
56 | for (var i = 1, len = arguments.length; i < len; i++) {
57 | for (var prop in arguments[i]) {
58 | if (arguments[i].hasOwnProperty(prop)) {
59 | target[prop] = arguments[i][prop]
60 | }
61 | }
62 | }
63 |
64 | return target
65 | },
66 | getStyle: function(element, prop) {
67 | return element.currentStyle ? element.currentStyle[prop] : document.defaultView.getComputedStyle(element)[prop]
68 | },
69 | getScrollOffsets: function() {
70 | var w = window;
71 | if (w.pageXOffset != null) return { x: w.pageXOffset, y: w.pageYOffset };
72 | var d = w.document;
73 | if (document.compatMode == "CSS1Compat") {
74 | return {
75 | x: d.documentElement.scrollLeft,
76 | y: d.documentElement.scrollTop
77 | }
78 | }
79 | return { x: d.body.scrollLeft, y: d.body.scrollTop }
80 | },
81 | setOpacity: function(ele, opacity) {
82 | if (ele.style.opacity != undefined) {
83 | ele.style.opacity = opacity / 100;
84 | } else {
85 | // 兼容低版本 IE 浏览器
86 | ele.style.filter = "alpha(opacity=" + opacity + ")";
87 | }
88 | },
89 | fadeIn: function(element, speed) {
90 | var opacity = 0;
91 | util.setOpacity(element, 0)
92 | var timer;
93 |
94 | function step() {
95 | util.setOpacity(element, opacity += speed)
96 | if (opacity < 100) {
97 | timer = requestAnimationFrame(step);
98 | } else {
99 | cancelAnimationFrame(timer)
100 | }
101 | }
102 | requestAnimationFrame(step)
103 | },
104 | fadeOut: function(element, speed) {
105 | var opacity = 100;
106 | util.setOpacity(element, 100)
107 | var timer;
108 |
109 | function step() {
110 | util.setOpacity(element, opacity -= speed)
111 | if (opacity > 0) {
112 | timer = requestAnimationFrame(step);
113 | } else {
114 | cancelAnimationFrame(timer)
115 | }
116 | }
117 | requestAnimationFrame(step)
118 | },
119 | addEvent: function(element, type, fn) {
120 | if (document.addEventListener) {
121 | element.addEventListener(type, fn, false);
122 | return fn;
123 | } else if (document.attachEvent) {
124 | var bound = function() {
125 | return fn.apply(element, arguments)
126 | }
127 | element.attachEvent('on' + type, bound);
128 | return bound;
129 | }
130 | },
131 | indexOf: function(array, item) {
132 | var result = -1;
133 | for (var i = 0, len = array.length; i < len; i++) {
134 | if (array[i] === item) {
135 | result = i;
136 | break;
137 | }
138 | }
139 | return result;
140 | },
141 | addClass: function(element, className) {
142 | var classNames = element.className.split(/\s+/);
143 | if (util.indexOf(classNames, className) == -1) {
144 | classNames.push(className);
145 | }
146 | element.className = classNames.join(' ')
147 | },
148 | removeClass: function(element, className) {
149 | var classNames = element.className.split(/\s+/);
150 | var index = util.indexOf(classNames, className)
151 | if (index !== -1) {
152 | classNames.splice(index, 1);
153 | }
154 | element.className = classNames.join(' ')
155 | },
156 | supportTouch: function() {
157 | return 'ontouchstart' in window ||
158 | window.DocumentTouch && document instanceof window.DocumentTouch;
159 |
160 | },
161 | getTime: function() {
162 | return new Date().getTime();
163 | }
164 | }
165 |
166 | function ScrollToTop(element, options) {
167 | this.element = typeof element === 'string' ? document.querySelector(element) : element;
168 |
169 | this.options = util.extend({}, this.constructor.defaultOptions, options)
170 |
171 | this.init();
172 | }
173 |
174 | ScrollToTop.VERSION = '1.0.0';
175 |
176 | ScrollToTop.defaultOptions = {
177 | // 默认值为 100,表示滚动条向下滑动 100px 时,出现回到顶部按钮
178 | showWhen: 100,
179 | // 回到顶部的速度。默认值为 100,数值越大,速度越快。 100 表示浏览器每次重绘,scrollTop 就减去 100px。
180 | speed: 100,
181 | // 元素淡入和淡出的速度。默认值为 10,数值越大,速度越快。 10 表示浏览器每次重绘,元素透明度以 10% 递增或者递减。
182 | fadeSpeed: 10
183 | }
184 |
185 | var proto = ScrollToTop.prototype;
186 |
187 | proto.init = function() {
188 | this.hideElement();
189 | this.bindScrollEvent();
190 | this.bindToTopEvent();
191 | }
192 |
193 | proto.hideElement = function() {
194 | util.setOpacity(this.element, 0);
195 | this.status = "hide";
196 | }
197 |
198 | proto.bindScrollEvent = function() {
199 | var self = this;
200 | util.addEvent(window, "scroll", function() {
201 | if (util.getScrollOffsets().y > self.options.showWhen) {
202 | if (self.status == 'hide') {
203 | util.fadeIn(self.element, self.options.fadeSpeed)
204 | self.status = 'show';
205 | }
206 | } else {
207 | if (self.status == 'show') {
208 | util.fadeOut(self.element, self.options.fadeSpeed);
209 | self.status = 'hide';
210 | util.removeClass(self.element, 'backing')
211 | }
212 | }
213 | })
214 |
215 | }
216 |
217 | proto.handleBack = function() {
218 | var timer, self = this;
219 | util.addClass(self.element, 'backing');
220 | cancelAnimationFrame(timer);
221 | timer = requestAnimationFrame(function fn() {
222 | var oTop = document.body.scrollTop || document.documentElement.scrollTop;
223 | if (oTop > 0) {
224 | document.body.scrollTop = document.documentElement.scrollTop = oTop - self.options.speed;
225 | timer = requestAnimationFrame(fn);
226 | } else {
227 | cancelAnimationFrame(timer);
228 | }
229 | })
230 | }
231 |
232 | proto.bindToTopEvent = function() {
233 | var self = this;
234 |
235 | util.addEvent(self.element, "click", self.handleBack.bind(self))
236 |
237 | if (util.supportTouch()) {
238 | util.addEvent(self.element, "touchstart", function(e) {
239 | self._startX = e.touches[0].pageX;
240 | self._startY = e.touches[0].pageY;
241 | self._startTime = util.getTime();
242 | })
243 |
244 | util.addEvent(self.element, "touchmove", function(e) {
245 | self._moveX = e.touches[0].pageX;
246 | self._moveY = e.touches[0].pageY;
247 | })
248 |
249 | util.addEvent(self.element, "touchend", function(e) {
250 |
251 | var endTime = util.getTime();
252 | if (self._moveX !== null && Math.abs(self._moveX - self.startX) > 10 ||
253 | self._moveY !== null && Math.abs(self._moveY - self.startY) > 10) {
254 |
255 | } else {
256 | // 手指移动的距离小于 10 像素并且手指和屏幕的接触时间要小于 500 毫秒
257 | if (endTime - self._startTime < 500) {
258 | self.handleBack()
259 | }
260 | }
261 | })
262 |
263 | }
264 |
265 | }
266 |
267 | if (typeof exports != 'undefined' && !exports.nodeType) {
268 | if (typeof module != 'undefined' && !module.nodeType && module.exports) {
269 | exports = module.exports = ScrollToTop;
270 | }
271 | exports.ScrollToTop = ScrollToTop;
272 | } else {
273 | root.ScrollToTop = ScrollToTop;
274 | }
275 | }());
--------------------------------------------------------------------------------