How to Recreate the Android Facebook app's view swiping UX? - javascript

I'm looking to make something exactly like Facebook's Android app's UX for swiping between News Feed, Friend Requests, Messages, and Notifications. You should be able to "peek" at the next view by panning to the right of left, and it should snap to the next page when released if some threshold has been passed or when swiped.
Every scroll snap solution I've seen only snaps after the scrolling stops, whereas I only ever want to scroll one page at a time.
EDIT: Here's what I have so far. It seems to work fine when emulating an Android device in Google Chrome, but doesn't work when I run it on my Galaxy S4 running 4.4.2. Looking into it a bit more, it looks like touchcancel is being fired right after the first touchmove event which seems like a bug. Is there any way to get around this?
var width = parseInt($(document.body).width());
var panThreshold = 0.15;
var currentViewPage = 0;
$('.listContent').on('touchstart', function(e) {
console.log("touchstart");
currentViewPage = Math.round(this.scrollLeft / width);
});
$('.listContent').on('touchend', function(e) {
console.log("touchend");
var delta = currentViewPage * width - this.scrollLeft;
if (Math.abs(delta) > width * panThreshold) {
if (delta < 0) {
currentViewPage++;
} else {
currentViewPage--;
}
}
$(this).animate({
scrollLeft: currentViewPage * width
}, 100);
});

In case anyone wants to do this in the future, the only way I found to actually do this was to manually control all touch events and then re-implement the normally-native vertical scrolling.
It might not be the prettiest, but here's a fiddle to what I ended up doing (edited to use mouse events instead of touch events): http://jsfiddle.net/xtwzcjhL/
$(function () {
var width = parseInt($(document.body).width());
var panThreshold = 0.15;
var currentViewPage = 0;
var start; // Screen position of touchstart event
var isHorizontalScroll = false; // Locks the scrolling as horizontal
var target; // Target of the first touch event
var isFirst; // Is the first touchmove event
var beginScrollTop; // Beginning scrollTop of ul
var atanFactor = 0.6; // atan(0.6) = ~31 degrees (or less) from horizontal to be considered a horizontal scroll
var isMove = false;
$('body').on('mousedown', '.listContent', function (e) {
isMove = true;
isFirst = true;
isHorizontalScroll = false;
target = $(this);
currentViewPage = Math.round(target.scrollLeft() / width);
beginScrollTop = target.closest('ul').scrollTop();
start = {
x: e.originalEvent.screenX,
y: e.originalEvent.screenY
}
}).on('mousemove', '.listContent', function (e) {
if (!isMove) {
return false;
}
e.preventDefault();
var delta = {
x: start.x - e.originalEvent.screenX,
y: start.y - e.originalEvent.screenY
}
// If already horizontally scrolling or the first touchmove is within the atanFactor, horizontally scroll, otherwise it's a vertical scroll of the ul
if (isHorizontalScroll || (isFirst && Math.abs(delta.x * atanFactor) > Math.abs(delta.y))) {
isHorizontalScroll = true;
target.scrollLeft(currentViewPage * width + delta.x);
} else {
target.closest('ul').scrollTop(beginScrollTop + delta.y);
}
isFirst = false;
}).on('mouseup mouseout', '.listContent', function (e) {
isMove = false;
isFirst = false;
if (isHorizontalScroll) {
var delta = currentViewPage * width - target.scrollLeft();
if (Math.abs(delta) > width * panThreshold) {
if (delta < 0) {
currentViewPage++;
} else {
currentViewPage--;
}
}
$(this).animate({
scrollLeft: currentViewPage * width
}, 100);
}
});
});

Related

Why updating brightness filter has not effect in Safari?

