Vanilla JS Swipe Function Now Overrides Links/Expected Behaviour - javascript

I have written a detect swipe left or right script; it has one unintended consequence. Links that reside within the swipeable container are no longer clickable.
I was wondering if anyone had any solutions they could point me to or any thoughts to experiment with my code.
I have tried:
Adding/toggling with event.preventDefault() within the Pointer Events
Looking at other get gesture type scripts, they include a distance threshold. If it is does not meet the threshold, I remove all event listeners. Adding this creates an unexpected result of working, only if you click the button twice.
Find the clickable elements within the container such as a span a , add an incremental click event listener then change the window location ref. Kinda works, but when you swipe near the span; the selection takes over and swipe actions stop. Experimenting with css touch-action: none or user-select:none have not improved the experience. It feels like I am breaking the default behaviour and reinventing the wheel.
link to a demo here on JS Bin
JS code here
window.addEventListener('load', () => {
let test = new getSwipeX({
elementId: 'container'
});
// previous attempt - adding a click on top of the browser default
let grabBtn = document.getElementsByClassName('button')[0];
grabBtn.addEventListener('click', (e) => {
window.location.href = 'www.google.co.uk'
});
})
function getSwipeX({elementId}) {
this.e = document.getElementsByClassName(elementId)[0];
this.initialPosition = 0;
this.lastPosition = 0;
this.threshold = 200;
this.diffInPosition = null;
this.diffVsThreshold = null;
this.gestureState = 0;
this.getTouchStart = (event) => {
event.stopPropagation();
if (window.PointerEvent) {
this.e.setPointerCapture(event.pointerId);
}
return this.initalTouchPos = this.getGesturePoint(event);
}
this.getTouchMove = (event) => {
event.stopPropagation();
return this.lastPosition = this.getGesturePoint(event);
}
this.getTouchEnd = (event) => {
event.stopPropagation();
if (window.PointerEvent) {
this.e.releasePointerCapture(event.pointerId);
}
this.doSomething();
this.initialPosition = 0;
}
this.getGesturePoint = (event) => {
this.point = event.pageX
return this.point;
}
this.whatGestureDirection = (event) => {
this.diffInPosition = this.initalTouchPos - this.lastPosition;
this.diffVsThreshold = Math.abs(this.diffInPosition) > this.threshold;
(Math.sign(this.diffInPosition) > 0) ? this.gestureState = 'L' : (Math.sign(this.diffInPosition) < 0) ? this.gestureState = 'R' : this.gestureState = 'N';
return [this.diffInPosition, this.diffVsThreshold, this.gestureState];
}
this.doSomething = (event) => {
let [gestureDelta,gestureThreshold,gestureDirection] = this.whatGestureDirection();
// USE THIS TO DEBUG
console.log(gestureDelta,gestureThreshold,gestureDirection);
if (gestureThreshold) {
(gestureDirection == 'L') ? console.log('Left') : console.log('Right');
} else {
this.e.removeEventListener('pointerdown', this.getTouchStart, true);
this.e.removeEventListener('pointermove', this.getTouchMove, true);
this.e.removeEventListener('pointerup', this.getTouchEnd, true);
this.e.removeEventListener('pointercancel', this.getTouchEnd, true);
}
}
if (window.PointerEvent) {
this.e.addEventListener('pointerdown', this.getTouchStart, true);
this.e.addEventListener('pointermove', this.getTouchMove, true);
this.e.addEventListener('pointerup', this.getTouchEnd, true);
this.e.addEventListener('pointercancel', this.getTouchEnd, true);
}
}

Related

How to trigger a JavaScript function only when the user scrolls the page

