2015-12-01 20:22:13 +03:00
|
|
|
import Util from './util'
|
2015-12-01 02:59:43 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
const Ripples = (($) => {
|
2015-11-21 02:50:50 +03:00
|
|
|
|
|
|
|
/**
|
2015-11-30 23:57:40 +03:00
|
|
|
* ------------------------------------------------------------------------
|
|
|
|
* Constants
|
|
|
|
* ------------------------------------------------------------------------
|
2015-11-21 02:50:50 +03:00
|
|
|
*/
|
2015-11-30 23:57:40 +03:00
|
|
|
const NAME = 'ripples'
|
2015-12-01 20:58:10 +03:00
|
|
|
const DATA_KEY = `mdb.${NAME}`
|
2015-11-30 23:57:40 +03:00
|
|
|
const JQUERY_NO_CONFLICT = $.fn[NAME]
|
2015-12-01 02:59:43 +03:00
|
|
|
|
2015-12-02 19:38:00 +03:00
|
|
|
const ClassName = {
|
|
|
|
CONTAINER: 'ripple-container',
|
|
|
|
DECORATOR: 'ripple-decorator'
|
|
|
|
}
|
|
|
|
|
|
|
|
const Selector = {
|
|
|
|
CONTAINER: `.${ClassName.CONTAINER}`,
|
|
|
|
DECORATOR: `.${ClassName.DECORATOR}` //,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-12-01 02:59:43 +03:00
|
|
|
const Default = {
|
2015-12-02 19:38:00 +03:00
|
|
|
container: {
|
|
|
|
template: `<div class='${ClassName.CONTAINER}'></div>`
|
|
|
|
},
|
|
|
|
decorator: {
|
|
|
|
template: `${ClassName.DECORATOR}'></div>`
|
|
|
|
},
|
|
|
|
trigger: {
|
|
|
|
start: 'mousedown touchstart',
|
|
|
|
end: 'mouseup mouseleave touchend'
|
|
|
|
},
|
2015-12-01 02:59:43 +03:00
|
|
|
touchUserAgentRegex: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i,
|
|
|
|
duration: 500
|
|
|
|
}
|
2015-11-30 23:57:40 +03:00
|
|
|
|
2015-11-21 02:50:50 +03:00
|
|
|
/**
|
2015-11-30 23:57:40 +03:00
|
|
|
* ------------------------------------------------------------------------
|
|
|
|
* Class Definition
|
|
|
|
* ------------------------------------------------------------------------
|
2015-11-21 02:50:50 +03:00
|
|
|
*/
|
2015-11-30 23:57:40 +03:00
|
|
|
class Ripples {
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-04 04:09:01 +03:00
|
|
|
constructor($element, config) {
|
|
|
|
this.$element = $element
|
2015-12-01 02:59:43 +03:00
|
|
|
this.config = $.extend({}, Default, config)
|
|
|
|
|
|
|
|
// attach initial listener
|
2015-12-04 04:09:01 +03:00
|
|
|
this.$element.on(this.config.triggerStart, this._onStartRipple)
|
2015-11-30 23:57:40 +03:00
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
dispose() {
|
2015-12-04 04:09:01 +03:00
|
|
|
$.removeData(this.$element, DATA_KEY)
|
|
|
|
this.$element = null
|
2015-12-01 02:59:43 +03:00
|
|
|
this.containerElement = null
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement = null
|
2015-12-01 02:59:43 +03:00
|
|
|
this.config = null
|
2015-11-30 23:57:40 +03:00
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// private
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-01 00:47:47 +03:00
|
|
|
_onStartRipple(event) {
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
// Verify if the user is just touching on a device and return if so
|
2015-12-02 19:38:00 +03:00
|
|
|
if (this.isTouch() && event.type === 'mousedown') {
|
2015-11-30 23:57:40 +03:00
|
|
|
return
|
2015-11-21 02:50:50 +03:00
|
|
|
}
|
|
|
|
|
2015-12-01 00:47:47 +03:00
|
|
|
// Find or create the ripple container element
|
|
|
|
this._findOrCreateContainer()
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-01 00:47:47 +03:00
|
|
|
// Get relY and relX positions of the container element
|
|
|
|
let relY = this._getRelY(event)
|
|
|
|
let relX = this._getRelX(event)
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
// If relY and/or relX are false, return the event
|
|
|
|
if (!relY && !relX) {
|
|
|
|
return
|
2015-11-21 02:50:50 +03:00
|
|
|
}
|
|
|
|
|
2015-12-01 00:47:47 +03:00
|
|
|
// set the location and color each time (even if element is cached)
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement.css({
|
2015-12-03 20:35:51 +03:00
|
|
|
left: relX,
|
|
|
|
top: relY,
|
|
|
|
'background-color': this._getRipplesColor()
|
|
|
|
})
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
// Make sure the ripple has the styles applied (ugly hack but it works)
|
2015-12-01 00:47:47 +03:00
|
|
|
this._forceStyleApplication()
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
// Turn on the ripple animation
|
2015-12-01 00:47:47 +03:00
|
|
|
this.rippleOn()
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-02 19:38:00 +03:00
|
|
|
// Call the rippleEnd function when the transition 'on' ends
|
2015-11-30 23:57:40 +03:00
|
|
|
setTimeout(() => {
|
2015-12-01 00:47:47 +03:00
|
|
|
this.rippleEnd()
|
2015-12-01 02:59:43 +03:00
|
|
|
}, this.config.duration)
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-01 00:47:47 +03:00
|
|
|
// Detect when the user leaves the element (attach only when necessary for performance)
|
2015-12-04 04:09:01 +03:00
|
|
|
this.$element.on(this.config.triggerEnd, () => {
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement.data('mousedown', 'off')
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-02 19:38:00 +03:00
|
|
|
if (this.decoratorElement.data('animating') === 'off') {
|
2015-12-01 00:47:47 +03:00
|
|
|
this.rippleOut()
|
2015-11-30 23:57:40 +03:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-01 00:47:47 +03:00
|
|
|
_findOrCreateContainer() {
|
2015-12-01 02:59:43 +03:00
|
|
|
if (!this.containerElement || !this.containerElement.length > 0) {
|
2015-12-04 04:09:01 +03:00
|
|
|
this.$element.append(this.config.container.template)
|
|
|
|
this.containerElement = this.$element.find(Selector.CONTAINER)
|
2015-12-01 00:47:47 +03:00
|
|
|
}
|
|
|
|
|
2015-12-01 02:59:43 +03:00
|
|
|
// always add the rippleElement, it is always removed
|
2015-12-04 04:09:01 +03:00
|
|
|
this.containerElement.append(this.config.$element.template)
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement = this.containerElement.find(Selector.DECORATOR)
|
2015-12-01 00:47:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the ripple has the styles applied (ugly hack but it works)
|
|
|
|
_forceStyleApplication() {
|
2015-12-02 19:38:00 +03:00
|
|
|
return window.getComputedStyle(this.decoratorElement[0]).opacity
|
2015-12-01 00:47:47 +03:00
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
/**
|
|
|
|
* Get the relX
|
|
|
|
*/
|
2015-12-01 00:47:47 +03:00
|
|
|
_getRelX(event) {
|
2015-12-01 02:59:43 +03:00
|
|
|
let wrapperOffset = this.containerElement.offset()
|
2015-11-30 23:57:40 +03:00
|
|
|
|
|
|
|
let result = null
|
|
|
|
if (!this.isTouch()) {
|
|
|
|
// Get the mouse position relative to the ripple wrapper
|
|
|
|
result = event.pageX - wrapperOffset.left
|
|
|
|
} else {
|
2015-12-03 20:35:51 +03:00
|
|
|
// Make sure the user is using only one finger and then get the touch
|
|
|
|
// position relative to the ripple wrapper
|
2015-11-30 23:57:40 +03:00
|
|
|
event = event.originalEvent
|
|
|
|
|
|
|
|
if (event.touches.length === 1) {
|
|
|
|
result = event.touches[0].pageX - wrapperOffset.left
|
|
|
|
} else {
|
|
|
|
result = false
|
|
|
|
}
|
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
return result
|
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
/**
|
|
|
|
* Get the relY
|
|
|
|
*/
|
2015-12-01 00:47:47 +03:00
|
|
|
_getRelY(event) {
|
2015-12-01 02:59:43 +03:00
|
|
|
let containerOffset = this.containerElement.offset()
|
2015-11-30 23:57:40 +03:00
|
|
|
let result = null
|
|
|
|
|
|
|
|
if (!this.isTouch()) {
|
|
|
|
/**
|
|
|
|
* Get the mouse position relative to the ripple wrapper
|
|
|
|
*/
|
2015-12-01 00:47:47 +03:00
|
|
|
result = event.pageY - containerOffset.top
|
2015-11-30 23:57:40 +03:00
|
|
|
} else {
|
|
|
|
/**
|
|
|
|
* Make sure the user is using only one finger and then get the touch
|
|
|
|
* position relative to the ripple wrapper
|
|
|
|
*/
|
|
|
|
event = event.originalEvent
|
|
|
|
|
|
|
|
if (event.touches.length === 1) {
|
2015-12-03 20:35:51 +03:00
|
|
|
result = event.touches[0].pageY - containerOffset.top
|
2015-11-30 23:57:40 +03:00
|
|
|
} else {
|
|
|
|
result = false
|
2015-11-21 02:50:50 +03:00
|
|
|
}
|
2015-11-30 23:57:40 +03:00
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
return result
|
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
/**
|
|
|
|
* Get the ripple color
|
|
|
|
*/
|
2015-12-01 00:47:47 +03:00
|
|
|
_getRipplesColor() {
|
2015-12-04 04:09:01 +03:00
|
|
|
let color = this.$element.data('ripple-color') ? this.$element.data('ripple-color') : window.getComputedStyle(this.$element[0]).color
|
2015-11-30 23:57:40 +03:00
|
|
|
return color
|
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
/**
|
|
|
|
* Verify if the client is using a mobile device
|
|
|
|
*/
|
|
|
|
isTouch() {
|
2015-12-01 02:59:43 +03:00
|
|
|
return this.config.touchUserAgentRegex.test(navigator.userAgent)
|
2015-11-21 02:50:50 +03:00
|
|
|
}
|
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
/**
|
|
|
|
* End the animation of the ripple
|
|
|
|
*/
|
2015-12-01 00:47:47 +03:00
|
|
|
rippleEnd() {
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement.data('animating', 'off')
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-02 19:38:00 +03:00
|
|
|
if (this.decoratorElement.data('mousedown') === 'off') {
|
|
|
|
this.rippleOut(this.decoratorElement)
|
2015-11-21 02:50:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
/**
|
|
|
|
* Turn off the ripple effect
|
|
|
|
*/
|
2015-12-01 00:47:47 +03:00
|
|
|
rippleOut() {
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement.off()
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-01 20:22:13 +03:00
|
|
|
if (Util.transitionEndSupported()) {
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement.addClass('ripple-out')
|
2015-11-30 23:57:40 +03:00
|
|
|
} else {
|
2015-12-03 20:35:51 +03:00
|
|
|
this.decoratorElement.animate({opacity: 0}, 100, () => {
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement.triggerStart('transitionend')
|
2015-11-30 23:57:40 +03:00
|
|
|
})
|
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement.on(Util.transitionEndSelector(), () => {
|
|
|
|
this.decoratorElement.remove()
|
|
|
|
this.decoratorElement = null
|
2015-11-30 23:57:40 +03:00
|
|
|
})
|
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
/**
|
|
|
|
* Turn on the ripple effect
|
|
|
|
*/
|
2015-12-01 00:47:47 +03:00
|
|
|
rippleOn() {
|
|
|
|
let size = this._getNewSize()
|
2015-11-30 23:57:40 +03:00
|
|
|
|
2015-12-01 20:22:13 +03:00
|
|
|
if (Util.transitionEndSupported()) {
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement
|
2015-11-30 23:57:40 +03:00
|
|
|
.css({
|
2015-12-02 19:38:00 +03:00
|
|
|
'-ms-transform': `scale(${size})`,
|
|
|
|
'-moz-transform': `scale(${size})`,
|
|
|
|
'-webkit-transform': `scale(${size})`,
|
2015-12-03 20:35:51 +03:00
|
|
|
transform: `scale(${size})`
|
2015-11-30 23:57:40 +03:00
|
|
|
})
|
2015-12-02 19:38:00 +03:00
|
|
|
.addClass('ripple-on')
|
|
|
|
.data('animating', 'on')
|
|
|
|
.data('mousedown', 'on')
|
2015-11-30 23:57:40 +03:00
|
|
|
} else {
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement.animate({
|
2015-12-04 04:09:01 +03:00
|
|
|
width: Math.max(this.$element.outerWidth(), this.$element.outerHeight()) * 2,
|
|
|
|
height: Math.max(this.$element.outerWidth(), this.$element.outerHeight()) * 2,
|
|
|
|
'margin-left': Math.max(this.$element.outerWidth(), this.$element.outerHeight()) * (-1),
|
|
|
|
'margin-top': Math.max(this.$element.outerWidth(), this.$element.outerHeight()) * (-1),
|
2015-12-03 20:35:51 +03:00
|
|
|
opacity: 0.2
|
2015-12-01 02:59:43 +03:00
|
|
|
}, this.config.duration, () => {
|
2015-12-02 19:38:00 +03:00
|
|
|
this.decoratorElement.triggerStart('transitionend')
|
2015-11-30 23:57:40 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
/**
|
|
|
|
* Get the new size based on the element height/width and the ripple width
|
|
|
|
*/
|
2015-12-01 00:47:47 +03:00
|
|
|
_getNewSize() {
|
2015-12-04 04:09:01 +03:00
|
|
|
return (Math.max(this.$element.outerWidth(), this.$element.outerHeight()) / this.decoratorElement.outerWidth()) * 2.5
|
2015-11-30 23:57:40 +03:00
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-01 00:47:47 +03:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// static
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-12-01 20:58:10 +03:00
|
|
|
static _jQueryInterface(config) {
|
2015-11-30 23:57:40 +03:00
|
|
|
return this.each(() => {
|
2015-12-04 04:09:01 +03:00
|
|
|
let $element = $(this)
|
|
|
|
let data = $element.data(DATA_KEY)
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
if (!data) {
|
2015-12-01 20:58:10 +03:00
|
|
|
data = new Ripples(this, config)
|
2015-12-04 04:09:01 +03:00
|
|
|
$element.data(DATA_KEY, data)
|
2015-11-30 23:57:40 +03:00
|
|
|
}
|
|
|
|
})
|
2015-11-21 02:50:50 +03:00
|
|
|
}
|
2015-11-30 23:57:40 +03:00
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
|
|
|
/**
|
2015-11-30 23:57:40 +03:00
|
|
|
* ------------------------------------------------------------------------
|
|
|
|
* jQuery
|
|
|
|
* ------------------------------------------------------------------------
|
2015-11-21 02:50:50 +03:00
|
|
|
*/
|
2015-11-30 23:57:40 +03:00
|
|
|
$.fn[NAME] = Ripples._jQueryInterface
|
|
|
|
$.fn[NAME].Constructor = Ripples
|
|
|
|
$.fn[NAME].noConflict = () => {
|
|
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT
|
|
|
|
return Ripples._jQueryInterface
|
|
|
|
}
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
return Ripples
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
})(jQuery)
|
2015-11-21 02:50:50 +03:00
|
|
|
|
2015-11-30 23:57:40 +03:00
|
|
|
export default Ripples
|