I'm updating DOM element's brightness/contrast from JavaScript:
let style = `brightness(${brightness}%) contrast(${contrast}%)`;
element.style.filter = style;
This happens when the user holds left mouse button and moves cursor. However, the brightness/contrast does NOT change in Safari (on Mac) unless the scroll bars are visible. However, the brightness does change in other browsers (Chrome, Firefox, Edge). Is it a bug in Safari? How do I make it work in Safari?
Demo: https://l36vng.csb.app/
Code: https://codesandbox.io/s/charming-vaughan-l36vng?file=/main.js:2207-2306
PS. This also does not work in Safari on iOS (if I include touch event handlers to the code above). My Mac Safari version is 16.1.
This seems to be fixed in the latest Technology Preview (Release 160 (Safari 16.4, WebKit 18615.1.14.3)).
For the time being, you can workaround the issue by adding a super fast transition on the filter property, this will make current stable happy and update the filter as expected.
// Change brightness/contrast of the image by holding
// left mouse button on the image and moving cursor.
// Moving horizontally changes brightness,
// moving vertically changes contrast.
class Brightness {
constructor(element) {
this.element = element;
// x and y mouse coordinates of the cursor
// when the brightness change was started
this.startPosition = null;
this.brightnessPercent = 100;
// Current brightness/contrast percentage.
this.currentAmount = {
brightness: 100,
contrast: 100
};
// True if the user holds left mouse bottom, which
// indicates that brightness is being changed.
// False by default or when the left mouse button is released.
this.active = false;
this.addEventListeners();
}
addEventListeners() {
let element = this.element;
// Start changing brightness
// -----------------
element.addEventListener("mousedown", (e) => {
if (e.button !== 0) return; // Not a left mouse button
this.active = true;
e.preventDefault();
this.startChange(e);
});
// End changing brightness
// -----------------
document.addEventListener("mouseup", () => {
this.active = false;
});
// Drag the pointer over the bar
// -----------------
document.addEventListener("mousemove", (e) => {
if (!this.active) return;
this.change(e);
});
}
startChange(e) {
this.startPosition = this.positionFromCursor(e);
}
change(e) {
let position = this.positionFromCursor(e);
let xChange = position.x - this.startPosition.x;
this.changeCurrentAmount(xChange, "brightness");
let yChange = position.y - this.startPosition.y;
this.changeCurrentAmount(yChange, "contrast");
this.changeImageStyle();
}
changeCurrentAmount(change, type) {
change /= 2; // Decrease rate of change
change = Math.round(change);
let amount = 100 + change;
if (type === "contrast") amount = 100 - change;
if (amount < 0) amount = 0;
this.currentAmount[type] = amount;
}
changeImageStyle() {
let brightness = this.currentAmount.brightness;
let contrast = this.currentAmount.contrast;
let css = `brightness(${brightness}%) contrast(${contrast}%)`;
this.element.style.filter = css;
}
positionFromCursor(e) {
let pointerX = e.pageX;
let pointerY = e.pageY;
if (e.touches && e.touches.length > 0) {
pointerX = e.touches[0].pageX;
pointerY = e.touches[0].pageY;
}
return { x: pointerX, y: pointerY };
}
}
document.addEventListener("DOMContentLoaded", function () {
const imageElement = document.querySelector(".Image");
new Brightness(imageElement);
});
.Image {
/* Safari bug workaround */
transition: filter 1e-6s linear;
}
<img
class="Image"
width="512"
height="512"
src="https://64.media.tumblr.com/df185589006321d70d37d1a59040d7c3/df57f19b4b4b783a-cb/s250x400/e76347d6b1cb4aa08839036fb0407a9f6108a0ab.jpg"
/>

Drag div content by grabbing its background

