Skip to content

Commit 08ea631

Browse files
committed
recreate ripple component and add a global toggle to it
1 parent 8db86ef commit 08ea631

File tree

4 files changed

+195
-35
lines changed

4 files changed

+195
-35
lines changed

src/core/components/mdInkRipple/index.js

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'scopedQuerySelectorShim/dist/scopedQuerySelectorShim';
2-
import './mdInkRipple.vue';
2+
import mdInkRipple from './mdInkRipple.vue';
33

44
export default function install(Vue) {
55
let rippleParentClass = 'md-ink-ripple';
@@ -116,31 +116,5 @@ export default function install(Vue) {
116116
});
117117
});
118118

119-
Vue.component('md-ink-ripple', {
120-
props: {
121-
mdDisabled: Boolean
122-
},
123-
render(createElement) {
124-
return createElement('div', {
125-
staticClass: 'md-ink-ripple'
126-
});
127-
},
128-
watch: {
129-
mdDisabled() {
130-
if (this.mdDisabled) {
131-
unregisterMouseEvent(this.$el.parentNode);
132-
} else {
133-
createRipple(this.$el.parentNode, this.$el);
134-
}
135-
}
136-
},
137-
mounted() {
138-
if (!this.mdDisabled) {
139-
createRipple(this.$el.parentNode, this.$el);
140-
}
141-
},
142-
destroyed() {
143-
unregisterMouseEvent(this.$el.parentNode);
144-
}
145-
});
119+
Vue.component('md-ink-ripple', Vue.extend(mdInkRipple));
146120
}

src/core/components/mdInkRipple/mdInkRipple.scss

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,32 @@
88
right: 0;
99
bottom: 0;
1010
left: 0;
11-
mask-image: radial-gradient(circle, white 100%, black 100%);
11+
-webkit-mask-image: radial-gradient(circle, white 100%, black 100%);
1212
transition: $swift-ease-in;
1313
}
1414

1515
.md-ripple {
1616
position: absolute;
17-
transform: scale(0);
1817
background-color: currentColor;
19-
opacity: .26;
2018
border-radius: 50%;
19+
opacity: .2;
20+
transform: scale(0) translateZ(0);
21+
transition: none;
22+
will-change: background-color, opacity, transform, width, height, top, left;
23+
24+
&.md-fadeout {
25+
opacity: 0;
26+
transition: $swift-ease-out;
27+
transition-duration: .6s;
28+
}
2129

2230
&.md-active {
23-
animation: ripple 1s $swift-ease-out-timing-function;
31+
animation: ripple 1s $swift-ease-out-timing-function forwards;
2432
}
2533
}
2634

