From aa93200a08ed068ea92d0ab72bf5d5e1bc0be36d Mon Sep 17 00:00:00 2001 From: nekosaur Date: Thu, 19 Apr 2018 10:59:44 +0200 Subject: [PATCH 1/6] fix: decreased hide timeout --- src/directives/ripple.ts | 54 ++++++++++++++++++++++------- src/stylus/components/_ripples.styl | 8 +++-- src/stylus/settings/_variables.styl | 3 +- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/directives/ripple.ts b/src/directives/ripple.ts index a50385432a3..3b8dad36d85 100644 --- a/src/directives/ripple.ts +++ b/src/directives/ripple.ts @@ -1,10 +1,14 @@ import { VNodeDirective } from 'vue' -function style (el: HTMLElement, value: string) { +function transform (el: HTMLElement, value: string) { el.style['transform'] = value el.style['webkitTransform'] = value } +function opacity (el: HTMLElement, value: number) { + el.style['opacity'] = value.toString() +} + declare global { interface Element { getElementsByClassName(classNames: string): NodeListOf @@ -15,6 +19,7 @@ declare global { enabled?: boolean centered?: boolean class?: string + circle?: boolean } } } @@ -22,6 +27,7 @@ declare global { interface RippleOptions { class?: string center?: boolean + circle?: boolean } const ripple = { @@ -40,28 +46,49 @@ const ripple = { container.className += ` ${value.class}` } - const size = Math.max(el.clientWidth, el.clientHeight) * (value.center ? 1 : 2) - const halfSize = size / 2 + const offset = el.getBoundingClientRect() + const localX = e.clientX - offset.left + const localY = e.clientY - offset.top + + let radius = 0, scale = 0.3 + if (el._ripple.circle) { + scale = 0.15 + radius = el.clientWidth / 2 + radius = radius + Math.sqrt((localX - radius)**2 + (localY - radius)**2) / 4 + } else { + radius = Math.sqrt(el.clientWidth**2 + el.clientHeight**2) / 2 + } + animation.className = 'v-ripple__animation' - animation.style.width = `${size}px` - animation.style.height = `${size}px` + animation.style.width = `${radius * 2}px` + animation.style.height = animation.style.width el.appendChild(container) const computed = window.getComputedStyle(el) if (computed.position !== 'absolute' && computed.position !== 'fixed') el.style.position = 'relative' - const offset = el.getBoundingClientRect() - const x = value.center ? 0 : e.clientX - offset.left - halfSize - const y = value.center ? 0 : e.clientY - offset.top - halfSize + const x = value.center ? '50%' : `${localX}px` + const y = value.center ? '50%' : `${localY}px` + const centerX = `${el.clientWidth / 2}px` + const centerY = `${el.clientHeight / 2}px` animation.classList.add('v-ripple__animation--enter') animation.classList.add('v-ripple__animation--visible') - style(animation, `translate(${x}px, ${y}px) scale3d(0, 0, 0)`) + transform(animation, `translate(${x}px, ${y}px) scale3d(${scale},${scale},${scale})`) + opacity(animation, 0) animation.dataset.activated = String(performance.now()) setTimeout(() => { - animation.classList.remove('v-ripple__animation--enter') - style(animation, `translate(${x}px, ${y}px) scale3d(1, 1, 1)`) + animation.classList.remove('ripple__animation--enter') + animation.classList.add('ripple__animation--in') + transform(animation, `translate(${centerX}, ${centerY}) scale3d(0.99,0.99,0.99)`) + opacity(animation, 0.25) + + setTimeout(() => { + animation.classList.remove('ripple__animation--in') + animation.classList.add('ripple__animation--out') + opacity(animation, 0) + }, 250) }, 0) }, @@ -77,7 +104,7 @@ const ripple = { else animation.dataset.isHiding = 'true' const diff = performance.now() - Number(animation.dataset.activated) - let delay = Math.max(300 - diff, 0) + let delay = Math.max(700 - diff, 0) setTimeout(() => { animation.classList.remove('v-ripple__animation--visible') @@ -124,6 +151,9 @@ function updateRipple (el: HTMLElement, binding: VNodeDirective, wasEnabled: boo if (value.class) { el._ripple.class = binding.value.class } + if (value.circle) { + el._ripple.circle = value.circle + } if (enabled && !wasEnabled) { if ('ontouchstart' in window) { el.addEventListener('touchend', rippleHide, false) diff --git a/src/stylus/components/_ripples.styl b/src/stylus/components/_ripples.styl index dee52198245..4c67ae4ae57 100644 --- a/src/stylus/components/_ripples.styl +++ b/src/stylus/components/_ripples.styl @@ -20,7 +20,6 @@ border-radius: 50% background: currentColor opacity: 0 - transition: $ripple-animation-transition pointer-events: none overflow: hidden will-change: transform, opacity @@ -28,5 +27,8 @@ &--enter transition: none - &--visible - opacity: $ripple-animation-visible-opacity + &--in + transition: $ripple-animation-transition-in + + &--out + transition: $ripple-animation-transition-out diff --git a/src/stylus/settings/_variables.styl b/src/stylus/settings/_variables.styl index 67994247043..75d75f617e5 100755 --- a/src/stylus/settings/_variables.styl +++ b/src/stylus/settings/_variables.styl @@ -303,5 +303,6 @@ $input-group-text-field-label-top := 18px // ============================================================ // Ripple animation -$ripple-animation-transition := .3s $transition.linear-out-slow-in +$ripple-animation-transition-in := transform .3s $transition.fast-out-slow-in, opacity .2s $transition.fast-out-slow-in +$ripple-animation-transition-out := opacity .4s $transition.fast-out-slow-in $ripple-animation-visible-opacity := .15 From 9bf7811e30cfec57a707251a577505664dbdaaf9 Mon Sep 17 00:00:00 2001 From: nekosaur Date: Thu, 19 Apr 2018 11:00:33 +0200 Subject: [PATCH 2/6] enh: refactored btn and routable to allow for circle: true option when using icon prop --- src/components/VBtn/VBtn.ts | 7 ++++++- src/mixins/routable.ts | 12 +++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/VBtn/VBtn.ts b/src/components/VBtn/VBtn.ts index 8cd38b751b4..061d847ab58 100644 --- a/src/components/VBtn/VBtn.ts +++ b/src/components/VBtn/VBtn.ts @@ -41,7 +41,7 @@ const VBtn = mixins( outline: Boolean, ripple: { type: [Boolean, Object], - default: true + default: null }, round: Boolean, small: Boolean, @@ -85,6 +85,11 @@ const VBtn = mixins( return (!this.outline && !this.flat) ? this.addBackgroundColorClassChecks(classes) : this.addTextColorClassChecks(classes) + }, + computedRipple () { + const defaultRipple = this.icon || this.fab ? { circle: true } : true + if (this.disabled) return false + else return this.ripple || defaultRipple } }, diff --git a/src/mixins/routable.ts b/src/mixins/routable.ts index 18bbfa6d0d5..34e637d340b 100644 --- a/src/mixins/routable.ts +++ b/src/mixins/routable.ts @@ -1,4 +1,4 @@ -import Vue, { VNodeData } from 'vue' +import Vue, { VNodeData, VNodeDirective } from 'vue' import Ripple from '../directives/ripple' export default Vue.extend({ @@ -26,6 +26,12 @@ export default Vue.extend({ target: String }, + computed: { + computedRipple (): VNodeDirective | boolean { + return (this.ripple && !this.disabled) ? this.ripple : false + } + }, + methods: { /* eslint-disable-next-line no-unused-vars */ click (e: MouseEvent): void { /**/ }, @@ -39,8 +45,8 @@ export default Vue.extend({ props: {}, directives: [{ name: 'ripple', - value: (this.ripple && !this.disabled) ? this.ripple : false - }] as any, // TODO + value: this.computedRipple + }], [this.to ? 'nativeOn' : 'on']: { ...this.$listeners, click: this.click From feafaf66f807cbf208861b95ace36492716d7fa7 Mon Sep 17 00:00:00 2001 From: nekosaur Date: Sat, 12 May 2018 16:45:31 +0200 Subject: [PATCH 3/6] fix(v-ripple): stuff --- src/components/VBtn/VBtn.ts | 2 +- src/directives/ripple.ts | 54 +++++++++++++++++------------ src/mixins/routable.ts | 6 ++-- src/stylus/settings/_variables.styl | 2 +- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/components/VBtn/VBtn.ts b/src/components/VBtn/VBtn.ts index 061d847ab58..4a23d6938b7 100644 --- a/src/components/VBtn/VBtn.ts +++ b/src/components/VBtn/VBtn.ts @@ -86,7 +86,7 @@ const VBtn = mixins( ? this.addBackgroundColorClassChecks(classes) : this.addTextColorClassChecks(classes) }, - computedRipple () { + computedRipple (): any { const defaultRipple = this.icon || this.fab ? { circle: true } : true if (this.disabled) return false else return this.ripple || defaultRipple diff --git a/src/directives/ripple.ts b/src/directives/ripple.ts index 3b8dad36d85..f904317a36f 100644 --- a/src/directives/ripple.ts +++ b/src/directives/ripple.ts @@ -30,6 +30,30 @@ interface RippleOptions { circle?: boolean } +const calculate = (e: MouseEvent, el: HTMLElement, value: RippleOptions = {}) => { + const offset = el.getBoundingClientRect() + const localX = e.clientX - offset.left + const localY = e.clientY - offset.top + + let radius = 0 + let scale = 0.3 + if (el._ripple && el._ripple.circle) { + scale = 0.15 + radius = el.clientWidth / 2 + radius = radius + Math.sqrt((localX - radius)**2 + (localY - radius)**2) / 4 + } else { + radius = Math.sqrt(el.clientWidth**2 + el.clientHeight**2) / 2 + } + + const x = value.center ? 0 : `${localX - radius}px` + const y = value.center ? 0 : `${localY - radius}px` + + const centerX = `${(el.clientWidth - (radius * 2)) / 2}px` + const centerY = `${(el.clientHeight - (radius * 2)) / 2}px` + + return { radius, scale, x, y, centerX, centerY } +} + const ripple = { show (e: MouseEvent, el: HTMLElement, value: RippleOptions = {}) { if (!el._ripple || !el._ripple.enabled) { @@ -46,18 +70,7 @@ const ripple = { container.className += ` ${value.class}` } - const offset = el.getBoundingClientRect() - const localX = e.clientX - offset.left - const localY = e.clientY - offset.top - - let radius = 0, scale = 0.3 - if (el._ripple.circle) { - scale = 0.15 - radius = el.clientWidth / 2 - radius = radius + Math.sqrt((localX - radius)**2 + (localY - radius)**2) / 4 - } else { - radius = Math.sqrt(el.clientWidth**2 + el.clientHeight**2) / 2 - } + const { radius, scale, x, y, centerX, centerY } = calculate(e, el, value) animation.className = 'v-ripple__animation' animation.style.width = `${radius * 2}px` @@ -67,28 +80,23 @@ const ripple = { const computed = window.getComputedStyle(el) if (computed.position !== 'absolute' && computed.position !== 'fixed') el.style.position = 'relative' - const x = value.center ? '50%' : `${localX}px` - const y = value.center ? '50%' : `${localY}px` - const centerX = `${el.clientWidth / 2}px` - const centerY = `${el.clientHeight / 2}px` - animation.classList.add('v-ripple__animation--enter') animation.classList.add('v-ripple__animation--visible') - transform(animation, `translate(${x}px, ${y}px) scale3d(${scale},${scale},${scale})`) + transform(animation, `translate(${x}, ${y}) scale3d(${scale},${scale},${scale})`) opacity(animation, 0) animation.dataset.activated = String(performance.now()) setTimeout(() => { - animation.classList.remove('ripple__animation--enter') - animation.classList.add('ripple__animation--in') + animation.classList.remove('v-ripple__animation--enter') + animation.classList.add('v-ripple__animation--in') transform(animation, `translate(${centerX}, ${centerY}) scale3d(0.99,0.99,0.99)`) opacity(animation, 0.25) setTimeout(() => { - animation.classList.remove('ripple__animation--in') - animation.classList.add('ripple__animation--out') + animation.classList.remove('v-ripple__animation--in') + animation.classList.add('v-ripple__animation--out') opacity(animation, 0) - }, 250) + }, 300) }, 0) }, diff --git a/src/mixins/routable.ts b/src/mixins/routable.ts index 34e637d340b..aae85ebb845 100644 --- a/src/mixins/routable.ts +++ b/src/mixins/routable.ts @@ -1,4 +1,4 @@ -import Vue, { VNodeData, VNodeDirective } from 'vue' +import Vue, { VNodeData } from 'vue' import Ripple from '../directives/ripple' export default Vue.extend({ @@ -27,7 +27,7 @@ export default Vue.extend({ }, computed: { - computedRipple (): VNodeDirective | boolean { + computedRipple (): any { return (this.ripple && !this.disabled) ? this.ripple : false } }, @@ -46,7 +46,7 @@ export default Vue.extend({ directives: [{ name: 'ripple', value: this.computedRipple - }], + }] as any, [this.to ? 'nativeOn' : 'on']: { ...this.$listeners, click: this.click diff --git a/src/stylus/settings/_variables.styl b/src/stylus/settings/_variables.styl index 75d75f617e5..59bc3ad9f89 100755 --- a/src/stylus/settings/_variables.styl +++ b/src/stylus/settings/_variables.styl @@ -303,6 +303,6 @@ $input-group-text-field-label-top := 18px // ============================================================ // Ripple animation -$ripple-animation-transition-in := transform .3s $transition.fast-out-slow-in, opacity .2s $transition.fast-out-slow-in +$ripple-animation-transition-in := transform .3s $transition.fast-out-slow-in, opacity .1s $transition.fast-out-slow-in $ripple-animation-transition-out := opacity .4s $transition.fast-out-slow-in $ripple-animation-visible-opacity := .15 From 02a6a75ee94495a7d4bb4a6e31697b724476066f Mon Sep 17 00:00:00 2001 From: nekosaur Date: Sun, 13 May 2018 14:00:42 +0200 Subject: [PATCH 4/6] fix: exporting RippleOptions, updated ts defs --- src/components/VBtn/VBtn.ts | 5 +++-- src/directives/ripple.ts | 2 +- src/mixins/routable.ts | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/VBtn/VBtn.ts b/src/components/VBtn/VBtn.ts index 4a23d6938b7..a1b9510a0a6 100644 --- a/src/components/VBtn/VBtn.ts +++ b/src/components/VBtn/VBtn.ts @@ -15,6 +15,7 @@ import Routable from '../../mixins/routable' import Themeable from '../../mixins/themeable' import { factory as ToggleableFactory } from '../../mixins/toggleable' import { inject as RegistrableInject } from '../../mixins/registrable' +import { RippleOptions } from '../../directives/ripple' const VBtn = mixins( Colorable, @@ -42,7 +43,7 @@ const VBtn = mixins( ripple: { type: [Boolean, Object], default: null - }, + } as PropValidator, round: Boolean, small: Boolean, tag: { @@ -86,7 +87,7 @@ const VBtn = mixins( ? this.addBackgroundColorClassChecks(classes) : this.addTextColorClassChecks(classes) }, - computedRipple (): any { + computedRipple (): RippleOptions | boolean { const defaultRipple = this.icon || this.fab ? { circle: true } : true if (this.disabled) return false else return this.ripple || defaultRipple diff --git a/src/directives/ripple.ts b/src/directives/ripple.ts index f904317a36f..d84c01b3f9f 100644 --- a/src/directives/ripple.ts +++ b/src/directives/ripple.ts @@ -24,7 +24,7 @@ declare global { } } -interface RippleOptions { +export interface RippleOptions { class?: string center?: boolean circle?: boolean diff --git a/src/mixins/routable.ts b/src/mixins/routable.ts index aae85ebb845..df6bc457ce5 100644 --- a/src/mixins/routable.ts +++ b/src/mixins/routable.ts @@ -1,5 +1,5 @@ import Vue, { VNodeData } from 'vue' -import Ripple from '../directives/ripple' +import Ripple, { RippleOptions } from '../directives/ripple' export default Vue.extend({ name: 'routable', @@ -27,7 +27,7 @@ export default Vue.extend({ }, computed: { - computedRipple (): any { + computedRipple (): RippleOptions | boolean { return (this.ripple && !this.disabled) ? this.ripple : false } }, @@ -46,7 +46,7 @@ export default Vue.extend({ directives: [{ name: 'ripple', value: this.computedRipple - }] as any, + }] as any, // TODO: remove as any when vuejs/vue#8013 is fixed [this.to ? 'nativeOn' : 'on']: { ...this.$listeners, click: this.click From 59ead2496d693aeb5c3d8bc1e3a4ed905e63607d Mon Sep 17 00:00:00 2001 From: nekosaur Date: Sun, 13 May 2018 14:23:28 +0200 Subject: [PATCH 5/6] fix(ripple): bug when setting ripple to false --- src/components/VBtn/VBtn.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VBtn/VBtn.ts b/src/components/VBtn/VBtn.ts index a1b9510a0a6..550e8a90e5f 100644 --- a/src/components/VBtn/VBtn.ts +++ b/src/components/VBtn/VBtn.ts @@ -90,7 +90,7 @@ const VBtn = mixins( computedRipple (): RippleOptions | boolean { const defaultRipple = this.icon || this.fab ? { circle: true } : true if (this.disabled) return false - else return this.ripple || defaultRipple + else return this.ripple !== null ? this.ripple : defaultRipple } }, From 0f54af66f53fadb9656e1f79b66a430a1597ff3e Mon Sep 17 00:00:00 2001 From: Kael Date: Sun, 13 May 2018 23:07:54 +1000 Subject: [PATCH 6/6] test(VBtn): make sure ripple can be disabled --- test/unit/components/VBtn/VBtn.spec.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/unit/components/VBtn/VBtn.spec.js b/test/unit/components/VBtn/VBtn.spec.js index dc57a36c258..8b869c73ba5 100644 --- a/test/unit/components/VBtn/VBtn.spec.js +++ b/test/unit/components/VBtn/VBtn.spec.js @@ -177,4 +177,14 @@ test('VBtn.js', ({ mount, compileToFunctions }) => { expect(wrapper.hasClass('v-btn--outline')).toBe(true) expect(wrapper.hasClass('v-btn--depressed')).toBe(true) }) + + it('should disable ripple', () => { + const wrapper = mount(VBtn, { + propsData: { + ripple: false + } + }) + + expect(wrapper.element._ripple.enabled).toBe(false) + }) })