mirror of
https://github.com/wassname/fullcalendar.git
synced 2026-07-28 11:20:16 +08:00
187 lines
4.7 KiB
JavaScript
187 lines
4.7 KiB
JavaScript
|
|
/* Creates a clone of an element and lets it track the mouse as it moves
|
|
----------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
function MouseFollower(sourceEl, options) {
|
|
this.options = options = options || {};
|
|
this.sourceEl = sourceEl;
|
|
this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent
|
|
}
|
|
|
|
|
|
MouseFollower.prototype = {
|
|
|
|
options: null,
|
|
|
|
sourceEl: null, // the element that will be cloned and made to look like it is dragging
|
|
el: null, // the clone of `sourceEl` that will track the mouse
|
|
parentEl: null, // the element that `el` (the clone) will be attached to
|
|
|
|
// the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl
|
|
top0: null,
|
|
left0: null,
|
|
|
|
// the initial position of the mouse
|
|
mouseY0: null,
|
|
mouseX0: null,
|
|
|
|
// the number of pixels the mouse has moved from its initial position
|
|
topDelta: null,
|
|
leftDelta: null,
|
|
|
|
mousemoveProxy: null, // document mousemove handler, bound to the MouseFollower's `this`
|
|
|
|
isFollowing: false,
|
|
isHidden: false,
|
|
isAnimating: false, // doing the revert animation?
|
|
|
|
|
|
// Causes the element to start following the mouse
|
|
start: function(ev) {
|
|
if (!this.isFollowing) {
|
|
this.isFollowing = true;
|
|
|
|
this.mouseY0 = ev.pageY;
|
|
this.mouseX0 = ev.pageX;
|
|
this.topDelta = 0;
|
|
this.leftDelta = 0;
|
|
|
|
if (!this.isHidden) {
|
|
this.updatePosition();
|
|
}
|
|
|
|
$(document).on('mousemove', this.mousemoveProxy = $.proxy(this, 'mousemove'));
|
|
}
|
|
},
|
|
|
|
|
|
// Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position.
|
|
// `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately.
|
|
stop: function(shouldRevert, callback) {
|
|
var _this = this;
|
|
var revertDuration = this.options.revertDuration;
|
|
|
|
function complete() {
|
|
this.isAnimating = false;
|
|
_this.destroyEl();
|
|
|
|
this.top0 = this.left0 = null; // reset state for future updatePosition calls
|
|
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
}
|
|
|
|
if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time
|
|
this.isFollowing = false;
|
|
|
|
$(document).off('mousemove', this.mousemoveProxy);
|
|
|
|
if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation?
|
|
this.isAnimating = true;
|
|
this.el.animate({
|
|
top: this.top0,
|
|
left: this.left0
|
|
}, {
|
|
duration: revertDuration,
|
|
complete: complete
|
|
});
|
|
}
|
|
else {
|
|
complete();
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
// Gets the tracking element. Create it if necessary
|
|
getEl: function() {
|
|
var el = this.el;
|
|
|
|
if (!el) {
|
|
this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
|
|
el = this.el = this.sourceEl.clone()
|
|
.css({
|
|
position: 'absolute',
|
|
visibility: '', // in case original element was hidden (commonly through hideEvents())
|
|
display: this.isHidden ? 'none' : '', // for when initially hidden
|
|
margin: 0,
|
|
right: 'auto', // erase and set width instead
|
|
bottom: 'auto', // erase and set height instead
|
|
width: this.sourceEl.width(), // explicit height in case there was a 'right' value
|
|
height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value
|
|
opacity: this.options.opacity || '',
|
|
zIndex: this.options.zIndex
|
|
})
|
|
.appendTo(this.parentEl);
|
|
}
|
|
|
|
return el;
|
|
},
|
|
|
|
|
|
// Removes the tracking element if it has already been created
|
|
destroyEl: function() {
|
|
if (this.el) {
|
|
this.el.remove();
|
|
this.el = null;
|
|
}
|
|
},
|
|
|
|
|
|
// Update the CSS position of the tracking element
|
|
updatePosition: function() {
|
|
var sourceOffset;
|
|
var origin;
|
|
|
|
this.getEl(); // ensure this.el
|
|
|
|
// make sure origin info was computed
|
|
if (this.top0 === null) {
|
|
this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
|
|
sourceOffset = this.sourceEl.offset();
|
|
origin = this.el.offsetParent().offset();
|
|
this.top0 = sourceOffset.top - origin.top;
|
|
this.left0 = sourceOffset.left - origin.left;
|
|
}
|
|
|
|
this.el.css({
|
|
top: this.top0 + this.topDelta,
|
|
left: this.left0 + this.leftDelta
|
|
});
|
|
},
|
|
|
|
|
|
// Gets called when the user moves the mouse
|
|
mousemove: function(ev) {
|
|
this.topDelta = ev.pageY - this.mouseY0;
|
|
this.leftDelta = ev.pageX - this.mouseX0;
|
|
|
|
if (!this.isHidden) {
|
|
this.updatePosition();
|
|
}
|
|
},
|
|
|
|
|
|
// Temporarily makes the tracking element invisible. Can be called before following starts
|
|
hide: function() {
|
|
if (!this.isHidden) {
|
|
this.isHidden = true;
|
|
if (this.el) {
|
|
this.el.hide();
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
// Show the tracking element after it has been temporarily hidden
|
|
show: function() {
|
|
if (this.isHidden) {
|
|
this.isHidden = false;
|
|
this.updatePosition();
|
|
this.getEl().show();
|
|
}
|
|
}
|
|
|
|
};
|