UNPKG

5.28 kBJavaScriptView Raw
1
2export const BunnyElement = {
3
4 getCurrentDocumentPosition(top = true) {
5 //return Math.abs(document.body.getBoundingClientRect().y);
6 return top ? window.scrollY : window.scrollY + window.innerHeight;
7 },
8
9 getPosition(el, top = true) {
10 let curTop = 0;
11 const originalEl = el;
12 if (el.offsetParent) {
13 do {
14 curTop += el.offsetTop;
15 } while (el = el.offsetParent);
16 }
17 if (!top) {
18 curTop += originalEl.offsetHeight;
19 }
20 return curTop;
21 },
22
23 isInViewport(el, offset = 0, top = false) {
24 const docPos = this.getCurrentDocumentPosition(top);
25 const elPos = this.getPosition(el, top);
26 return elPos + offset <= docPos;
27 },
28
29 scrollToIfNeeded(target, viewportOffset = 0, viewportTop = false, duration = 500, scrollOffset = 0) {
30 if (!this.isInViewport(target, viewportOffset, viewportTop)) {
31 this.scrollTo(target, duration, scrollOffset);
32 }
33 },
34
35 /**
36 * Smooth scrolling to DOM element or to relative window position
37 * If target is string it should be CSS selector
38 * If target is object it should be DOM element
39 * If target is number - it is used to relatively scroll X pixels form current position
40 *
41 * Based on https://www.sitepoint.com/smooth-scrolling-vanilla-javascript/
42 *
43 * @param {HTMLElement, string, number} target
44 * @param {Number|function} duration
45 * @param {Number} offset
46 * @param {HTMLElement} rootElement
47 */
48 scrollTo(target, duration = 500, offset = 0, rootElement = window) {
49 return new Promise(onAnimationEnd => {
50
51 let element;
52 if (typeof target === 'string') {
53 element = document.querySelector(target);
54 } else if (typeof target === 'object') {
55 element = target;
56 } else {
57 // number
58 element = null;
59 }
60
61 if (element !== null && element.offsetParent === null) {
62 // element is not visible, scroll to top of parent element
63 element = element.parentNode;
64 }
65
66 const start = rootElement === window ? window.pageYOffset : rootElement.scrollTop;
67 let distance = 0;
68 if (element !== null) {
69 distance = element.getBoundingClientRect().top;
70 } else {
71 // number
72 distance = target;
73 }
74
75 distance = distance + offset;
76
77 if (typeof duration === 'function') {
78 duration = duration(distance);
79 }
80
81 let timeStart = 0;
82 let timeElapsed = 0;
83
84 requestAnimationFrame( time => {
85 timeStart = time;
86 loop(time);
87 });
88
89 function setScrollYPosition(el, y) {
90 if (el === window) {
91 window.scrollTo(0, y);
92 } else {
93 el.scrollTop = y;
94 }
95 }
96
97 function loop(time) {
98 timeElapsed = time - timeStart;
99 setScrollYPosition(rootElement, easeInOutQuad(timeElapsed, start, distance, duration));
100 if (timeElapsed < duration) {
101 requestAnimationFrame(loop);
102 } else {
103 end();
104 }
105 }
106
107 function end() {
108 setScrollYPosition(rootElement, start + distance);
109 onAnimationEnd();
110 }
111
112 // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/
113 function easeInOutQuad(t, b, c, d) {
114 t /= d / 2;
115 if (t < 1) return c / 2 * t * t + b;
116 t--;
117 return -c / 2 * (t * (t - 2) - 1) + b;
118 }
119 });
120 },
121
122 hide(element) {
123 return new Promise(resolve => {
124 element.style.opacity = 0;
125 element.style.overflow = 'hidden';
126 const steps = 40;
127 const step_delay_ms = 10;
128 const height = element.offsetHeight;
129 const height_per_step = Math.round(height / steps);
130 element._originalHeight = height;
131 for (let k = 1; k <= steps; k++) {
132 if (k === steps) {
133 setTimeout(() => {
134 element.style.display = 'none';
135 element.style.height = '0px';
136 resolve();
137 }, step_delay_ms * k)
138 } else {
139 setTimeout(() => {
140 element.style.height = height_per_step * (steps - k) + 'px';
141 }, step_delay_ms * k);
142 }
143 }
144 })
145 },
146
147 show(element) {
148 if (element._originalHeight === undefined) {
149 throw new Error('element._originalHeight is undefined. Save original height when hiding element or use BunnyElement.hide()');
150 }
151 return new Promise(resolve => {
152 element.style.display = '';
153 const steps = 40;
154 const step_delay_ms = 10;
155 const height = element._originalHeight;
156 const height_per_step = Math.round(height / steps);
157 delete element._originalHeight;
158 for (let k = 1; k <= steps; k++) {
159 if (k === steps) {
160 setTimeout(() => {
161 element.style.opacity = 1;
162 element.style.height = '';
163 element.style.overflow = '';
164 resolve();
165 }, step_delay_ms * k)
166 } else {
167 setTimeout(() => {
168 element.style.height = height_per_step * k + 'px';
169 }, step_delay_ms * k);
170 }
171 }
172 })
173 },
174
175 remove(element) {
176 element.parentNode.removeChild(element);
177 }
178
179};