I need to add positon: fixed to .box only if the user scrolls the page.
The important point here is that the element with the class .box get generated only after the user scrolls the page.
This is what I came up with:
window.addEventListener('scroll', () => {
const myDiv = document.querySelector('.box')
if (myDiv) {
if (myDiv.style.position !== 'fixed') {
myDiv.style.position = 'fixed'
}
}
})
The problem with my code is that the scroll event is now going to fire a million events all the time and kill performance.
What would be the right way to achieve the above without firing scroll event again and again.
After the box has been inserted into the DOM, you can query the box and add the fixed position style to it.
If you're doing this often then here is a more general function that will (given a selector, e.g. .box) wait for an element to be inserted into the DOM:
function waitForElement(selector) {
let element = null;
let handled = false;
let nextFrame;
return (callback) => {
function tick() {
element = document.querySelector(selector);
if (element === null) {
nextFrame = requestAnimationFrame(tick);
}
if (element !== null && !handled) {
handled = true;
callback(element);
}
}
cancelAnimationFrame(nextFrame);
requestAnimationFrame(tick);
};
}
Usage:
const waitForBox = waitForElement(".box");
waitForBox((box) => {
box.style.position = "fixed";
});
Demo:
// Demo section, ignore this
const plane = document.querySelector(".plane");
const box = document.createElement("div");
box.className = "box";
box.style.width = "100px";
box.style.height = "100px";
box.style.background = "crimson";
function waitForElement(selector) {
let element = null;
let handled = false;
let nextFrame;
return (callback) => {
function tick() {
element = document.querySelector(selector);
if (element === null) {
nextFrame = requestAnimationFrame(tick);
}
if (element !== null && !handled) {
handled = true;
callback(element);
}
}
cancelAnimationFrame(nextFrame);
requestAnimationFrame(tick);
};
}
// A function to call inside scroll callback that will
// wait for the .box element to be found in the DOM
const waitForBox = waitForElement(".box");
let boxAppended = false;
document.addEventListener("scroll", () => {
// Appending the box to the DOM, ignore this
if (!boxAppended) {
boxAppended = true;
plane.appendChild(box);
}
// Callback will be fired once when the .box
// element is found in the DOM
waitForBox((box) => {
box.style.position = "fixed";
});
});
body {
padding: 0;
height: 100vh;
overflow: auto;
}
.plane {
height: 200vh;
}
<div class="plane"></div>
You can for example use requestAnimationFrame() or setTimeout() to prevent the scroll event from firing too many times.
Here is an example of how to do this with requestAnimationFrame() from the Mozilla scroll event documentation:
let lastKnownScrollPosition = 0;
let ticking = false;
function doSomething(scrollPos) {
// Do something with the scroll position
}
document.addEventListener('scroll', function(e) {
lastKnownScrollPosition = window.scrollY;
if (!ticking) {
window.requestAnimationFrame(function() {
doSomething(lastKnownScrollPosition);
ticking = false;
});
ticking = true;
}
});

How to drag an Openseadragon canvas with mouse middle wheel button

I am using Openseadragon library with fabricjs overlay. I have a case where I want to drag the canvas but instead of mouse primary button, I want to drag it with middle mouse button press. Could anyone please help me get the desired behavior?
OpenSeadragon doesn't have a flag for that, but you can easily build it using the MouseTracker. Here's an example (coded from memory and not tested, but it should give you the idea).
var drag;
var mouseTracker = new OpenSeadragon.MouseTracker({
element: viewer.container,
nonPrimaryPressHandler: function(event) {
if (event.button === 1) { // Middle
drag = {
lastPos: event.position.clone()
};
}
},
moveHandler: function(event) {
if (drag) {
var deltaPixels = drag.lastPos.minus(event.position);
var deltaPoints = viewer.viewport.deltaPointsFromPixels(deltaPixels);
viewer.viewport.panBy(deltaPoints);
drag.lastPos = event.position.clone();
}
},
nonPrimaryReleaseHandler: function(event) {
if (event.button === 1) {
drag = null;
}
}
});
EDIT: I had a bug in the example code above; fixed.
Expanding on #iangilman 's answer...
To improve the user experience when the middle button is released outside the MouseTracker's element while dragging, causing the nonPrimaryReleaseHandler to never get called, the pointer can be captured...something like this:
var trackerElement = viewer.container;
var drag;
function capturePointer(event) {
if (OpenSeadragon.MouseTracker.havePointerCapture) {
if (OpenSeadragon.MouseTracker.havePointerEvents) {
// Can throw InvalidPointerId
try {
trackerElement.setPointerCapture(event.originalEvent.pointerId);
} catch () {
//
}
} else {
trackerElement.setCapture(true);
}
}
}
function releasePointer(event) {
if (OpenSeadragon.MouseTracker.havePointerCapture) {
if (OpenSeadragon.MouseTracker.havePointerEvents) {
// Can throw InvalidPointerId
try {
trackerElement.releasePointerCapture(event.originalEvent.pointerId);
} catch () {
//
}
} else {
trackerElement.releaseCapture();
}
}
}
var mouseTracker = new OpenSeadragon.MouseTracker({
element: trackerElement,
nonPrimaryPressHandler: function(event) {
if (event.button === 1) { // Middle
capturePointer(event);
drag = {
lastPos: event.position.clone()
};
}
},
moveHandler: function(event) {
if (drag) {
var deltaPixels = drag.lastPos.minus(event.position);
var deltaPoints = viewer.viewport.deltaPointsFromPixels(deltaPixels);
viewer.viewport.panBy(deltaPoints);
drag.lastPos = event.position.clone();
}
},
nonPrimaryReleaseHandler: function(event) {
if (event.button === 1) {
releasePointer(event);
drag = null;
}
}
});

Event Listener on images ignored within modal window

