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;
}
}
});
Related
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);
}
}
I am testing twoway-motion.js on Aframe, by providing a simple way to navigate specifically without a device orientation permission from a mobile phone.
please check this glitch page for details: https://glitch.com/~scrawny-efraasia
also please see twoway-motion.js by #flowerio
AFRAME.registerComponent('twoway-motion', {
schema: {
speed: { type: "number", default: 40 },
threshold: { type: "number", default: -40 },
nonMobileLoad: { type: "boolean", default: false },
removeCheckpoints: {type: "boolean", default: true },
chatty: {type: "boolean", default: true }
},
init: function () {
var twowaymotion = document.querySelector("[camera]").components["twoway-motion"];
twowaymotion.componentName = "twoway-motion";
report = function(text) {
if (twowaymotion.data.chatty) {
console.log(twowaymotion.componentName, ":", text);
}
}
report("init.");
// report("asked to load with speed=", this.data.speed);
if (!AFRAME.utils.device.isMobile() && this.data.nonMobileLoad === false) {
// this is only for mobile devices.
//document.querySelector("[camera]").removeAttribute("twoway-motion");
report("Retired. Will only work on mobile.");
return;
} else {
if (this.data.nonMobileLoad === true) {
report("Loading on non-mobile platform.");
}
}
if (this.el.components["wasd-controls"] === undefined) {
this.el.setAttribute("wasd-controls", "true");
report("Installing wasd-controls.");
}
this.el.components["wasd-controls"].data.acceleration = this.data.speed;
// two-way hides checkpoint-controls by default.
if (this.data.removeCheckpoints) {
if (this.el.components["checkpoint-controls"] !== undefined) {
var checkpoints = document.querySelectorAll("[checkpoint]");
for (var cp = 0; cp < checkpoints.length; cp++) {
checkpoints[cp].setAttribute("visible", false);
}
}
}
this.el.removeAttribute("universal-controls");
if (this.el.components["look-controls"] === undefined) {
this.el.setAttribute("look-controls", "true");
}
var cur = document.querySelector("[cursor]");
if (cur !== null) {
console.log(this.componentName, ": found a cursor.");
this.cur = cur;
//this.curcolor = cur.getAttribute("material").color;
this.curcolor = cur.getAttribute("color");
} else {
console.log(this.componentName, ": didn't find a cursor.");
}
var canvas = document.querySelector(".a-canvas");
canvas.addEventListener("mousedown", function (e) {
report("mousedown", e);
twowaymotion.touching = true;
this.touchTime = new Date().getTime();
});
canvas.addEventListener("mouseup", function (e) {
report("mouseup", e);
twowaymotion.touching = false;
});
canvas.addEventListener("touchstart", function (e) {
this.touch = e;
report("touches.length: ", e.touches.length);
if (e.touches.length > 1) {
report("multitouch: doing nothing");
} else {
report("touchstart", e);
twowaymotion.touching = true;
}
});
canvas.addEventListener("touchend", function () {
console.log(this.componentName, " touchend");
twowaymotion.touching = false;
});
},
update: function() {
if (this.el.components["twoway-controls"] !== undefined) {
this.el.components["wasd-controls"].data.acceleration = this.el.components["wasd-controls"].data.speed;
}
},
tick: function () {
if (!AFRAME.utils.device.isMobile() && this.data.nonMobileLoad === false) {
// this is only for mobile devices, unless you ask for it.
return;
}
if (!this.isPlaying) {
return;
}
var cam = this.el;
var camrot = cam.getAttribute("rotation");
if (camrot.x < this.data.threshold) {
// we are looking down
if (this.cur !== null && this.cur !== undefined) {
this.cur.setAttribute("material", "color", "orange");
}
if (this.touching === true) {
cam.components["wasd-controls"].keys["ArrowDown"] = true;
} else {
cam.components["wasd-controls"].keys["ArrowDown"] = false;
cam.components["wasd-controls"].keys["ArrowUp"] = false;
}
} else {
// we are looking forward or up
if (this.cur !== null && this.cur !== undefined) {
this.cur.setAttribute("material", "color", this.curcolor);
}
if (this.touching === true) {
cam.components["wasd-controls"].keys["ArrowUp"] = true;
} else {
cam.components["wasd-controls"].keys["ArrowDown"] = false;
cam.components["wasd-controls"].keys["ArrowUp"] = false;
}
}
},
pause: function () {
// we get isPlaying automatically from A-Frame
},
play: function () {
// we get isPlaying automatically from A-Frame
},
remove: function () {
if (this.el.components["wasd-controls"] === undefined) {
this.el.removeAttribute("wasd-controls");
}
} });
Since device orientation permission is not granted on mobile phones, move backward is not working, also, when the audience tries to rotate in a different direction by touching the screen or sliding on screen, it still functions as move forward.
from what I imagine, if there is a simple edit, if the audience touches the screen more than 2 seconds, it start to move forward, if audience just rotate, it will not move forward, since when you slide or touch on the screen to rotate the touching time might not be so long as 2 seconds...
this is the easiest solution that I can imagine under the restriction of without device orientation permission.
or is there any other better way to divide rotate and move forward by touching screen regarding touching time?
Thks!!!!!
I've been trying to get mithril touch to work using a good code on github:
https://gist.github.com/webcss/debc7b60451f2ad2af41
import m from 'mithril'
/*****************************************
/* DOM touch support module
/*****************************************/
if (!window.CustomEvent) {
window.CustomEvent = function (event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
window.CustomEvent.prototype = window.Event.prototype;
}
(function(document) {
var TAPTRESHOLD = 200, // time within a double tap should have happend
TAPPRECISION = 60 / 2, // distance to identify a swipe gesture
touch = { },
tapCount = 0, // counts the number of touchstart events
tapTimer = 0, // timer to detect double tap
isTouchSwipe = false, // set to true whenever
absolute = Math.abs,
touchSupported = 'ontouchstart' in window;
function parentIfText (node) {
return 'tagName' in node ? node : node.parentNode;
}
function dispatchEvent(type, touch) {
if(touchSupported) {
touch.originalEvent.preventDefault();
touch.originalEvent.stopImmediatePropagation();
}
var event = new CustomEvent(type, {
detail: touch,
bubbles: true,
cancelable: true
});
touch.target.dispatchEvent(event);
console.log(type);
touch = { };
tapCount = 0;
return event;
}
function touchStart(e) {
if( !touchSupported || e.touches.length === 1) {
var coords = e.targetTouches ? e.targetTouches[0] : e;
touch = {
originalEvent: e,
target: parentIfText(e.target),
x1: coords.pageX,
y1: coords.pageY,
x2: coords.pageX,
y2: coords.pageY
};
isTouchSwipe = false;
tapCount++;
if (!e.button || e.button === 1) {
clearTimeout(tapTimer);
tapTimer = setTimeout(function() {
if(absolute(touch.x2 - touch.x1) < TAPPRECISION &&
absolute(touch.y2 - touch.y2) < TAPPRECISION &&
!isTouchSwipe) {
dispatchEvent((tapCount===2)? 'dbltap' : 'tap', touch);
clearTimeout(tapTimer);
}
tapCount = 0;
}, TAPTRESHOLD);
}
}
}
function touchMove(e) {
var coords = e.changedTouches ? e.changedTouches[0] : e;
isTouchSwipe = true;
touch.x2 = coords.pageX;
touch.y2 = coords.pageY;
/* the following is obsolete since at least chrome handles this
// if movement is detected within 200ms from start, preventDefault to preserve browser scroll etc.
// if (touch.target &&
// (absolute(touch.y2 - touch.y1) <= TAPPRECISION ||
// absolute(touch.x2 - touch.x1) <= TAPPRECISION)
// ) {
// e.preventDefault();
// touchCancel(e);
// }
*/
}
function touchCancel(e) {
touch = {};
tapCount = 0;
isTouchSwipe = false;
}
function touchEnd(e) {
var distX = touch.x2 - touch.x1,
distY = touch.y2 - touch.y1,
absX = absolute(distX),
absY = absolute(distY);
// use setTimeout here to register swipe over tap correctly,
// otherwise a tap would be fired immediatly after a swipe
setTimeout(function() {
isTouchSwipe = false;
},0);
// if there was swipe movement, resolve the direction of swipe
if(absX || absY) {
if(absX > absY) {
dispatchEvent((distX<0)? 'swipeleft': 'swiperight', touch);
} else {
dispatchEvent((distY<0)? 'swipeup': 'swipedown', touch);
}
}
}
document.addEventListener(touchSupported ? 'touchstart' : 'mousedown', touchStart, false);
document.addEventListener(touchSupported ? 'touchmove' : 'mousemove', touchMove, false);
document.addEventListener(touchSupported ? 'touchend' : 'mouseup', touchEnd, false);
// on touch devices, the taphold complies with contextmenu
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
dispatchEvent('taphold', {
originalEvent: e,
target: parentIfText(e.target)
});
}, false);
if (touchSupported) {
document.addEventListener('touchcancel', touchCancel, false);
}
}(window.document));
m.touchHelper = function(options) {
return function(element, initialized, context) {
if (!initialized) {
Object.keys(options).forEach(function(touchType) {
element.addEventListener(touchType, options[touchType], false);
});
context.onunload = function() {
Object.keys(options).forEach(function(touchType) {
element.removeEventListener(touchType, options[touchType], false);
});
};
}
};
};
The only thing I've added is import m from 'mithril'.
Launching the app I can see that everything is registering as should through the console, however, in my main code I want to use that touch data:
const app = {
view: vnode => {
return m('.main', {config: m.touchHelper({ 'tap': consoleLog})
}
}
function consoleLog() {
console.log('Triggered')
}
However, this function is not triggered.
I don't know how much Mithril has changed since February 2017 (I have only recently picked up Mithril), but that m.touchHelper seems weird. What are element, initialized and context? Why is a function returned; is it ever called?
Using m.touchHelper with the config attribute seems also weird. When you do this:
return m('.main', {config: m.touchHelper({'tap': consoleLog})})
the resulting DOM element looks like this:
<div config="function(element, initialized, context) {
if (!initialized) {
Object.keys(options).forEach(function(touchType) {
element.addEventListener(touchType, options[touchType], false);
});
context.onunload = function() {
Object.keys(options).forEach(function(touchType) {
element.removeEventListener(touchType, options[touchType], false);
});
};
}
}" class="main"></div>
See JSFiddle with your code. (Note that I added missing }) to the end of app's view method.)
I would change m.touchHelper to something like this:
m.touchHelper = function(vnode, options) {
if (vnode.state.initialized) return;
vnode.state.initialized = true;
Object.keys(options).forEach(function(touchType) {
vnode.dom.addEventListener(touchType, options[touchType], false);
});
// Note that I removed the `context.unload` part as I'm unsure what its purpose was
};
And call it in a component's oncreate lifecycle method:
const app = {
oncreate(vnode) {
m.touchHelper(vnode, {'tap': consoleLog});
},
view: vnode => {
return m('.main');
}
};
Then it seems to work. See JSFiddle with updated code.
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
})
At the moment we are using the below script to create a lightweight lightbox for a wordpress site, which works perfectly on desktop, but the overlay is being triggered when just scrolling on a touch device if you scroll with your finger on one of the images, is there anyway to stop this happening?
<script>
var WHLightbox = {
settings: {
overlay: $('.portfolio-tile--overlay'),
imageCell: $('.cell-image, .portfolio-tile--image')
},
data: {
images: []
},
init: function() {
this.events();
this.buildImageData();
},
events: function() {
var self = this;
this.settings.imageCell.on('click touchend', function(e) {
e.preventDefault();
e.stopPropagation();
// set up the overlay
self._positionOverlay();
self._openOverlay();
self._preventScrolling();
// create the image slide
self._createImageSlide($(this));
});
this.settings.overlay.on('click touchend', function(e) {
e.preventDefault();
e.stopPropagation();
self._closeOverlay();
});
$('.portfolio-tile--overlay--controls--prev, .portfolio-tile--overlay--controls--next').on('click touchend', function(e) {
e.preventDefault();
e.stopPropagation();
});
$('.portfolio-tile--overlay--controls--prev').on('click touchend', function(e) {
e.preventDefault();
e.stopPropagation();
self.showPrev();
});
$('.portfolio-tile--overlay--controls--next').on('click touchend', function(e) {
e.preventDefault();
e.stopPropagation();
self.showNext();
});
},
// public functions
showPrev: function() {
var index = this.currentImageIndex();
if(index === 0) {
index = this.data.images.length;
}
this._createImageSlide(false, index-1);
},
showNext: function() {
var index = this.currentImageIndex();
if(index === this.data.images.length-1) {
// set to -1 because it adds 1 in the _createImageSlide call
index = -1;
}
this._createImageSlide(false, index+1);
},
currentImageIndex: function() {
if(this.settings.overlay.hasClass('open')) {
var imageUrl = $('.portfolio-tile--main-image').attr('src');
for(var i=0; i<this.data.images.length; i++) {
if(this.data.images[i].imageUrl === imageUrl) {
return i;
}
}
} else {
return false;
}
},
// image data
buildImageData: function() {
var self = this,
i = 0;
this.settings.imageCell.each(function() {
self.data.images[i] = {
imageUrl: self._getImagePath($(this))
}
i++;
});
},
// slide
_createImageSlide: function($el, index) {
var imagePath;
if(!$el) {
imagePath = this.data.images[index].imageUrl;
} else {
imagePath = this._getImagePath($el);
}
this.settings.overlay.find('.portfolio-tile--main-image').attr('src', imagePath);
},
_getImagePath: function($el) {
var imagePath,
spanEl = $el.find('span.js-cell-image-background'),
imgEl = $el.find('img.cell-image__image');
if(spanEl.length) {
imagePath = spanEl.css('backgroundImage');
imagePath = imagePath.replace('url(', '').replace(')', '');
} else if(imgEl.length) {
imagePath = imgEl.attr('src');
}
return imagePath;
},
// overlay
_positionOverlay: function() {
this.settings.overlay.css({
// position the overlay to current scroll position
top: $(window).scrollTop()
});
},
_openOverlay: function() {
this.settings.overlay.addClass('open');
},
_preventScrolling: function() {
$('html, body').addClass('no-scroll');
},
_reInitScrolling: function() {
$('html, body').removeClass('no-scroll');
},
_closeOverlay: function() {
this.settings.overlay.removeClass('open');
this._reInitScrolling();
}
};
WHLightbox.init();
</script>
You could use a library such as Modernizr to detect touch take a look at the answer to this question on detecting touch What's the best way to detect a 'touch screen' device using JavaScript?
With Modernizr only:
if (Modernizr.touch) {
alert('Touch Screen');
} else {
alert('No Touch Screen');
}
or with the use of jQuery & Modernizr you can do something like:
if($('html').hasClass('touch') === false) {
//run your code here
}
Thank you for your reply aharen, I managed to get it working by just changing touchend to tap and it seems to be working as intended now.