When the content goes outside the div, we use scrollbars to see it. How can I scroll the div content by grabbing and dragging its background? I've searched the solution but did not find what I need. Here is my fiddle:
https://jsfiddle.net/vaxobasilidze/xhn49e1j/
Drag any item to the right div and move it outside the container to the right or bottom. scrollbars appear to help you to scroll. Here is an example of what I want to achieve. See the first diagram on the link and drag it:
https://jsplumbtoolkit.com/
Any tips on how to do this?
You should just need to detect when the mouse is down and then when the mouse is moving afterwards you can store the previous mouse coordinates and reference the current coordinates. Finally you can scroll the div in question by an amount based on the difference in drag since the last mousemove call.
var mouseDown = false;
var prevCoords = { x: 0, y: 0 };
$("#mainDiv").mousedown(function() {
mouseDown = true;
}).mousemove(function(e) {
var currentScrollX = $('#mainDiv').scrollLeft();
var currentScrollY = $('#mainDiv').scrollTop();
if(mouseDown) {
$('#mainDiv').scrollLeft(currentScrollX + prevCoords.x - (e.clientX + currentScrollX))
$('#mainDiv').scrollTop(currentScrollY + prevCoords.y - e.clientY)
};
prevCoords.x = e.clientX + currentScrollX;
prevCoords.y = e.clientY;
}).mouseup(function() {
mouseDown = false;
});
https://jsfiddle.net/6rx30muh/
EDIT: Fixed bug with wiggling tables when dragging:
var mouseDown = false;
var prevCoords = { x: 0, y: 0 };
$("#mainDiv").mousedown(function() {
mouseDown = true;
}).mousemove(function(e) {
var currentScrollX = $('#mainDiv').scrollLeft();
var currentScrollY = $('#mainDiv').scrollTop();
if(mouseDown) {
$('#mainDiv').scrollLeft(currentScrollX + prevCoords.x - e.clientX)
$('#mainDiv').scrollTop(currentScrollY + prevCoords.y - e.clientY)
};
prevCoords.x = e.clientX;
prevCoords.y = e.clientY;
}).mouseup(function() {
mouseDown = false;
});
Check for mousemove between mousedown and mouseup on the body element is a good place to start.
element = $('body');
element.addEventListener("mousedown", function(){
flag = 0;
}, false);
element.addEventListener("mousemove", function(){
flag = 1;
}, false);
element.addEventListener("mouseup", function(){
if(flag === 0){
console.log("click");
}
else if(flag === 1){
console.log("drag");
}
}, false);

JQuery-UI horizontal dragging with vertical scrolling

I have a page full of draggable divs only in horizontal (on axis X).
When I'm in a touch device I can't scroll down the page, due conflicts between scroll and drag.
Here's a jsfiddle example (test in touch devices and try to scroll).
My draggable code:
$(".cell").draggable({
axis: "x",
drag: function (event, ui) {
var size = $(".cell").width();
if (ui.offset.left > size - em(4)) {
ui.position.left = size - em(4);
}
},
start: function (event, ui) {
if (initialPosition == null) {
initialPosition = ui.position.left;
}
$(".cell").not(ui.helper).each(function () {
var pos = $(this).position().left;
if (pos > 0) {
$(this).animate({
left: initialPosition
}, 200, null);
}
});
},
stop: function (event, ui) {
var size = $(".cell").width();
if (ui.position.left > initialPosition) {
if (ui.position.left - initialPosition >= size / 3) {
ui.helper.animate({
left: size - em(4)
}, 200, null);
} else {
ui.helper.animate({
left: initialPosition
}, 200, null);
}
}
}
});
I want to detect if the user is scrolling vertically before start dragging and cancel the
horizontal dragging.
Help me, please. How can I make this work?
I had a problem similar to this one and eventually found a fairly simple solution.
In my scenario I had an inbox list whose items that you could drag to the left or right to expose action buttons. The entire inbox item must be draggable -- so the use of a drag handle was not an option.
jQuery's draggable prevents vertical scrolling on touch screens if the touch was initiated inside a draggable element. So if the screen was filled with draggable inbox items, then the user would become trapped -- unable to scroll up or down.
The solution that worked for me was to measure any change in the cursor's vertical position and use window.scrollBy to manually scroll the window by the same amount:
var firstY = null;
var lastY = null;
var currentY = null;
var vertScroll = false;
var initAdjustment = 0;
// record the initial position of the cursor on start of the touch
jqDraggableItem.on("touchstart", function(event) {
lastY = currentY = firstY = event.originalEvent.touches[0].pageY;
});
// fires whenever the cursor moves
jqDraggableItem.on("touchmove", function(event) {
currentY = event.originalEvent.touches[0].pageY;
var adjustment = lastY-currentY;
// Mimic native vertical scrolling where scrolling only starts after the
// cursor has moved up or down from its original position by ~30 pixels.
if (vertScroll == false && Math.abs(currentY-firstY) > 30) {
vertScroll = true;
initAdjustment = currentY-firstY;
}
// only apply the adjustment if the user has met the threshold for vertical scrolling
if (vertScroll == true) {
window.scrollBy(0,adjustment + initAdjustment);
lastY = currentY + adjustment;
}
});
// when the user lifts their finger, they will again need to meet the
// threshold before vertical scrolling starts.
jqDraggableItem.on("touchend", function(event) {
vertScroll = false;
});
This will closely mimic native scrolling on a touch device.
I use jquery.ui.touch-punch.js and this worked for me, line 38:
event.preventDefault();
Reference