I'm stuck as to why I can't get an AddEventListener click event to work on a set of images that appear in a modal. I had them working before before the modal aspect was involve, but I'm not sure that the modal broke the image click event either.
Here is the function in question, which is called within a massive document.addEventListener("DOMContentLoaded", function (event) function:
var attachClick = function () {
Array.prototype.forEach.call(containers, function (n, i) {
n.addEventListener('click', function (e) {
// populate
cleanDrawer();
var mediaFilterSelected = document.querySelector('.media-tags .tag-container .selected');
var selectedFilters = "";
if (mediaFilterSelected != "" && mediaFilterSelected != null) {
selectedFilters = mediaFilterSelected.innerHTML;
}
var portfolioItemName = '';
var selectedID = this.getAttribute('data-portfolio-item-id');
var data = portfolioItems.filter(function (item) {
portfolioItemName = item.name;
return item.id === selectedID;
})[0];
clientNameContainer.innerHTML = data.name;
descriptionContainer.innerHTML = data.description;
var childItems = data.child_items;
//We will group the child items by media tag and target the unique instance from each group to get the right main banner
Array.prototype.groupBy = function (prop) {
return this.reduce(function (groups, item) {
var val = item[prop];
groups[val] = groups[val] || [];
groups[val].push(item);
return groups;
}, {});
}
var byTag = childItems.groupBy('media_tags');
if (childItems.length > 0) {
handleBannerItem(childItems[0]);
var byTagValues = Object.values(byTag);
byTagValues.forEach(function (tagValue) {
for (var t = 0; t < tagValue.length; t++) {
if (tagValue[t].media_tags == selectedFilters) {
handleBannerItem(tagValue[0]);
}
}
});
childItems.forEach(function (item, i) {
// console.log("childItems.forEach"); we get into here
var img = document.createElement('img'),
container = document.createElement('div'),
label = document.createElement('p');
container.appendChild(img);
var mediaTags = item.media_tags;
container.className = "thumb";
label.className = "childLabelInactive thumbLbl";
thumbsContainer.appendChild(container);
if (selectedFilters.length > 0 && mediaTags.length > 0) {
for (var x = 0; x < mediaTags.length; x++) {
if (mediaTags[x] == selectedFilters) {
container.className = "thumb active";
label.className = "childLabel thumbLbl";
}
}
}
else {
container.className = i == 0 ? "thumb active" : "thumb";
// console.log("no tags selected"); we get to here
}
img.src = item.thumb;
if (item.media_tags != 0 && item.media_tags != null) {
childMediaTags = item.media_tags;
childMediaTags.forEach(function (cMTag) {
varLabelTxt = document.createTextNode(cMTag);
container.appendChild(label);
label.appendChild(varLabelTxt);
});
}
//console.log("before adding click to images"); we get here
console.log(img.src);
img.addEventListener("click", function () {
console.log("thumbnail clicked"); //this is never reached
resetThumbs();
handleBannerItem(item);
container.className = "thumb active";
});
});
}
attachClick();
//open a modal to show off the portfolio pieces for the selected client
var tingleModal = document.querySelector('.tingle-modal');
drawer.className = 'drawer';
var portfolioModal = new tingle.modal({
onOpen: function() {
if(tingleModal){
tingleModal.remove();
}
console.log('modal open');
},
onClose: function() {
console.log('modal closed');
//tingleModal.remove();
}
});
e.preventDefault();
portfolioModal.open();
portfolioModal.setContent(document.querySelector('.drawer-content').innerHTML);
});
});
};
And the specific bit that I'm having trouble with:
console.log(img.src);
img.addEventListener("click", function () {
console.log("thumbnail clicked"); //this is never reached
resetThumbs();
handleBannerItem(item);
container.className = "thumb active";
});
I tried removing the e.PreventDefault() bit but that didn't solve the issue. I know the images are being created, so the img variable isn't empty. I feel like the addEventListener is setup correctly. I also tried moving that bit up just under the img.src = item.thumb line, but no luck. For Some reason, the click event just will not trigger for the images.
So if I understand correctly, you have a modal that lies above the images (it has a higher z-index)? Well in this case the clicks are not reaching the images as they will hit the modal. You can pass clicks through elements that lie above by applying the css property pointer-events: none; to the modal, but thats somehow controversial to what a modal is intended to do.
Are the images present in the modal on DOMContentLoaded? You may be able to try delegating the handling of clicks to a parent element if that's the case.
You can try the delegation approach shown here: Vanilla JavaScript Event Delegation

I want to make left click behave as right click using either JS or jQuery

I have been trying to trigger right click even when the users left click.
I have tried trigger, triggerHandler, mousedown but I wasn't able to get it to work.
I'm able to catch the click events themselves but not able to trigger the context menu.
Any ideas?
To trigger the mouse right click
function triggerRightClick(){
var evt = new MouseEvent("mousedown", {
view: window,
bubbles: true,
cancelable: true,
clientX: 20,
button: 2
});
some_div.dispatchEvent(evt);
}
To trigger the context menu
function triggerContextMenu(){
var evt = new MouseEvent("contextmenu", {
view: window
});
some_div.dispatchEvent(evt);
}
Here is the bin: http://jsbin.com/rimejisaxi
For better reference/explanation: https://stackoverflow.com/a/7914742/1957036
Use the following code for reversing mouse clicks.
$.extend($.ui.draggable.prototype, {
_mouseInit: function () {
var that = this;
if (!this.options.mouseButton) {
this.options.mouseButton = 1;
}
$.ui.mouse.prototype._mouseInit.apply(this, arguments);
if (this.options.mouseButton === 3) {
this.element.bind("contextmenu." + this.widgetName, function (event) {
if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
$.removeData(event.target, that.widgetName + ".preventClickEvent");
event.stopImmediatePropagation();
return false;
}
event.preventDefault();
return false;
});
}
this.started = false;
},
_mouseDown: function (event) {
// we may have missed mouseup (out of window)
(this._mouseStarted && this._mouseUp(event));
this._mouseDownEvent = event;
var that = this,
btnIsLeft = (event.which === this.options.mouseButton),
// event.target.nodeName works around a bug in IE 8 with
// disabled inputs (#7620)
elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
return true;
}
this.mouseDelayMet = !this.options.delay;
if (!this.mouseDelayMet) {
this._mouseDelayTimer = setTimeout(function () {
that.mouseDelayMet = true;
}, this.options.delay);
}
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
this._mouseStarted = (this._mouseStart(event) !== false);
if (!this._mouseStarted) {
event.preventDefault();
return true;
}
}
// Click event may never have fired (Gecko & Opera)
if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
$.removeData(event.target, this.widgetName + ".preventClickEvent");
}
// these delegates are required to keep context
this._mouseMoveDelegate = function (event) {
return that._mouseMove(event);
};
this._mouseUpDelegate = function (event) {
return that._mouseUp(event);
};
$(document)
.bind("mousemove." + this.widgetName, this._mouseMoveDelegate)
.bind("mouseup." + this.widgetName, this._mouseUpDelegate);
event.preventDefault();
mouseHandled = true;
return true;
}
});
Now at the function calling event use mouseButton : 3 for right click and 1 for left click

