-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfi-ripple.js
128 lines (107 loc) · 3.33 KB
/
fi-ripple.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import './fi-ripple.css';
// default max ripple size
const MAX_SIZE = 300;
// px helper
const px = n => n.toString() + 'px';
// offsetX polyfill
const getOffset = (event, container) => {
if (!event.target) return event;
let target = event.target;
// direct click on the target
const targetOk = target.isSameNode(container);
if (event.offsetX !== undefined && targetOk) {
return { x: event.offsetX, y: event.offsetY };
}
// need to calculate the offset of the target
const offset = { x: 0, y: 0 };
while(!target.isSameNode(container) && target.offsetParent) {
offset.x += target.offsetLeft;
offset.y += target.offsetTop;
target = target.offsetParent;
}
offset.x = event.offsetX + offset.x;
offset.y = event.offsetY + offset.y;
return offset;
}
const calcRadius = (c, maxSize) => {
return (Math.min(Math.max(c.offsetHeight, c.offsetWidth), maxSize) - 2) * 0.85;
}
export class FiRipple {
constructor(el) {
// default settings
this.container = el;
// prepare ripple container
this.updateContainer();
el.addEventListener('mousedown', this.spawnRipple.bind(this), { passive: true });
}
spawnRipple(event) {
const container = this.container;
const { x, y } = getOffset(event, container);
const radius = calcRadius(container, event.maxSize || MAX_SIZE);
// create the ripple & prepare it
const ripple = document.createElement('div');
ripple.className = 'fi-ripple';
// calc the ripple size
Object.assign(ripple.style, {
transform: 'scale(0) translateZ(0)',
width: px(radius * 2),
height: px(radius * 2),
left: px(x - radius),
top: px(y - radius),
backgroundColor: event.color || this.backgroundColor,
});
// Append child when the browser expects it
window.requestAnimationFrame(() => {
container.appendChild(ripple);
// wait some time, so that the transition actually triggers
window.requestAnimationFrame(() => {
ripple.style.transform = `scale(1) translateZ(0)`;
});
});
// programmatic trigger
if (event instanceof MouseEvent !== true) {
if (event.persistent !== true) this.removeRipple(ripple);
return ripple;
}
// the next mouseup event should remove the ripple again
window.addEventListener('mouseup', this.removeRipple.bind(this, ripple), {
passive: true,
once: true,
});
return ripple;
}
removeRipple(ripple) {
if (!ripple) return;
// fade out ripple after some time
setTimeout(() => ripple.style.opacity = 0, 150);
// remove ripple after transition is done
setTimeout(() => {
window.requestAnimationFrame(() => {
this.container.removeChild(ripple);
});
}, 500);
}
updateContainer() {
this.container.classList.add('fi-ripple-container');
setTimeout(() => {
this.backgroundColor = window.getComputedStyle(this.container).color;
}, 0);
}
destroy() {
// TODO: do we need to remove the class? (causes weird ripple positioning)
// this.container.classList.remove('fi-ripple-container');
this.container.removeEventListener('mousedown', this.spawnRipple);
}
}
export default {
name: 'fi-ripple',
bind(el, binding) {
el._fiRipple = new FiRipple(el);
},
update(el) {
el._fiRipple.updateContainer();
},
unbind(el) {
el._fiRipple.destroy();
},
}