jQuery Volume Slider

seems like I cannot find a solution for this. Please Help me out.
What I want to do is to have a simple volume slider.
SO, as you can see the orange part is my volume slider.
This is my jQuery:
var mouseDown = false;
$("#volSlider").mousedown(function() { mouseDown = true; });
$("#volSlider").mouseup(function() { mouseDown = false; });
$("#volSlider").mouseleave(function() { mouseDown = false; });
$("#controlVolume").mousemove(function(e)
{
if (mouseDown == true)
{
var caretFromTop = $("#volCaret").position().top;
var areaHeight = $("#volSlider").height();
var volume = (caretFromTop / areaHeight) * 100;
volume = Math.round(volume);
$("#volCaret").css("bottom", volume);
$("#volText").text(volume);
if (volume <= 100 && volume >= 0)
{
//To be added.
}
}
});
EDIT: For those who want to see my HTML:
<div id="controlVolume">
<div id="volSlider">
<div id="volCaret"></div>
</div>
<div id="volText"></div>
</div>
When i try to drag the caret to the top, it just goes to "1" and not further. Anything I am missing? Thanks in advance.
What you actually want to do is track the Y vertex of the mouse, not the height of the caret (well, technically yes - the height of the caret that changes between the mouse moves). You're currently tracking the position of the volume bar, which doesn't change.
As such your code should be something like this:
var mousePos = 0;
var mouseDown = false;
var height = 0;
$("#volSlider").mousedown(function(e) { mouseDown = true; mousePos = e.pageY; height = $("#volCaret").height(); });
$("#volSlider").mouseup(function() { mouseDown = false; mousePos = 0 });
$("#volSlider").mouseleave(function() { mouseDown = false; mousePos = 0 });
$("#controlVolume").mousemove(function(e)
{
if (mouseDown == true)
{
var areaHeight = $("#volSlider").height();
var caretHeight = height + (e.pageY - mousePos);
$("#volCaret").height(caretHeight );
var volume = caretHeight / areaHeight * 100;
console.log(volume);
}
});
It would be great if you'd put your code on jsfiddle, as probably there's something I've not thought of and this code fails horribly.

Simplest way to detect a pinch

