Hammer.js how to pan an element and scale it progressively? - javascript

I want to pan an element only vertically and it gets resized and minimized (transforming scale and origin) until reaching the bottom of the screen when I release the touch. This behaviour that I try to reproduce is very simillar with the behaviour of the video stream on youtube(when you drag the video down it sends the container in the bottom right of the screen).
function dragStart(e) {
var transforms = [];
e.preventDefault();
currentScale = adjustScale - (e.distance/1000);
console.log(currentScale);
currentDeltaX = adjustDeltaX + (e.deltaX / currentScale);
currentDeltaY = adjustDeltaY + (e.deltaY / currentScale);
transforms.push('scale(' + currentScale + ')');
elem.style.transform = transforms.join(' ');
elem.style.transformOrigin = "100% 100%";
}
This is a sample of my code: https://codepen.io/ACondur/pen/qVYeVJ
The questions are:
How to make the pan accept only vertical pan?
How to make the element scale properly?
How to stop the panning of the element when it reaches the width that I want it to be?

Related

div follow mouse when scroll in javascript

I build a website with some sliders (Wordpress - Divi). I added some javascript to let the slider description follow the mousepointer when the mouse hovers over the slider. Works perfectly, BUT: when I scroll the mouse the pointer shifts downwards as expected, but the slider description does not shift accordingly; it remains where it was. So from that point on the link between the mouse and the description is gone.
Question: how can I enforce the description to follow the mouse when scrolling......
If you want to see the result until now, please check https://roel.famnabuurs.nl
Any help appreciated
The code as requested:
const sliderzelf = document.getElementById(slidernaam);
const titleblockslist = document.getElementById(slidernaam).getElementsByClassName('et_pb_slide_description');
for (let indslide of titleblockslist) {
indslide.style.setProperty('opacity',1, 'important');
var sliderOffset = sliderzelf.getBoundingClientRect();
let xpos = e.clientX - sliderOffset.left ;
// do not shift too far to the right
if (xpos > sliderzelf.clientWidth - indslide.clientWidth) {
xpos = sliderzelf.clientWidth - indslide.clientWidth;
}
if (xpos < 15) {
// do not shift too far to the left
xpos = 15;
}
indslide.style.left = (xpos) + 'px';
indslide.style.top = e.clientY + 'px';
}

Moving dynamically created grouped object within contained area? (html5canvas - fabric.js)