While mousedown JS

I'm trying to run a function while mousedown but for the life of me I can't get it to work while holding down but everything works by just clicking. I'm trying to change color of countries on a map as I hold down.
Here's my code:
var int;
var mouseStillDown = false;
function mousedown(geography)
{ console.log('mousedown '+mousedownID);
mouseStillDown = true;
int = setInterval( performWhileMouseDown(geography), 100);
}
function mouseup()
{
clearInterval(int);
mouseStillDown = false;
}
function mouseout()
{
clearInterval(int);
}
function performWhileMouseDown(geography)
{
if (!mouseStillDown)
{console.log('error');}
if (mouseStillDown) {
if(data[geography.id])
{
data[geography.id] ++;
}else
{
data[geography.id] = 1;
}
var m = {};
m[geography.id] = color(data[geography.id]);
map.updateChoropleth(m);
}
/* if (mouseStillDown)
{ setInterval(performWhileMouseDown(geography), 100); }*/
}
You could try to use mousemove instead, mousedown will only fire once.
var mouseDown = false;
window.addEventListener('mousedown', function() { mouseDown = true })
window.addEventListener('mouseup', function() { mouseDown = false })
window.addEventListener('mousemove', function() {
if (!mouseDown) {
return;
}
// perform while mouse is moving
})
here's what worked for me
var timeout ;
function mouseDown(geography){
timeout = setInterval( function(){
if(data[geography.id]){
data[geography.id] ++;
}else{
data[geography.id] = 1;
}
var m = {};
m[geography.id] = color(data[geography.id]);
map.updateChoropleth(m);}, 100);
return false;
}
function mouseUp(geography){
clearInterval(timeout);
return false;
}
The two conditions for your event are that the code executes every time there is an update in mouse position AND the mouse button is pressed.
Addressing the first part can be done with the 'mousemove' event, which fires when the mouse is moved over the element.
The second filter can be solved, by checking the mouse event, on if the button is pressed. If not, we don't execute the following code.
window.addEventListener('mousemove', function() { // Attach listener
if (event.buttons == 0) // Check event for button
return; // Button not pressed, exit
// perform while mouse is moving
})

Categories