This is a WEB APP not a native app. Please no Objective-C NS commands.
So I need to detect 'pinch' events on iOS. Problem is every plugin or method I see for doing gestures or multi-touch events, is (usually) with jQuery and is a whole additional pluggin for every gesture under the sun. My application is huge, and I am very sensitive to deadwood in my code. All I need is to detect a pinch, and using something like jGesture is just way to bloated for my simple needs.
Additionally, I have a limited understanding of how to detect a pinch manually. I can get the position of both fingers, can't seem to get the mix right to detect this. Does anyone have a simple snippet that JUST detects pinch?
Think about what a pinch event is: two fingers on an element, moving toward or away from each other.
Gesture events are, to my knowledge, a fairly new standard, so probably the safest way to go about this is to use touch events like so:
(ontouchstart event)
if (e.touches.length === 2) {
scaling = true;
pinchStart(e);
}
(ontouchmove event)
if (scaling) {
pinchMove(e);
}
(ontouchend event)
if (scaling) {
pinchEnd(e);
scaling = false;
}
To get the distance between the two fingers, use the hypot function:
var dist = Math.hypot(
e.touches[0].pageX - e.touches[1].pageX,
e.touches[0].pageY - e.touches[1].pageY);
You want to use the gesturestart, gesturechange, and gestureend events. These get triggered any time 2 or more fingers touch the screen.
Depending on what you need to do with the pinch gesture, your approach will need to be adjusted. The scale multiplier can be examined to determine how dramatic the user's pinch gesture was. See Apple's TouchEvent documentation for details about how the scale property will behave.
node.addEventListener('gestureend', function(e) {
if (e.scale < 1.0) {
// User moved fingers closer together
} else if (e.scale > 1.0) {
// User moved fingers further apart
}
}, false);
You could also intercept the gesturechange event to detect a pinch as it happens if you need it to make your app feel more responsive.
Hammer.js all the way! It handles "transforms" (pinches).
http://eightmedia.github.com/hammer.js/
But if you wish to implement it youself, i think that Jeffrey's answer is pretty solid.
Unfortunately, detecting pinch gestures across browsers is a not as simple as one would hope, but HammerJS makes it a lot easier!
Check out the Pinch Zoom and Pan with HammerJS demo. This example has been tested on Android, iOS and Windows Phone.
You can find the source code at Pinch Zoom and Pan with HammerJS.
For your convenience, here is the source code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport"
content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
<title>Pinch Zoom</title>
</head>
<body>
<div>
<div style="height:150px;background-color:#eeeeee">
Ignore this area. Space is needed to test on the iPhone simulator as pinch simulation on the
iPhone simulator requires the target to be near the middle of the screen and we only respect
touch events in the image area. This space is not needed in production.
</div>
<style>
.pinch-zoom-container {
overflow: hidden;
height: 300px;
}
.pinch-zoom-image {
width: 100%;
}
</style>
<script src="https://hammerjs.github.io/dist/hammer.js"></script>
<script>
var MIN_SCALE = 1; // 1=scaling when first loaded
var MAX_SCALE = 64;
// HammerJS fires "pinch" and "pan" events that are cumulative in nature and not
// deltas. Therefore, we need to store the "last" values of scale, x and y so that we can
// adjust the UI accordingly. It isn't until the "pinchend" and "panend" events are received
// that we can set the "last" values.
// Our "raw" coordinates are not scaled. This allows us to only have to modify our stored
// coordinates when the UI is updated. It also simplifies our calculations as these
// coordinates are without respect to the current scale.
var imgWidth = null;
var imgHeight = null;
var viewportWidth = null;
var viewportHeight = null;
var scale = null;
var lastScale = null;
var container = null;
var img = null;
var x = 0;
var lastX = 0;
var y = 0;
var lastY = 0;
var pinchCenter = null;
// We need to disable the following event handlers so that the browser doesn't try to
// automatically handle our image drag gestures.
var disableImgEventHandlers = function () {
var events = ['onclick', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover',
'onmouseup', 'ondblclick', 'onfocus', 'onblur'];
events.forEach(function (event) {
img[event] = function () {
return false;
};
});
};
// Traverse the DOM to calculate the absolute position of an element
var absolutePosition = function (el) {
var x = 0,
y = 0;
while (el !== null) {
x += el.offsetLeft;
y += el.offsetTop;
el = el.offsetParent;
}
return { x: x, y: y };
};
var restrictScale = function (scale) {
if (scale < MIN_SCALE) {
scale = MIN_SCALE;
} else if (scale > MAX_SCALE) {
scale = MAX_SCALE;
}
return scale;
};
var restrictRawPos = function (pos, viewportDim, imgDim) {
if (pos < viewportDim/scale - imgDim) { // too far left/up?
pos = viewportDim/scale - imgDim;
} else if (pos > 0) { // too far right/down?
pos = 0;
}
return pos;
};
var updateLastPos = function (deltaX, deltaY) {
lastX = x;
lastY = y;
};
var translate = function (deltaX, deltaY) {
// We restrict to the min of the viewport width/height or current width/height as the
// current width/height may be smaller than the viewport width/height
var newX = restrictRawPos(lastX + deltaX/scale,
Math.min(viewportWidth, curWidth), imgWidth);
x = newX;
img.style.marginLeft = Math.ceil(newX*scale) + 'px';
var newY = restrictRawPos(lastY + deltaY/scale,
Math.min(viewportHeight, curHeight), imgHeight);
y = newY;
img.style.marginTop = Math.ceil(newY*scale) + 'px';
};
var zoom = function (scaleBy) {
scale = restrictScale(lastScale*scaleBy);
curWidth = imgWidth*scale;
curHeight = imgHeight*scale;
img.style.width = Math.ceil(curWidth) + 'px';
img.style.height = Math.ceil(curHeight) + 'px';
// Adjust margins to make sure that we aren't out of bounds
translate(0, 0);
};
var rawCenter = function (e) {
var pos = absolutePosition(container);
// We need to account for the scroll position
var scrollLeft = window.pageXOffset ? window.pageXOffset : document.body.scrollLeft;
var scrollTop = window.pageYOffset ? window.pageYOffset : document.body.scrollTop;
var zoomX = -x + (e.center.x - pos.x + scrollLeft)/scale;
var zoomY = -y + (e.center.y - pos.y + scrollTop)/scale;
return { x: zoomX, y: zoomY };
};
var updateLastScale = function () {
lastScale = scale;
};
var zoomAround = function (scaleBy, rawZoomX, rawZoomY, doNotUpdateLast) {
// Zoom
zoom(scaleBy);
// New raw center of viewport
var rawCenterX = -x + Math.min(viewportWidth, curWidth)/2/scale;
var rawCenterY = -y + Math.min(viewportHeight, curHeight)/2/scale;
// Delta
var deltaX = (rawCenterX - rawZoomX)*scale;
var deltaY = (rawCenterY - rawZoomY)*scale;
// Translate back to zoom center
translate(deltaX, deltaY);
if (!doNotUpdateLast) {
updateLastScale();
updateLastPos();
}
};
var zoomCenter = function (scaleBy) {
// Center of viewport
var zoomX = -x + Math.min(viewportWidth, curWidth)/2/scale;
var zoomY = -y + Math.min(viewportHeight, curHeight)/2/scale;
zoomAround(scaleBy, zoomX, zoomY);
};
var zoomIn = function () {
zoomCenter(2);
};
var zoomOut = function () {
zoomCenter(1/2);
};
var onLoad = function () {
img = document.getElementById('pinch-zoom-image-id');
container = img.parentElement;
disableImgEventHandlers();
imgWidth = img.width;
imgHeight = img.height;
viewportWidth = img.offsetWidth;
scale = viewportWidth/imgWidth;
lastScale = scale;
viewportHeight = img.parentElement.offsetHeight;
curWidth = imgWidth*scale;
curHeight = imgHeight*scale;
var hammer = new Hammer(container, {
domEvents: true
});
hammer.get('pinch').set({
enable: true
});
hammer.on('pan', function (e) {
translate(e.deltaX, e.deltaY);
});
hammer.on('panend', function (e) {
updateLastPos();
});
hammer.on('pinch', function (e) {
// We only calculate the pinch center on the first pinch event as we want the center to
// stay consistent during the entire pinch
if (pinchCenter === null) {
pinchCenter = rawCenter(e);
var offsetX = pinchCenter.x*scale - (-x*scale + Math.min(viewportWidth, curWidth)/2);
var offsetY = pinchCenter.y*scale - (-y*scale + Math.min(viewportHeight, curHeight)/2);
pinchCenterOffset = { x: offsetX, y: offsetY };
}
// When the user pinch zooms, she/he expects the pinch center to remain in the same
// relative location of the screen. To achieve this, the raw zoom center is calculated by
// first storing the pinch center and the scaled offset to the current center of the
// image. The new scale is then used to calculate the zoom center. This has the effect of
// actually translating the zoom center on each pinch zoom event.
var newScale = restrictScale(scale*e.scale);
var zoomX = pinchCenter.x*newScale - pinchCenterOffset.x;
var zoomY = pinchCenter.y*newScale - pinchCenterOffset.y;
var zoomCenter = { x: zoomX/newScale, y: zoomY/newScale };
zoomAround(e.scale, zoomCenter.x, zoomCenter.y, true);
});
hammer.on('pinchend', function (e) {
updateLastScale();
updateLastPos();
pinchCenter = null;
});
hammer.on('doubletap', function (e) {
var c = rawCenter(e);
zoomAround(2, c.x, c.y);
});
};
</script>
<button onclick="zoomIn()">Zoom In</button>
<button onclick="zoomOut()">Zoom Out</button>
<div class="pinch-zoom-container">
<img id="pinch-zoom-image-id" class="pinch-zoom-image" onload="onLoad()"
src="https://hammerjs.github.io/assets/img/pano-1.jpg">
</div>
</div>
</body>
</html>
detect two fingers pinch zoom on any element, easy and w/o hassle with 3rd party libs like Hammer.js (beware, hammer has issues with scrolling!)
function onScale(el, callback) {
let hypo = undefined;
el.addEventListener('touchmove', function(event) {
if (event.targetTouches.length === 2) {
let hypo1 = Math.hypot((event.targetTouches[0].pageX - event.targetTouches[1].pageX),
(event.targetTouches[0].pageY - event.targetTouches[1].pageY));
if (hypo === undefined) {
hypo = hypo1;
}
callback(hypo1/hypo);
}
}, false);
el.addEventListener('touchend', function(event) {
hypo = undefined;
}, false);
}
The simplest way is to respond to the 'wheel' event.
You need to call ev.preventDefault() to prevent the browser from doing a full screen zoom.
Browsers synthesize the 'wheel' event for pinches on a trackpad, and as a bonus you also handle mouse wheel events. This is the way mapping applications handle it.
More details in my example:
let element = document.getElementById('el');
let scale = 1.0;
element.addEventListener('wheel', (ev) => {
// This is crucial. Without it, the browser will do a full page zoom
ev.preventDefault();
// This is an empirically determined heuristic.
// Unfortunately I don't know of any way to do this better.
// Typical deltaY values from a trackpad pinch are under 1.0
// Typical deltaY values from a mouse wheel are more than 100.
let isPinch = Math.abs(ev.deltaY) < 50;
if (isPinch) {
// This is a pinch on a trackpad
let factor = 1 - 0.01 * ev.deltaY;
scale *= factor;
element.innerText = `Pinch: scale is ${scale}`;
} else {
// This is a mouse wheel
let strength = 1.4;
let factor = ev.deltaY < 0 ? strength : 1.0 / strength;
scale *= factor;
element.innerText = `Mouse: scale is ${scale}`;
}
});
<div id='el' style='width:400px; height:300px; background:#ffa'>
Scale: 1.0
</div>
None of these answers achieved what I was looking for, so I wound up writing something myself. I wanted to pinch-zoom an image on my website using my MacBookPro trackpad. The following code (which requires jQuery) seems to work in Chrome and Edge, at least. Maybe this will be of use to someone else.
function setupImageEnlargement(el)
{
// "el" represents the image element, such as the results of document.getElementByd('image-id')
var img = $(el);
$(window, 'html', 'body').bind('scroll touchmove mousewheel', function(e)
{
//TODO: need to limit this to when the mouse is over the image in question
//TODO: behavior not the same in Safari and FF, but seems to work in Edge and Chrome
if (typeof e.originalEvent != 'undefined' && e.originalEvent != null
&& e.originalEvent.wheelDelta != 'undefined' && e.originalEvent.wheelDelta != null)
{
e.preventDefault();
e.stopPropagation();
console.log(e);
if (e.originalEvent.wheelDelta > 0)
{
// zooming
var newW = 1.1 * parseFloat(img.width());
var newH = 1.1 * parseFloat(img.height());
if (newW < el.naturalWidth && newH < el.naturalHeight)
{
// Go ahead and zoom the image
//console.log('zooming the image');
img.css(
{
"width": newW + 'px',
"height": newH + 'px',
"max-width": newW + 'px',
"max-height": newH + 'px'
});
}
else
{
// Make image as big as it gets
//console.log('making it as big as it gets');
img.css(
{
"width": el.naturalWidth + 'px',
"height": el.naturalHeight + 'px',
"max-width": el.naturalWidth + 'px',
"max-height": el.naturalHeight + 'px'
});
}
}
else if (e.originalEvent.wheelDelta < 0)
{
// shrinking
var newW = 0.9 * parseFloat(img.width());
var newH = 0.9 * parseFloat(img.height());
//TODO: I had added these data-attributes to the image onload.
// They represent the original width and height of the image on the screen.
// If your image is normally 100% width, you may need to change these values on resize.
var origW = parseFloat(img.attr('data-startwidth'));
var origH = parseFloat(img.attr('data-startheight'));
if (newW > origW && newH > origH)
{
// Go ahead and shrink the image
//console.log('shrinking the image');
img.css(
{
"width": newW + 'px',
"height": newH + 'px',
"max-width": newW + 'px',
"max-height": newH + 'px'
});
}
else
{
// Make image as small as it gets
//console.log('making it as small as it gets');
// This restores the image to its original size. You may want
//to do this differently, like by removing the css instead of defining it.
img.css(
{
"width": origW + 'px',
"height": origH + 'px',
"max-width": origW + 'px',
"max-height": origH + 'px'
});
}
}
}
});
}
My answer is inspired by Jeffrey's answer. Where that answer gives a more abstract solution, I try to provide more concrete steps on how to potentially implement it. This is simply a guide, one that can be implemented more elegantly. For a more detailed example check out this tutorial by MDN web docs.
HTML:
<div id="zoom_here">....</div>
JS
<script>
var dist1=0;
function start(ev) {
if (ev.targetTouches.length == 2) {//check if two fingers touched screen
dist1 = Math.hypot( //get rough estimate of distance between two fingers
ev.touches[0].pageX - ev.touches[1].pageX,
ev.touches[0].pageY - ev.touches[1].pageY);
}
}
function move(ev) {
if (ev.targetTouches.length == 2 && ev.changedTouches.length == 2) {
// Check if the two target touches are the same ones that started
var dist2 = Math.hypot(//get rough estimate of new distance between fingers
ev.touches[0].pageX - ev.touches[1].pageX,
ev.touches[0].pageY - ev.touches[1].pageY);
//alert(dist);
if(dist1>dist2) {//if fingers are closer now than when they first touched screen, they are pinching
alert('zoom out');
}
if(dist1<dist2) {//if fingers are further apart than when they first touched the screen, they are making the zoomin gesture
alert('zoom in');
}
}
}
document.getElementById ('zoom_here').addEventListener ('touchstart', start, false);
document.getElementById('zoom_here').addEventListener('touchmove', move, false);
</script>
Its same as commented by Jeffrey Sweeney, Full example to how to implement in your class.
this.touch.isPinch = false;
this.touc.pinchStart = 0;
this.touch.onTouchStart = (e) => {
if (e.touches.length === 2) {
this.touch.pinchStart = Math.hypot(e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY);
this.touch.isScaling = true;
}
}
this.touch.onTouchMove = (e) => {
if (this.touch.isScaling) {
const distance = Math.hypot(e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY);
if (this.touch.pinchStart >= 200 && distance <= 90) this.touchPichOut(); //call function for pinchOut
if (this.touch.pinchStart <= 100 && distance >= 280) this.touchPichIn(); //call function for pinchIn
}
}
this.touch.onTouchCancel = (e) => {
this.touch.isScaling = false;
}
this.touch.onTouchEnd = (e) => {
if (this.touch.isScaling) this.touch.isScaling = false;
}
Regards

Categories

Resources