What I'm trying to create is a small canvas widget that would allow a user to dynamically create a shape onto an image and then place it above an area that caught their interest, effectively it is a highlighter.
The problem is with adding a zoom function, as when I zoom onto the image I would like to ensure that;
There is no possible way for the dynamically created shape to be
dragged anywhere outside the image area. (completed - ish, relies on 2nd step)
You cannot drag the image out of the page view, the canvas area cannot show white space. Part of the image must always be shown, and fill the entire canvas area. (problem)
Here are two examples that I've drawn up, neither of which work correctly;
First example - getBoundingRect does not update and is bound to the image
Second example - getBoundingRect does update and is bound to the grouped object
From the link description you can see that I think I've narrowed the problem down, or at least noticed a key difference between the scripts with how the getBoundingRect behaves.
The first plunk seems to work fine, until you try to zoom in multiple times and at a greater zoom level, then it seems to start bugging out (may take a few clicks and a bit of messing around, it is very inconsistent). The second plunk is very jittery and doesn't work very well.
I've been stuck on this for a week or so now, and I'm at breaking point! So really hoping someone can point out what I'm doing wrong?
Code snippet below for first plunk;
// creates group
var objs = canvas.getObjects();
var group = new fabric.Group(objs, {
status: 'moving'
});
// sets grouped object position
var originalX = active.left,
originalY = active.top,
mouseX = evt.e.pageX,
mouseY = evt.e.pageY;
active.on('moving', function(evt) {
group.left += evt.e.pageX - mouseX;
group.top += evt.e.pageY - mouseY;
active.left = originalX;
active.top = originalY;
originalX = active.left;
originalY = active.top;
mouseX = evt.e.pageX;
mouseY = evt.e.pageY;
// sets boundary area for image when zoomed
// THIS IS THE PART THAT DOESN'T WORK
active.setCoords();
// SET BOUNDING RECT TO 'active'
var boundingRect = active.getBoundingRect();
var zoom = canvas.getZoom();
var viewportMatrix = canvas.viewportTransform;
// scales bounding rect when zoomed
boundingRect.top = (boundingRect.top - viewportMatrix[5]) / zoom;
boundingRect.left = (boundingRect.left - viewportMatrix[4]) / zoom;
boundingRect.width /= zoom;
boundingRect.height /= zoom;
var canvasHeight = canvas.height / zoom,
canvasWidth = canvas.width / zoom,
rTop = boundingRect.top + boundingRect.height,
rLeft = boundingRect.left + boundingRect.width;
// checks top left
if (rTop < canvasHeight || rLeft < canvasWidth) {
group.top = Math.max(group.top, canvasHeight - boundingRect.height);
group.left = Math.max(group.left, canvasWidth - boundingRect.width);
}
// checks bottom right
if (rTop > 0 || rLeft > 0) {
group.top = Math.min(group.top, canvas.height - boundingRect.height + active.top - boundingRect.top);
group.left = Math.min(group.left, canvas.width - boundingRect.width + active.left - boundingRect.left);
}
});
// deactivates all objects on mouseup
active.on('mouseup', function() {
active.off('moving');
canvas.deactivateAll().renderAll();
})
// sets group
canvas.setActiveGroup(group.setCoords()).renderAll();
}
EDIT:
I've added comments and tried to simplify the code in the plunks.
The relevant code starts within the if (active.id == "img") { code block.
I've put irrelevant code as functions at the bottom, they can largely be ignored. ( createNewRect() + preventRectFromLeaving() )
I've removed one of the plunks to avoid confusion.
Let me know if it helps, or If I should try to simplify further.
Thanks!
I think that the grouping was messing with the position of the background image. So, I tried removing the group when the image is moving and manually updating the position of the rect instead.
It sets the last position of the image before moving
var lastLeft = active.left,
lastTop = active.top;
And then it updates those and the position of the rect every time the image moves
rect.left += active.left - lastLeft;
rect.top += active.top - lastTop;
// I think this is needed so the rectangle can be re-selected
rect.setCoords();
lastLeft = active.left;
lastTop = active.top;
Since the image has to stay within the canvas, the rect stays inside the canvas, too, whenever the image moves. The rest of the code you wrote seemed to work fine.
http://plnkr.co/edit/6GGcUxGC7CjcyQzExMoK?p=preview

CSS Magnifying Glass (Increase zoom level)

First: I am familiarize very little with Javascript but mostly I do CSS and HTML.
I am creating a website and I have a very big image so I downloaded a code to implement the magnifying glass effect on a picture. However the image has a lot of details that requires to zoom more to appreciate. Is there any way I can modify this code to create a deeper level of zoom of the image?
This is the code I used:
https://codepen.io/akhbar/pen/Biupr
I believe this is the most relevant part of the code that creates the magnifying glass effect:
$(document).ready(function(){
var native_width = 0;
var native_height = 0;
$(".large").css("background","url('" + $(".small").attr("src") + "') no-repeat");
//Now the mousemove function
$(".magnify").mousemove(function(e){
//When the user hovers on the image, the script will first calculate
//the native dimensions if they don't exist. Only after the native dimensions
//are available, the script will show the zoomed version.
if(!native_width && !native_height)
{
//This will create a new image object with the same image as that in .small
//We cannot directly get the dimensions from .small because of the
//width specified to 200px in the html. To get the actual dimensions we have
//created this image object.
var image_object = new Image();
image_object.src = $(".small").attr("src");
//This code is wrapped in the .load function which is important.
//width and height of the object would return 0 if accessed before
//the image gets loaded.
native_width = image_object.width;
native_height = image_object.height;
}
else
{
//x/y coordinates of the mouse
//This is the position of .magnify with respect to the document.
var magnify_offset = $(this).offset();
//We will deduct the positions of .magnify from the mouse positions with
//respect to the document to get the mouse positions with respect to the
//container(.magnify)
var mx = e.pageX - magnify_offset.left;
var my = e.pageY - magnify_offset.top;
//Finally the code to fade out the glass if the mouse is outside the container
if(mx < $(this).width() && my < $(this).height() && mx > 0 && my > 0)
{
$(".large").fadeIn(100);
}
else
{
$(".large").fadeOut(100);
}
if($(".large").is(":visible"))
{
//The background position of .large will be changed according to the position
//of the mouse over the .small image. So we will get the ratio of the pixel
//under the mouse pointer with respect to the image and use that to position the
//large image inside the magnifying glass
var rx = Math.round(mx/$(".small").width()*native_width - $(".large").width()/2)*-1;
var ry = Math.round(my/$(".small").height()*native_height - $(".large").height()/2)*-1;
var bgp = rx + "px " + ry + "px";
//Time to move the magnifying glass with the mouse
var px = mx - $(".large").width()/2;
var py = my - $(".large").height()/2;
//Now the glass moves with the mouse
//The logic is to deduct half of the glass's width and height from the
//mouse coordinates to place it with its center at the mouse coordinates
//If you hover on the image now, you should see the magnifying glass in action
$(".large").css({left: px, top: py, backgroundPosition: bgp});
}
}
})
})

How to move the canvas like drag and drop?

I need to move the whole canvas by touch like the movement of drag and drop
the position of the canvas is supposed to be from
flattenCanvas.height = canvas.height = SCREEN_HEIGHT - menu.container.offsetHeight;
canvas.style.position = 'absolute';
canvas.style.top = menu.container.offsetHeight+'px';
canvas.style.left = -(menu.container.offsetHeight)/2+'px';
context = canvas.getContext("2d");
and the touches for drawing or scrolling is from this
else if(event.touches.length == 1) ////one finger scrolling
{
canvas.style.top = (((event.touches[0].pageY)))+'px';
canvas.style.left = (((event.touches[0].pageX)))+'px';
}
from the last code i can move the canvas by one touch but it is not really dragging.
how to fix the canvas screen movement?
this is a demo of the webapp demo
Based on these docs, the event you want to handle is touchmove.
The code in your question appears to live inside a touchstart handler; just execute the same code in a touchmove handler to continuously update during the 'drag'.
Update
What you are trying to do is offset a target element (in this case, a canvas element) by the difference between a touch event's "current" (touchmove) and "start" (touchstart) positions. To do so, you should capture the positions of both target and touch in a touchstart handler. Then, in your touchmove handler, take the difference in touch positions, and add them to the target's start position. This might look like:
// Get your draggable element from the DOM
var draggable = document.getElementById('draggable');
// Set initial position.
draggable.style.position = 'relative'; // 'absolute' also works.
draggable.style.top = '0';
draggable.style.left = '0';
var targetStartX, targetStartY, touchStartX, touchStartY;
// Capture original coordinates of target and touch
function dragStart(e) {
targetStartX = parseInt(e.target.style.left);
targetStartY = parseInt(e.target.style.top);
touchStartX = e.touches[0].pageX;
touchStartY = e.touches[0].pageY;
}
function dragMove(e) {
// Calculate touch offsets
var touchOffsetX = e.touches[0].pageX - touchStartX,
touchOffsetY = e.touches[0].pageY - touchStartY;
// Add touch offsets to original target coordinates,
// then assign them to target element's styles.
e.target.style.left = targetStartX + touchOffsetX + 'px';
e.target.style.top = targetStartY + touchOffsetY + 'px';
}
draggable.addEventListener('touchstart', dragStart);
draggable.addEventListener('touchmove', dragMove);
Here's a live demo.

Zoom image to cursor breaks when mouse is moved

This is a followup question to How to zoom to mouse pointer while using my own mousewheel smoothscroll?
I am using css transforms to zoom an image to the mouse pointer. I am also using my own smooth scroll algorithm to interpolate and provide momentum to the mousewheel.
With Bali Balo's help in my previous question I have managed to get 90% of the way there.
You can now zoom the image all the way in to the mouse pointer while still having smooth scrolling as the following JSFiddle illustrates:
http://jsfiddle.net/qGGwx/7/
However, the functionality is broken when the mouse pointer is moved.
To further clarify, If I zoom in one notch on the mousewheel the image is zoomed around the correct position. This behavior continues for every notch I zoom in on the mousewheel, completely as intended. If however, after zooming part way in, I move the mouse to a different position, the functionality breaks and I have to zoom out completely in order to change the zoom position.
The intended behavior is for any changes in mouse position during the zooming process to be correctly reflected in the zoomed image.
The two main functions that control the current behavior are as follows:
self.container.on('mousewheel', function (e, delta) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
if (!self.running) {
self.running = true;
self.animateLoop();
}
self.delta = delta
self.smoothWheel(delta);
return false;
});
This function collects the current position of the mouse at the current scale of the zoomed image.
It then starts my smooth scroll algorithm which results in the next function being called for every interpolation:
zoom: function (scale) {
var self = this;
self.currentLocation.x += ((self.mouseLocation.x - self.currentLocation.x) / self.currentscale);
self.currentLocation.y += ((self.mouseLocation.y - self.currentLocation.y) / self.currentscale);
var compat = ['-moz-', '-webkit-', '-o-', '-ms-', ''];
var newCss = {};
for (var i = compat.length - 1; i; i--) {
newCss[compat[i] + 'transform'] = 'scale(' + scale + ')';
newCss[compat[i] + 'transform-origin'] = self.currentLocation.x + 'px ' + self.currentLocation.y + 'px';
}
self.image.css(newCss);
self.currentscale = scale;
},
This function takes the scale amount (1-10) and applies the css transforms, repositioning the image using transform-origin.
Although this works perfectly for a stationary mouse position chosen when the image is completely zoomed out; as stated above it breaks when the mouse cursor is moved after a partial zoom.
Huge thanks in advance to anyone who can help.
Actually, not too complicated. You just need to separate the mouse location updating logic from the zoom updating logic. Check out my fiddle:
http://jsfiddle.net/qGGwx/41/
All I have done here is add a 'mousemove' listener on the container, and put the self.mouseLocation updating logic in there. Since it is no longer required, I also took out the mouseLocation updating logic from the 'mousewheel' handler. The animation code stays the same, as does the decision of when to start/stop the animation loop.
here's the code:
self.container.on('mousewheel', function (e, delta) {
if (!self.running) {
self.running = true;
self.animateLoop();
}
self.delta = delta
self.smoothWheel(delta);
return false;
});
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
});
Before you check this fiddle out; I should mention:
First of all, within your .zoom() method; you shouldn't divide by currentscale:
self.currentLocation.x += ((self.mouseLocation.x - self.currentLocation.x) / self.currentscale);
self.currentLocation.y += ((self.mouseLocation.y - self.currentLocation.y) / self.currentscale);
because; you already use that factor when calculating the mouseLocation inside the initmousewheel() method like this:
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
So instead; (in the .zoom() method), you should:
self.currentLocation.x += (self.mouseLocation.x - self.currentLocation.x);
self.currentLocation.y += (self.mouseLocation.y - self.currentLocation.y);
But (for example) a += b - a will always produce b so the code above equals to:
self.currentLocation.x = self.mouseLocation.x;
self.currentLocation.y = self.mouseLocation.y;
in short:
self.currentLocation = self.mouseLocation;
Then, it seems you don't even need self.currentLocation. (2 variables for the same value). So why not use mouseLocation variable in the line where you set the transform-origin instead and get rid of currentLocation variable?
newCss[compat[i] + 'transform-origin'] = self.mouseLocation.x + 'px ' + self.mouseLocation.y + 'px';
Secondly, you should include a mousemove event listener within the initmousewheel() method (just like other devs here suggest) but it should update the transform continuously, not just when the user wheels. Otherwise the tip of the pointer will never catch up while you're zooming out on "any" random point.
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
self.zoom(self.currentscale);
});
So; you wouldn't need to calculate this anymore within the mousewheel event handler so, your initmousewheel() method would look like this:
initmousewheel: function () {
var self = this;
self.container.on('mousewheel', function (e, delta) {
if (!self.running) {
self.running = true;
self.animateLoop();
}
self.delta = delta;
self.smoothWheel(delta);
return false;
});
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
self.zoom(self.currentscale); // <--- update transform origin dynamically
});
}
One Issue:
This solution works as expected but with a small issue. When the user moves the mouse in regular or fast speed; the mousemove event seems to miss the final position (tested in Chrome). So the zooming will be a little off the pointer location. Otherwise, when you move the mouse slowly, it gets the exact point. It should be easy to workaround this though.
Other Notes and Suggestions:
You have a duplicate property (prevscale).
I suggest you always use JSLint or JSHint (which is available on
jsFiddle too) to validate your code.
I highly suggest you to use closures (often refered to as Immediately Invoked Function Expression (IIFE)) to avoid the global scope when possible; and hide your internal/private properties and methods.
Add a mousemover method and call it in the init method:
mousemover: function() {
var self = this;
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
self.zoom(self.currentscale);
});
},
Fiddle: http://jsfiddle.net/powtac/qGGwx/34/
Zoom point is not exactly right because of scaling of an image (0.9 in ratio). In fact mouse are pointing in particular point in container but we scale image. See this fiddle http://jsfiddle.net/qGGwx/99/ I add marker with position equal to transform-origin. As you can see if image size is equal to container size there is no issue. You need this scaling? Maybe you can add second container? In fiddle I also added condition in mousemove
if(self.running && self.currentscale>1 && self.currentscale != self.lastscale) return;
That is preventing from moving image during zooming but also create an issue. You can't change zooming point if zoom is still running.
Extending #jordancpaul's answer I have added a constant mouse_coord_weight which gets multiplied to delta of the mouse coordinates. This is aimed at making the zoom transition less responsive to the change in mouse coordinates. Check it out http://jsfiddle.net/7dWrw/
I have rewritten the onmousemove event hander as:
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
console.log(offset);
var x = (e.pageX - offset.left) / self.currentscale,
y = (e.pageY - offset.top) / self.currentscale;
if(self.running) {
self.mouseLocation.x += (x - self.mouseLocation.x) * self.mouse_coord_weight;
self.mouseLocation.y += (y - self.mouseLocation.y) * self.mouse_coord_weight;
} else {
self.mouseLocation.x = x;
self.mouseLocation.y = y;
}
});

Categories