1 |
|
2 | export const BunnyElement = {
|
3 |
|
4 | getCurrentDocumentPosition(top = true) {
|
5 |
|
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 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
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 |
|
58 | element = null;
|
59 | }
|
60 |
|
61 | if (element !== null && element.offsetParent === null) {
|
62 |
|
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 |
|
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 |
|
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 | };
|