I am rolling out a custom drag drop implementation which is a simplified version of the Dragula library. Here are my fiddles:
With Dragula: http://jsfiddle.net/8m4jrfwn/
With my custom drag drop implementation: http://jsfiddle.net/7wLtojs4/1/
The issue I am seeing is that my custom drag and drop implementation is a bit to free-form. I would like for the user to only be able to drop on the top or bottom of another div with the class box. How can I make this possible? Code here:
function handleMouseDown(e) {
window.dragging = {};
dragging.pageX = e.pageX;
dragging.pageY = e.pageY;
dragging.elem = this;
dragging.offset = $(this).offset();
$('body')
.on('mouseup', handleMouseUp)
.on('mousemove', handleDragging);
}
function handleDragging(e) {
let left = dragging.offset.left + (e.pageX - dragging.pageX);
let top = dragging.offset.top + (e.pageY - dragging.pageY);
$(dragging.elem)
.offset({top: top, left: left});
}
function handleMouseUp() {
$('body')
.off('mousemove', handleDragging)
.off('mouseup', handleMouseUp);
}
let boxElement = '.item';
$(boxElement).mousedown(handleMouseDown);
I does not see any logic here about what will happen on mouseup event. Seems you need detect on mouseup new order of elements and then exchange their positions.
For this you should know positions of all of them. On mouseup you can reorder them based on new positions.
In my realization(https://jsfiddle.net/jaromudr/c99oueg5/) I used next logic:
onMove(draggable) {
const sortedDraggables = this.getSortedDraggables()
const pinnedPositions = sortedDraggables.map((draggable) => draggable.pinnedPosition)
const currentIndex = sortedDraggables.indexOf(draggable)
const targetIndex = indexOfNearestPoint(pinnedPositions, draggable.position, this.options.radius, getYDifference)
if (targetIndex !== -1 && currentIndex !== targetIndex) {
arrayMove(sortedDraggables, currentIndex, targetIndex)
this.bubling(sortedDraggables, draggable)
}
}
bubling(sortedDraggables, currentDraggable) {
const currentPosition = this.startPosition.clone()
sortedDraggables.forEach((draggable) => {
if (!draggable.pinnedPosition.compare(currentPosition)) {
if (draggable === currentDraggable && !currentDraggable.nativeDragAndDrop) {
draggable.pinnedPosition = currentPosition.clone()
} else {
draggable.pinPosition(currentPosition, (draggable === currentDraggable) ? 0 : this.options.timeExcange)
}
}
currentPosition.y = currentPosition.y + draggable.getSize().y + this.verticalGap
})
}
where on move we detect if position in list was changed and move other draggables to their new positions.
On drag end we just move currentDraggable to pinnedPosition
Is it possible to know if mouse has left specific portion of the window? The values are given below:
var cursorX;
var cursorY;
document.onmousemove = function(e){
cursorX = e.pageX;
cursorY = e.pageY;
}
document.addEventListener('mouseout', function(e) {
var dims = elem.getBoundingClientRect();
var top = window.scrollY + dims.top;
var left = dims.left;
var width = dims.width;
var bottom = dims.height;
if (what condition should be here) {
console.log('yes mouse has left that portion')
document.getElementById('div-in-the-end-of-body').style .display = 'none';
}
});
For example values of dims in console log are ClientRect {top: 155.375, right: 621, bottom: 540.375, left: 313, width: 308…}
Here is visual of what I'm trying to achieve.
Edit: That div at the end of the body has absolute position and it hovers on the images. If I hover at that div images consider mouse has left it and hides the div. This is the reason I want to do it this way.
Edit # 2: Here is the solution
elem.onmouseout=function(){
var dims = this.getBoundingClientRect();
var top = window.scrollY + dims.top;
var left = dims.left;
var width = dims.width;
var bottom = dims.height;
if(top > 10 || left > 10 || width > 10 || bottom > 10){
document.getElementById('div-in-the-end-of-body').style .display = 'none';
}
}
Have a look at the mouseout event. Alternatively, you could capture mouse events as soon as your mouse has entered your specific region.
You can bind event listeners to the "regions" you want to check. As your regions are objects in javascript, there are event listeners built in or you can using obj.call to call functions not belong to the object you created. For instance,
let rects[] // this is your list of regions
for (let rect of rects) {
rect.onMouseMove((e) => {
// do something when mouse cursor is moving
})
rect.onMouseEnter((e) => {
// do somthing when mouse cursor enters the area
})
rect.onMouseLeave((e) => {
// do something when mouse cursor exits the area
})
}
Ok, I've figured out the solution. Here is working code for those who might need.
elem.onmouseout=function(){
var dims = this.getBoundingClientRect();
var top = window.scrollY + dims.top;
var left = dims.left;
var width = dims.width;
var bottom = dims.height;
if(top > 10 || left > 10 || width > 10 || bottom > 10){
console.log('yay!! ship has left the dock')
}
}
I have developed a Windows application using TideSDK with HTML , CSS and Javascript.To give it a widget look I disappered its title bar.So I can't drag it.The TideSDK documentation says "after disappearing title bars, you can drag window using its contents with javascript".
How can I drag the window with javascript selecting its components(tiles in my case)?Any tutorial or code as I found nothing on google with my search.
My App looks like below:
I've written the following generic function for dragging using JS:
function setDragOfElementOnAnother ($draggableElement, $draggedElememnt, allowDragPredicate) {
var stopDragFunction = function() {
$draggedElememnt.data("drag", false);
$draggedElememnt.removeData("startPoint");
$("html, body").unbind("mouseup.drag");
$("html, body").unbind("mousemove.drag");
};
var dragFunction = function(e) {
if (!parseBoolean($draggedElememnt.data("drag")))
return;
var begin = $draggedElememnt.data("startPoint");
$draggedElememnt.css({ left: e.clientX - begin.x, top: e.clientY - begin.y });
};
$draggableElement.mousedown(function(e) {
if ((e.clientX - $(this).offset().left < 0 || $(this).offset().left + $(this).width() < e.clientX) ||
e.clientY - $(this).offset().top < 0)
return;
if (allowDragPredicate && !allowDragPredicate(e))
return;
$draggedElememnt.data("drag", true);
$draggedElememnt.data("startPoint", Point(e.clientX - $(this).offset().left, e.clientY - $(this).offset().top));
$("html, body").bind("mouseup.drag", stopDragFunction);
$("html, body").bind("mousemove.drag", dragFunction);
});
$draggableElement.mouseup(stopDragFunction);
$draggableElement.mousemove(dragFunction);
}
This function is used to declare dragging of one element using another.
Prerequisites:
(1) The dragged element's position has to be either fixed or absolute (relative should also work). (2) jQuery.
By specifying both dragged and draggable elements as the same one, pressing on the element and moving the mouse will cause the element to drag.
The 'allowDragPredicate' variable is optional, and is useful to create boundaries.
EDIT: Forgot. Also add the code:
function Point(xVal, yVal) {
if (xVal === undefined) {
xVal = 0;
}
if (yVal === undefined) {
yVal = 0;
}
return {
x: xVal,
y: yVal
};
}
EDIT 2: And:
function parseBoolean(str) {
if (str === true)
return true;
if (str)
return /^true$/i.test(str);
return false;
}
EDIT 3: And, added a simple jsFiddle example.
I created a website with jQueryMobile for iOS and Android.
I don't want the document itself to scroll. Instead, just an area (a <div> element) should be scrollable (via css property overflow-y:scroll).
So I disabled document scrolling via:
$(document).bind("touchstart", function(e){
e.preventDefault();
});
$(document).bind("touchmove", function(e){
e.preventDefault();
});
But that will also disable scrolling for all other elements in the document, no matter if overflow:scroll is set or not.
How can I solve this?
How about this CSS only solution:
https://jsfiddle.net/Volker_E/jwGBy/24/
body gets position: fixed; and every other element you wish an overflow: scroll;.
Works on mobile Chrome (WebKit)/Firefox 19/Opera 12.
You'll also see my various attempts towards a jQuery solution. But as soon as you're binding touchmove/touchstart to document, it hinders scrolling in the child div no matter if unbinded or not.
Disclaimer: Solutions to this problem are in many ways basically not very nice UX-wise! You'll never know how big the viewport of your visitors exactly is or which font-size they are using (client user-agent style like), therefore it could easily be, that important content is hidden to them in your document.
Maybe I misunderstood the question, but if I'm correct:
You want not to be able to scroll except a certain element so you:
$(document).bind("touchmove", function(e){
e.preventDefault();
});
Prevent everything within the document.
Why don't you just stop the event bubbling on the element where you wish to scroll?
(PS: you don't have to prevent touchstart -> if you use touch start for selecting elements instead of clicks that is prevented as well, touch move is only needed because then it is actually tracing the movement)
$('#element').on('touchmove', function (e) {
e.stopPropagation();
});
Now on the element CSS
#element {
overflow-y: scroll; // (vertical)
overflow-x: hidden; // (horizontal)
}
If you are on a mobile device, you can even go a step further.
You can force hardware accelerated scrolling (though not all mobile browsers support this);
Browser Overflow scroll:
Android Browser Yes
Blackberry Browser Yes
Chrome for Mobile Yes
Firefox Mobile Yes
IE Mobile Yes
Opera Mini No
Opera Mobile Kinda
Safari Yes
#element.nativescroll {
-webkit-overflow-scrolling: touch;
}
normal:
<div id="element"></div>
native feel:
<div id="element" class="nativescroll"></div>
Finally, I got it to work. Really simple:
var $layer = $("#layer");
$layer.bind('touchstart', function (ev) {
var $this = $(this);
var layer = $layer.get(0);
if ($this.scrollTop() === 0) $this.scrollTop(1);
var scrollTop = layer.scrollTop;
var scrollHeight = layer.scrollHeight;
var offsetHeight = layer.offsetHeight;
var contentHeight = scrollHeight - offsetHeight;
if (contentHeight == scrollTop) $this.scrollTop(scrollTop-1);
});
Here is a solution I am using:
$scrollElement is the scroll element, $scrollMask is a div with style position: fixed; top: 0; bottom: 0;.
The z-index of $scrollMask is smaller than $scrollElement.
$scrollElement.on('touchmove touchstart', function (e) {
e.stopPropagation();
});
$scrollMask.on('touchmove', function(e) {
e.stopPropagation();
e.preventDefault();
});
I was looking for a solution that did not require calling out specific areas that should scroll. Piecing together a few resources, here is what worked for me:
// Detects if element has scroll bar
$.fn.hasScrollBar = function() {
return this.get(0).scrollHeight > this.outerHeight();
}
$(document).on("touchstart", function(e) {
var $scroller;
var $target = $(e.target);
// Get which element could have scroll bars
if($target.hasScrollBar()) {
$scroller = $target;
} else {
$scroller = $target
.parents()
.filter(function() {
return $(this).hasScrollBar();
})
.first()
;
}
// Prevent if nothing is scrollable
if(!$scroller.length) {
e.preventDefault();
} else {
var top = $scroller[0].scrollTop;
var totalScroll = $scroller[0].scrollHeight;
var currentScroll = top + $scroller[0].offsetHeight;
// If at container edge, add a pixel to prevent outer scrolling
if(top === 0) {
$scroller[0].scrollTop = 1;
} else if(currentScroll === totalScroll) {
$scroller[0].scrollTop = top - 1;
}
}
});
This code requires jQuery.
Sources:
this post
https://github.com/luster-io/prevent-overscroll
How can I check if a scrollbar is visible?
Jquery check if any parent div have scroll bar
Update
I needed a vanilla JavaScript version of this, so the following is a modified version. I implemented a margin-checker and something that explicitly allows input/textareas to be clickable (I was running into issues with this on the project I used it on...it may not be necessary for your project). Keep in mind this is ES6 code.
const preventScrolling = e => {
const shouldAllowEvent = element => {
// Must be an element that is not the document or body
if(!element || element === document || element === document.body) {
return false;
}
// Allow any input or textfield events
if(['INPUT', 'TEXTAREA'].indexOf(element.tagName) !== -1) {
return true;
}
// Get margin and outerHeight for final check
const styles = window.getComputedStyle(element);
const margin = parseFloat(styles['marginTop']) +
parseFloat(styles['marginBottom']);
const outerHeight = Math.ceil(element.offsetHeight + margin);
return (element.scrollHeight > outerHeight) && (margin >= 0);
};
let target = e.target;
// Get first element to allow event or stop
while(target !== null) {
if(shouldAllowEvent(target)) {
break;
}
target = target.parentNode;
}
// Prevent if no elements
if(!target) {
e.preventDefault();
} else {
const top = target.scrollTop;
const totalScroll = target.scrollHeight;
const currentScroll = top + target.offsetHeight;
// If at container edge, add a pixel to prevent outer scrolling
if(top === 0) {
target.scrollTop = 1;
} else if(currentScroll === totalScroll) {
target.scrollTop = top - 1;
}
}
};
document.addEventListener('touchstart', preventScrolling);
document.addEventListener('mousedown', preventScrolling);
In my case, I have a scrollable body and a scrollable floating menu over it. Both have to be scrollable, but I had to prevent body scrolling when "floating menu" (position:fixed) received touch events and was scrolling and it reached top or bottom. By default browser then started to scroll the body.
I really liked jimmont's answer, but unfortunatelly it did not work well on all devices and browsers, especially with a fast and long swipe.
I ended up using MOMENTUM SCROLLING USING JQUERY (hnldesign.nl) on floating menu, which prevents default browser scrolling and then animates scrolling itself. I include that code here for completeness:
/**
* jQuery inertial Scroller v1.5
* (c)2013 hnldesign.nl
* This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
**/
/*jslint browser: true*/
/*global $, jQuery*/
/* SETTINGS */
var i_v = {
i_touchlistener : '.inertialScroll', // element to monitor for touches, set to null to use document. Otherwise use quotes. Eg. '.myElement'. Note: if the finger leaves this listener while still touching, movement is stopped.
i_scrollElement : '.inertialScroll', // element (class) to be scrolled on touch movement
i_duration : window.innerHeight * 1.5, // (ms) duration of the inertial scrolling simulation. Devices with larger screens take longer durations (phone vs tablet is around 500ms vs 1500ms). This is a fixed value and does not influence speed and amount of momentum.
i_speedLimit : 1.2, // set maximum speed. Higher values will allow faster scroll (which comes down to a bigger offset for the duration of the momentum scroll) note: touch motion determines actual speed, this is just a limit.
i_handleY : true, // should scroller handle vertical movement on element?
i_handleX : true, // should scroller handle horizontal movement on element?
i_moveThreshold : 100, // (ms) determines if a swipe occurred: time between last updated movement # touchmove and time # touchend, if smaller than this value, trigger inertial scrolling
i_offsetThreshold : 30, // (px) determines, together with i_offsetThreshold if a swipe occurred: if calculated offset is above this threshold
i_startThreshold : 5, // (px) how many pixels finger needs to move before a direction (horizontal or vertical) is chosen. This will make the direction detection more accurate, but can introduce a delay when starting the swipe if set too high
i_acceleration : 0.5, // increase the multiplier by this value, each time the user swipes again when still scrolling. The multiplier is used to multiply the offset. Set to 0 to disable.
i_accelerationT : 250 // (ms) time between successive swipes that determines if the multiplier is increased (if lower than this value)
};
/* stop editing here */
//set some required vars
i_v.i_time = {};
i_v.i_elem = null;
i_v.i_elemH = null;
i_v.i_elemW = null;
i_v.multiplier = 1;
// Define easing function. This is based on a quartic 'out' curve. You can generate your own at http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
if ($.easing.hnlinertial === undefined) {
$.easing.hnlinertial = function (x, t, b, c, d) {
"use strict";
var ts = (t /= d) * t, tc = ts * t;
return b + c * (-1 * ts * ts + 4 * tc + -6 * ts + 4 * t);
};
}
$(i_v.i_touchlistener || document)
.on('touchstart touchmove touchend', function (e) {
"use strict";
//prevent default scrolling
e.preventDefault();
//store timeStamp for this event
i_v.i_time[e.type] = e.timeStamp;
})
.on('touchstart', function (e) {
"use strict";
this.tarElem = $(e.target);
this.elemNew = this.tarElem.closest(i_v.i_scrollElement).length > 0 ? this.tarElem.closest(i_v.i_scrollElement) : $(i_v.i_scrollElement).eq(0);
//dupecheck, optimizes code a bit for when the element selected is still the same as last time
this.sameElement = i_v.i_elem ? i_v.i_elem[0] == this.elemNew[0] : false;
//no need to redo these if element is unchanged
if (!this.sameElement) {
//set the element to scroll
i_v.i_elem = this.elemNew;
//get dimensions
i_v.i_elemH = i_v.i_elem.innerHeight();
i_v.i_elemW = i_v.i_elem.innerWidth();
//check element for applicable overflows and reevaluate settings
this.i_scrollableY = !!((i_v.i_elemH < i_v.i_elem.prop('scrollHeight') && i_v.i_handleY));
this.i_scrollableX = !!((i_v.i_elemW < i_v.i_elem.prop('scrollWidth') && i_v.i_handleX));
}
//get coordinates of touch event
this.pageY = e.originalEvent.touches[0].pageY;
this.pageX = e.originalEvent.touches[0].pageX;
if (i_v.i_elem.is(':animated') && (i_v.i_time.touchstart - i_v.i_time.touchend) < i_v.i_accelerationT) {
//user swiped while still animating, increase the multiplier for the offset
i_v.multiplier += i_v.i_acceleration;
} else {
//else reset multiplier
i_v.multiplier = 1;
}
i_v.i_elem
//stop any animations still running on element (this enables 'tap to stop')
.stop(true, false)
//store current scroll positions of element
.data('scrollTop', i_v.i_elem.scrollTop())
.data('scrollLeft', i_v.i_elem.scrollLeft());
})
.on('touchmove', function (e) {
"use strict";
//check if startThreshold is met
this.go = (Math.abs(this.pageX - e.originalEvent.touches[0].pageX) > i_v.i_startThreshold || Math.abs(this.pageY - e.originalEvent.touches[0].pageY) > i_v.i_startThreshold);
})
.on('touchmove touchend', function (e) {
"use strict";
//check if startThreshold is met
if (this.go) {
//set animpar1 to be array
this.animPar1 = {};
//handle events
switch (e.type) {
case 'touchmove':
this.vertical = Math.abs(this.pageX - e.originalEvent.touches[0].pageX) < Math.abs(this.pageY - e.originalEvent.touches[0].pageY); //find out in which direction we are scrolling
this.distance = this.vertical ? this.pageY - e.originalEvent.touches[0].pageY : this.pageX - e.originalEvent.touches[0].pageX; //determine distance between touches
this.acc = Math.abs(this.distance / (i_v.i_time.touchmove - i_v.i_time.touchstart)); //calculate acceleration during movement (crucial)
//determine which property to animate, reset animProp first for when no criteria is matched
this.animProp = null;
if (this.vertical && this.i_scrollableY) { this.animProp = 'scrollTop'; } else if (!this.vertical && this.i_scrollableX) { this.animProp = 'scrollLeft'; }
//set animation parameters
if (this.animProp) { this.animPar1[this.animProp] = i_v.i_elem.data(this.animProp) + this.distance; }
this.animPar2 = { duration: 0 };
break;
case 'touchend':
this.touchTime = i_v.i_time.touchend - i_v.i_time.touchmove; //calculate touchtime: the time between release and last movement
this.i_maxOffset = (this.vertical ? i_v.i_elemH : i_v.i_elemW) * i_v.i_speedLimit; //(re)calculate max offset
//calculate the offset (the extra pixels for the momentum effect
this.offset = Math.pow(this.acc, 2) * (this.vertical ? i_v.i_elemH : i_v.i_elemW);
this.offset = (this.offset > this.i_maxOffset) ? this.i_maxOffset : this.offset;
this.offset = (this.distance < 0) ? -i_v.multiplier * this.offset : i_v.multiplier * this.offset;
//if the touchtime is low enough, the offset is not null and the offset is above the offsetThreshold, (re)set the animation parameters to include momentum
if ((this.touchTime < i_v.i_moveThreshold) && this.offset !== 0 && Math.abs(this.offset) > (i_v.i_offsetThreshold)) {
if (this.animProp) { this.animPar1[this.animProp] = i_v.i_elem.data(this.animProp) + this.distance + this.offset; }
this.animPar2 = { duration: i_v.i_duration, easing : 'hnlinertial', complete: function () {
//reset multiplier
i_v.multiplier = 1;
}};
}
break;
}
// run the animation on the element
if ((this.i_scrollableY || this.i_scrollableX) && this.animProp) {
i_v.i_elem.stop(true, false).animate(this.animPar1, this.animPar2);
}
}
});
Another observation: I also tried various combinations of e.stopPropagation() on menu div and e.preventDefault() on window/body at touchmove event, but without success, I only managed to prevent scrolling I wanted and not scrolling I did not want. I also tried to have a div over whole document, with z-index between document and menu, visible only between touchstart and touchend, but it did not receive touchmove event (because it was under menu div).
Here is a solution that uses jQuery for the events.
var stuff = {};
$('#scroller').on('touchstart',stuff,function(e){
e.data.max = this.scrollHeight - this.offsetHeight;
e.data.y = e.originalEvent.pageY;
}).on('touchmove',stuff,function(e){
var dy = e.data.y - e.originalEvent.pageY;
// if scrolling up and at the top, or down and at the bottom
if((dy < 0 && this.scrollTop < 1)||(dy > 0 && this.scrollTop >= e.data.max)){
e.preventDefault();
};
});
First position the innerScroller wherever you want on the screen and then fix outerScroller by setting it css to 'hidden'. When you want to restore it you can set it back to 'auto' or 'scroll', whichever you used previously.
Here is my implementation which works on touch devices and laptops.
function ScrollManager() {
let startYCoord;
function getScrollDiff(event) {
let delta = 0;
switch (event.type) {
case 'mousewheel':
delta = event.wheelDelta ? event.wheelDelta : -1 * event.deltaY;
break;
case 'touchstart':
startYCoord = event.touches[0].clientY;
break;
case 'touchmove': {
const yCoord = event.touches[0].clientY;
delta = yCoord - startYCoord;
startYCoord = yCoord;
break;
}
}
return delta;
}
function getScrollDirection(event) {
return getScrollDiff(event) >= 0 ? 'UP' : 'DOWN';
}
function blockScrollOutside(targetElement, event) {
const { target } = event;
const isScrollAllowed = targetElement.contains(target);
const isTouchStart = event.type === 'touchstart';
let doScrollBlock = !isTouchStart;
if (isScrollAllowed) {
const isScrollingUp = getScrollDirection(event) === 'UP';
const elementHeight = targetElement.scrollHeight - targetElement.offsetHeight;
doScrollBlock =
doScrollBlock &&
((isScrollingUp && targetElement.scrollTop <= 0) ||
(!isScrollingUp && targetElement.scrollTop >= elementHeight));
}
if (doScrollBlock) {
event.preventDefault();
}
}
return {
blockScrollOutside,
getScrollDirection,
};
}
const scrollManager = ScrollManager();
const testBlock = document.body.querySelector('.test');
function handleScroll(event) {
scrollManager.blockScrollOutside(testBlock, event);
}
window.addEventListener('scroll', handleScroll);
window.addEventListener('mousewheel', handleScroll);
window.addEventListener('touchstart', handleScroll);
window.addEventListener('touchmove', handleScroll);
.main {
border: 1px solid red;
height: 200vh;
}
.test {
border: 1px solid green;
height: 300px;
width: 300px;
overflow-y: auto;
position: absolute;
top: 100px;
left: 50%;
}
.content {
height: 100vh;
}
<div class="main">
<div class="test">
<div class="content"></div>
</div>
</div>
This is what worked for me for Android and IOS devices.
Imagine that we have a div class="backdrop"> element that we don't want to be scrolled, ever. But we want to be able to scroll over an element that is on top of this backdrop.
function handleTouchMove(event) {
const [backdrop] = document.getElementsByClassName('backdrop');
const isScrollingBackdrop = backdrop === event.target;
isScrollingBackdrop ? event.preventDefault() : event.stopPropagation();
}
window.addEventListener('touchmove', handleTouchMove, { passive: false });
So, we listen to the touchmove event, if we're scrolling over the backdrop, we prevent it. If we're scrolling over something else, we allow it but stop its propagation so it doesn't scroll also the backdrop.
Of course this is pretty basic and can be re-worked and expanded a lot, but this is what fixed my issue in a VueJs2 project.
Hope it helps! ;)
I was able to disable scrolling of the main document by adding css "overflow-y: hidden" on HTML.
It did not mess with positioning at all.
I have a function to follow the object after the mouse,
and I want to be able to stop and start following at will, without hiding the object.
It almost works as I wanted, and is following the mouse indeed, but I cannot make it move initial position without actually moving the mouse.
E.G. When I trigger the function, the object is still somewhere in another place, until I move the mouse, but what I'm trying to do is to move it the initial position first, before attaching the mousemove event.
Here is how I want to trigger the function:
showtrail();
function showtrail(shit){
//this is how I tried to set the initial position first, but this get me an error:..
//followmouse();
document.onmousemove=followmouse; //and this is how I attach the event.
}
This is a part of the actual function to move the object,
but, I can't get the coordinates if I try to initilize/imitate the first movement.
function followmouse(e){
var xcoord=offsetfrommouse[0]
var ycoord=offsetfrommouse[1]
if (typeof e != "undefined"){ //This- if triggered by mousemove, and it works
xcoord+=e.pageX
ycoord+=e.pageY
}
else { //this was meant for the initial call, but... for some reason
xcoord+=document.body.scrollLeft+event.clientX // it triggers an error,
ycoord+=document.body.scrollTop+event.clientY // saying event.clientX undefined.
}
}
So the event.clientX never seems to work, and I cannot figure out how to get the actual mouse position otherwise..
Please guide..
event.clientX and event.clientY are wrong. They should be e.clientX and e.clientY
A more elegant cross browser way to get xcoord and ycoord in followmouse(e) is:
xcoord = e.pageX||(e.clientX+(document.body.scrollLeft||document.documentElement.scrollLeft));
ycoord = e.pageY||(e.clientY+(document.body.scrollTop||document.documentElement.scrollTop));
Now if I'm getting it right, the object that follows is expected to have an initial absolute position and displayed as a block, meaning that you have initial x and y (left and top). Therefore by using a global bool var for currently following or not you're done.
<style>
...
#trail {position:absolute;left:0;top:0;display:none}
...
</style>
<script>
var following = false;
...
function followmouse(e){
if (!following){
document.getElementById('trail').style.display='none';
return;
}
...
document.getElementById('trail').style.display='block';
}
</script>
By changing display you have the option to move your #trail to its initial position and then follow the mouse, and the option to avoid the move and let it follow the mouse from its latest following position.
EDIT 1:
For this very purpose, I recommend using of requestAnimationFrame API, not classic DOM events. said API is more efficient for creating animations and pausing them.
take a look at this too: requestAnimationFrame for smart animating
This is sad, but true that you can not get mouse's initial position before moving mouse on a webpage. I mean you can't calibrate your object before mousemove event. this is what I will do in a similar project:
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
<script type="text/javascript">
var targetID = 'mydiv'; // div that is to follow the mouse
var pauseFollowing = false;
// (must be position:absolute)
var offX = 15; // X offset from mouse position
var offY = 15; // Y offset from mouse position
function mouseX(evt) {if (!evt) evt = window.event; if (evt.pageX) return evt.pageX; else if (evt.clientX)return evt.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft); else return 0;}
function mouseY(evt) {if (!evt) evt = window.event; if (evt.pageY) return evt.pageY; else if (evt.clientY)return evt.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop); else return 0;}
function follow(evt) {
if(pauseFollowing) {
//or do something else at pause
return false;
}
var obj = document.getElementById(targetID).style;
obj.visibility = 'visible';
obj.left = (parseInt(mouseX(evt))+offX) + 'px';
obj.top = (parseInt(mouseY(evt))+offY) + 'px';
}
function toggleFollow() {
pauseFollowing = !pauseFollowing;
}
window.onload = function() {
window.onclick = toggleFollow;
document.onmousemove = follow;
}
</script>
</head>
<body>
<div id="mydiv" style="visibility: hidden; top:0; left:0 ;width: 100px; height: 100px; background: #ff0; position: absolute;"></div>
</body>
</html>
Alright that's how I done it.
The best Idea was to always capture a move to set position in a global var.
Now I have an option to display it fixed at any specific place (if I pass coords to showtrail)
or to actually follow the mouse;
I also added an event listener, so if the mouse gets outside the window while following- it will be hidden.
So far it works exactly as I wanted:
var trailimage=["scripts/loading_mouse.gif", 24, 24] //image path, plus width and height
var offsetfrommouse=[2,10] //image x,y offsets from cursor position in pixels. Enter 0,0 for no offset
var global_coord=[0,0]
var follow=false;
if (document.getElementById || document.all)
document.write('<div id="trailimageid" style="z-index: 10;position:absolute;display:none;left:0px;top:0px;width:1px;height:1px"><img src="'+trailimage[0]+'" border="0" width="'+trailimage[1]+'px" height="'+trailimage[2]+'px"></div>')
function gettrailobj(){
if (document.getElementById)
return document.getElementById("trailimageid").style
else if (document.all)
return document.all.trailimagid.style
}
function hidett(){ gettrailobj().display="none"; }
function showtt(){ gettrailobj().display="block"; }
function truebody(){ return (document.body||document.documentElement); }
function showtrail(shit){
if (typeof shit == "undefined"){ //Follow Mouse
follow=true;
setmousepos(global_coord[0],global_coord[1]);
}
else { //Set fixed in specific place
follow=false;
showtt()
gettrailobj().left=shit.left+6+"px"
gettrailobj().top=shit.top-5+"px"
}
}
function hidetrail(){
hidett()
follow=false;
}
function setcoord(e){
var xcoord=offsetfrommouse[0]
var ycoord=offsetfrommouse[1]
var xxcoord = e.pageX||(e.clientX+truebody().scrollLeft);
var yycoord = e.pageY||(e.clientY+truebody().scrollTop);
if (typeof xxcoord != "undefined"&&typeof yycoord != "undefined"){
xcoord+=xxcoord;
ycoord+=yycoord;
global_coord=[xcoord,ycoord];
if (follow) setmousepos(xcoord,ycoord);
}}
function setmousepos(xcoord,ycoord){
var docwidth=truebody().scrollLeft+truebody().clientWidth
var docheight=Math.max(truebody().scrollHeight, truebody().clientHeight)
if ((xcoord+trailimage[1]+3>docwidth || ycoord+trailimage[2]> docheight ||!follow)){
hidett()
}
else{
showtt();
gettrailobj().left=xcoord+"px"
gettrailobj().top=ycoord+"px"
}
}
window.addEventListener("mouseout",
function(e){
mouseX = e.pageX;
mouseY = e.pageY;
if ((mouseY >= 0 && mouseY <= window.innerHeight)
&& (mouseX >= 0 && mouseX <= window.innerWidth)){
return false;
}else{
if (follow) hidett()
}
},
false);
document.onmousemove=setcoord;