I'm working on a javascript draggable interaction that has to work with both mouse and touch input and does not have any dependencies. So far it works fine on desktops and mobiles.
Except Firefox for Android shows the following behaviour:
page is not scrolled: fine
page is scrolled vertically: element can only be dragged horizontally
page is scrolled horizontally: element can only be dragged vertically
page is scrolled both vertically and horizontally: element can't be
dragged at all
scroll page back to the very top and left: element can be dragged as
expected again
The code:
var evtStart, evtMove, evtEnd;
if ('ontouchend' in window) {
evtStart = 'touchstart';
evtMove = 'touchmove';
evtEnd = 'touchend';
} else {
evtStart = 'mousedown';
evtMove = 'mousemove';
evtEnd = 'mouseup';
}
// BASIC DRAGGABLE INTERACTION
// No further configuration, just drags ....
var panel = document.querySelector('.testpanel');
panel.addEventListener(evtStart, function(e) {
e.preventDefault();
var styles = window.getComputedStyle(panel),
left = parseFloat(styles.left), // css left on mousedown
top = parseFloat(styles.top), // css top on mousedown
psx = e.pageX || e.touches[0].pageX, // pointer x on mousedown
psy = e.pageY || e.touches[0].pageY; // pointer y on mousedown
// function actually draging the elmt
var drag = function (e) {
e.preventDefault();
var pmx = e.pageX || e.touches[0].pageX, // current pointer x while pointer moves
pmy = e.pageY || e.touches[0].pageY; // current pointer y while pointer moves
panel.style.left = left + (pmx - psx) + 'px'; // set new css left of elmt
panel.style.top = top + (pmy - psy) + 'px'; // set new css top of elmt
};
panel.addEventListener(evtMove, drag);
panel.addEventListener(evtEnd, function () {
panel.removeEventListener(evtMove, drag);
});
});
Demo page
Again, it works fine on desktop and mobiles except FF for Android.
Why does it not work on FF for Android? Is it something in my code or is it a bug in FF? So far I could not find anything helpful.
I would greatly appreciate any help.
Finally I found it myself:
Just swap pageX/pageY with clientX/clientY and it works in FF for Android as well.
I guess the implementation in FF for Android is somewhat different, pageX/pageY are not finalized standards yet according to MDN.
Related
In a nutshell I'm creating a sticky button that shows after the scroll position pass a target element on the page. I'm trying to calculate the distance from the top of the page to the bottom of the target element. The script below seems to work find on load but if I resize the browser the numbers are not recalculated to get the correct distance. I know I should be using another event listener like "on resize" but I can't seem to get the logic right with my current code. Any help is welcome thanks!
Current Code
$(function(){
function ctaBundle(){
//target element
var cardsContainer = document.querySelector('.card-block');
// calculate the distance from top to the bottom of target element plus padding offset
var elDistanceToTop = window.pageYOffset + cardsContainer.getBoundingClientRect().bottom - 48;
//using to only trigger on mobile using mql
var mq = window.matchMedia('(max-width: 30em)');
//function with if statement to fade in if you pass target element
$(window).on('scroll', function() {
if ($(this).scrollTop() > elDistanceToTop && mq.matches) {
$(".sticky-cta-double").fadeIn();
}else{
$(".sticky-cta-double").hide();
}
});
}
ctaBundle();
});
I think I figured it out. By removing the on scroll event in the function and adding both event listeners after the function it seems to work.
$(function(){
function ctaBundle(){
var cardsContainer = document.querySelector('.card-block');
var bundleHeader = document.querySelector('.bundle-header');
var elDistanceToTop = window.pageYOffset + cardsContainer.getBoundingClientRect().bottom - 48;
var mq = window.matchMedia('(max-width: 30em)');
if ($(this).scrollTop() > elDistanceToTop && mq.matches) {
$(".sticky-cta-double").fadeIn();
}else{
$(".sticky-cta-double").hide();
}
}
ctaBundle();
window.addEventListener('resize', ctaBundle, false);
window.addEventListener('scroll', ctaBundle, false);
});
If anyone has a better answer/logic please let me know but this seems to be working as intended now.
I have a page with a header section. In it, two blocks that move sideways after scrolling or dragging on the mobile.
I am trying to set the scrolling for the header, but I want too that the rest of the page stays in place until the side blocks reach left: -50% and right:-50%.
I have an event scroll set to header, with pageYoffset values.
I tried to set the rest of the content the page gives to the section with the position:fixed, but then the scroll does not work anymore, and do not count pageYoffset.
Do you have any ideas how to get around it, so that the rest of the page would scroll only after the full unveiling of the header?
(in short, the pink section should be on top and wait until the header disappears)
let current = $(window).scrollTop();
let windowHeight = $(window).height();
let eleLeft = $(".cd-half-left");
let eleRight = $(".cd-half-right");
let currPositionLeft = eleLeft.position().left;
let currPositionRight = eleRight.position().right;
let headerHeaight = $(".cd-section").height();
let halfBlockWidth = $(".cd-half-block").width();
let windowWidth = $(window).width();
$(window).scroll(function (event) {
current = $(window).scrollTop();
console.log({total:total,current:current});
var newPosition = ((current / headerHeaight)*100) / 2;
console.log(newPosition);
eleLeft.css({left:"-"+newPosition+'%'});
eleRight.css({right:"-"+newPosition+'%'});
});
FIDDLE
A solution would be not to use window scroll but instead handle scroll gesture (from mousewheel and touchmove) to control left and right panel, and prevent actual scroll when the panels are not fully opened.
so instead of $(window].scroll(handler), try with $('.cd-block').bind('mousewheel', handler) and $('.cd-block').bind('mousewheel', handler)
The handler being:
function updateCurrent(event) {
if (current >= 50) {
current = 50;
} else {
if (current <= 0) {
current = 0;
}
// if below 50 we cancel the event to prevent the scroll
event.originalEvent.preventDefault();
}
eleLeft.css({left:"-"+current+'%'});
eleRight.css({right:"-"+current+'%'});
}
Here is a buggy but working solution (keyboard space, up and down should be handled too):
fiddle
On the Chrome browser, I get everything working, but, if I want to make it work at the native Android Browser, I can't see the div I want to show. Instead, it shows me the first span with the class leftscroller.
How could this be? I tried Android 4.1.2 and 4.1.3 browsers.
This is the JS code:
$('.navigation-block').on('click', function(){
$(this).parents('#navigation.small').toggleClass('open');
});
/* Scrollable */
var showScrollIndicators = function() {
var subnav = $('#navigation.small .subnav')
if (subnav.length > 0) {
var scrollLeft = subnav.scrollLeft();
var viewportWidth = subnav.innerWidth();
var scrollWidth = subnav[0].scrollWidth;
var leftScroller = $('#navigation.small').find('.leftscroller'),
rightScroller = $('#navigation.small').find('.rightscroller');
leftScroller.addClass('enabled');
rightScroller.addClass('enabled');
if (scrollLeft === 0) {
// we've reached the far left part of the scroll area
leftScroller.removeClass('enabled');
}
if (scrollWidth - scrollLeft <= viewportWidth) {
// we've reached the far right part
rightScroller.removeClass('enabled');
}
}
};
/* Add scroll indicators */
$('#navigation.small .ph_subnav').append(
'<span class="leftscroller">You can scroll to the left</span>' +
'<span class="rightscroller">You can scroll to the right</span>'
);
showScrollIndicators();
$('#navigation.small .subnav').scroll(function() {
showScrollIndicators();
});
What am I doing wrong? So the whole area which falls beneath /Scrollable/ is not shown.
I'm having a problem in IE 9 (haven't tested 8), What the code is supposed to do is detect whether or not the mouse was dragged after clicking on a certain element. The problem is that IE 9 automatically enters the $(window).mousemove event handler even though I don't move the mouse. Works Fine in Chrome and FF.
$(Element).mousedown(function() {
$(window).mousemove(function() {
isDragging = true;
$(window).unbind("mousemove");
});
}).mouseup(function() {
$(window).unbind("mousemove");
});
You should be only starting a drag after the mouse has moved a certain minimum distance. To do that, record the mouse position in the mousedown handler and then in mousemove, only start your drag when the mouse has moved a minimum distance.
$(Element).mousedown(function(e) {
var x = e.clientX;
var y = e.clientY;
var minMovement = 3;
$(window).mousemove(function(e) {
if (Math.abs(e.clientX - x) > minMovement || Math.abs(e.clientY - y) > minMovement) {
isDragging = true;
$(window).unbind("mousemove");
}
});
}).mouseup(function() {
$(window).unbind("mousemove");
});
FYI, some mice can record very, very tiny movements (less than one pixel on the screen) so it's likely that the mouse does actually move after you press the mouse down and IE is probably just reporting that movement. Other browsers may wait until the mouse moves a whole pixel. In any case, if you require a minimum number of pixels of movement then you won't have this issue.
Because the movement of the mouse is so minute with a laser mouse I changed your method a bit to better suit my need of monitoring just the fact of weather or not the mouse is moving:
$(window).mousemove(function (e) {
var smallNo = 0;
var x = e.clientX;
var y = e.clientY;
var minMovement = 1;
if ((x - smallNo) > minMovement || (y - smallNo) > minMovement) {
countDownTime = logoutTime;
}
});
Even though the mousemove event is firing constantly the coordinates do not change unless you physically move your mouse. I used that to figure out weather or not the mouse had moved or not.
I have made something like a drag-and-drop element with JS.
function Draggable(elm) {
this.d = elm;
this.style.position = "absolute";
elm.onselectstart = elm.ondragstart = function() { return false; }
elm.addEventListener('mousedown', this._start.bindAsEventListener(this), false);
}
Draggable.prototype._start = function (event) {
this.deltaX = event.clientX;
this.deltaY = event.clientY;
if (!this.dm) {
this.dm = document.createElement("div");
this.dm.setAttribute("class", "dragger");
this.dm.onmousemove = this._move.bindAsEventListener(this);
this.dm.onmouseup = this._stop.bindAsEventListener(this);
this.dm.onselectstart = RetFalse;
this.dm.ondragstart = RetFalse;
}
document.body.appendChild(this.dm);
this.lastX = this.lastY = 0;
this.ondragstart();
return false;
}
Draggable.prototype._move = function (event) {
var newx = (event.clientX - this.deltaX);
var newy = (event.clientY - this.deltaY);
if (newx < this.x0) newx = this.x0;
if (newx > this.x1) newx = this.x1;
if (newy < this.y0) newy = this.y0;
if (newy > this.y1) newy = this.y1;
this.d.style.left = newx + "px";
this.d.style.top = newy + "px";
if (window.getSelection) window.getSelection().removeAllRanges(); else document.selection.empty();
return false;
}
Draggable.prototype._stop = function (event) {
document.body.removeChild(this.dm);
return false;
}
The "dragger" is transparent DIV that fills the whole page, to prevent the dragged target from losing capture when mouse moves too fast. (If I could capture the mouse, I would need it.)
.dragger {
cursor:move;
position:absolute;
width:100%;height:100%;
left:0px;top:0px;
margin:0px;padding:0px;
z-index:32767;
background: transparent;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
However, if I:
Press left mouse button on the draggable element
Drag it outside the client area (outside the brower window)
Release mouse button
The element will lose the capture, so that if I move the cursor back,
without having receive a mouse-up event, the element follows the cursor everywhere.
(until you click to make a mouse-up again.)
Just now, I saw it perfectly done on this website: (www.box.net)
Even if you release mouse button outside the browser window, the blue selecting box can still resize when the cursor moves, and disappear when button is released.
But I cannot receive any mousemove or mouseup when cursor is outside.
What API can I use to capture the mouse?
As you can see, I'm using Chrome Browser.
It is said that there's no API like HTMLElement.setCapture in non-IE browser.
This page uses jQuery, but what does jQuery use?
What is the raw javascript Code to do that?
Instead of creating a big, transparent element (dm), bind your mouse events to window.
It gets mouse events everywhere on the page; during dragging you'll keep getting mousemove events even if the cursor goes outside the window, as well as a mouseup if you release the mouse button outside the window.
P.S. If you call .preventDefault() on the mousedown event, the browser won’t select any text and you won’t have to clear the selection on mousemove.
Although it is a little outdated (FF now supports setCapture), I found this article to be extraordinarily helpful. The basis of the fix goes something like this:
var dragTarget = element.setCapture ? element : document; // setCapture fix
I've set up this little example. The javascript is copied straight from a webpage I'm building for a client where it works perfect*. Unfortunately I haven't been able to find a fix for draggable content inside an iframe, so it will still appear broken in Chrome if viewed at jsFiddle, Codepen, etc. You'll have to trust me that it works (or try it out yourself). If anyone knows of a fix for this iframe issue, please share.
*in Chrome, Safari and FF, I haven't tested in Opera or IE yet