2735
@keyframes ripple {
2836
to {
29-
transform: scale(1.5);
30-
opacity: 0;
37+
transform: scale(2.2) translateZ(0);
3138
}
3239
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,179 @@
1+
<template>
2+
<div class="md-ink-ripple" v-if="!disabled">
3+
<div class="md-ripple" :class="classes" :style="styles" ref="ripple"></div>
4+
</div>
5+
</template>
6+
17
<style lang="scss" src="./mdInkRipple.scss"></style>
8+
9+
<script>
10+
export default {
11+
props: {
12+
mdDisabled: Boolean
13+
},
14+
data: () => ({
15+
rippleElement: null,
16+
parentElement: null,
17+
parentDimensions: {
18+
width: null,
19+
height: null,
20+
top: null,
21+
left: null
22+
},
23+
awaitingComplete: false,
24+
hasCompleted: false,
25+
fadeOut: false,
26+
active: false
27+
}),
28+
computed: {
29+
classes() {
30+
return {
31+
'md-fadeout': this.fadeOut,
32+
'md-active': this.active
33+
};
34+
},
35+
styles() {
36+
return {
37+
width: this.parentDimensions.width,
38+
height: this.parentDimensions.height,
39+
top: this.parentDimensions.top,
40+
left: this.parentDimensions.left
41+
};
42+
},
43+
disabled() {
44+
return this.mdDisabled || !this.$material.inkRipple;
45+
}
46+
},
47+
watch: {
48+
disabled(disabled) {
49+
if (!disabled) {
50+
this.init();
51+
} else {
52+
this.destroy();
53+
}
54+
}
55+
},
56+
methods: {
57+
checkAvailablePositions(element) {
58+
const availablePositions = ['relative', 'absolute', 'fixed'];
59+
60+
return availablePositions.indexOf(getComputedStyle(element).position) > -1;
61+
},
62+
getClosestPositionedParent(element) {
63+
const parent = element.parentNode;
64+
65+
if (!element || !parent || parent.tagName.toLowerCase() === 'body') {
66+
return false;
67+
}
68+
69+
if (this.checkAvailablePositions(element)) {
70+
return element;
71+
}
72+
73+
return this.getClosestPositionedParent(element.parentNode);
74+
},
75+
getParentSize() {
76+
const parent = this.parentElement;
77+
78+
return Math.round(Math.max(parent.offsetWidth, parent.offsetHeight)) + 'px';
79+
},
80+
getClickPosition(event) {
81+
const rect = this.parentElement.getBoundingClientRect();
82+
const top = event.pageY - rect.top - this.$refs.ripple.offsetHeight / 2 - document.body.scrollTop + 'px';
83+
const left = event.pageX - rect.left - this.$refs.ripple.offsetWidth / 2 - document.body.scrollLeft + 'px';
84+
85+
return {
86+
top,
87+
left
88+
};
89+
},
90+
setDimensions() {
91+
const size = this.getParentSize();
92+
93+
this.parentDimensions.width = size;
94+
this.parentDimensions.height = size;
95+
},
96+
setPositions(event) {
97+
const positions = this.getClickPosition(event);
98+
99+
this.parentDimensions.top = positions.top;
100+
this.parentDimensions.left = positions.left;
101+
},
102+
clearState() {
103+
this.active = false;
104+
this.fadeOut = false;
105+
this.hasCompleted = false;
106+
this.setDimensions();
107+
window.clearTimeout(this.awaitingComplete);
108+
document.body.removeEventListener('mouseup', this.endRipple);
109+
},
110+
startRipple(event) {
111+
window.requestAnimationFrame(() => {
112+
this.clearState();
113+
this.awaitingComplete = window.setTimeout(() => {
114+
this.hasCompleted = true;
115+
}, 400);
116+
117+
document.body.addEventListener('mouseup', this.endRipple);
118+
119+
this.setPositions(event);
120+
121+
window.setTimeout(() => {
122+
this.active = true;
123+
});
124+
});
125+
},
126+
endRipple() {
127+
if (this.hasCompleted) {
128+
this.fadeOut = true;
129+
} else {
130+
this.awaitingComplete = window.setTimeout(() => {
131+
this.fadeOut = true;
132+
}, 200);
133+
}
134+
135+
document.body.removeEventListener('mouseup', this.endRipple);
136+
},
137+
registerMouseEvent() {
138+
this.parentElement.addEventListener('mousedown', this.startRipple);
139+
},
140+
unregisterMouseEvent() {
141+
if (this.parentElement) {
142+
this.parentElement.removeEventListener('mousedown', this.startRipple);
143+
document.body.removeEventListener('mouseup', this.endRipple);
144+
}
145+
},
146+
init() {
147+
this.$nextTick(() => {
148+
this.rippleElement = this.$el;
149+
this.parentElement = this.getClosestPositionedParent(this.$el.parentNode);
150+
151+
if (!this.parentElement) {
152+
this.$destroy();
153+
} else {
154+
this.rippleElement.parentNode.removeChild(this.rippleElement);
155+
this.parentElement.appendChild(this.rippleElement);
156+
this.registerMouseEvent();
157+
this.setDimensions();
158+
}
159+
});
160+
},
161+
destroy() {
162+
if (this.rippleElement && this.rippleElement.parentNode) {
163+
this.unregisterMouseEvent();
164+
this.rippleElement.parentNode.removeChild(this.rippleElement);
165+
}
166+
}
167+
},
168+
mounted() {
169+
if (!this.disabled) {
170+
this.init();
171+
} else {
172+
this.destroy();
173+
}
174+
},
175+
beforeDestroy() {
176+
this.destroy();
177+
}
178+
};
179+
</script>

src/core/components/mdTheme/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ export default function install(Vue) {
121121
Vue.material = new Vue({
122122
data: () => ({
123123
styles: [],
124-
currentTheme: null
124+
currentTheme: null,
125+
inkRipple: true
125126
}),
126127
methods: {
127128
registerTheme(name, spec) {

0 commit comments

Comments
 (0)