How do you zoom into a specific point (no canvas)? - javascript

The goal is simple, using a mousewheel, zoom into a specific point (where the mouse is). This means after zooming the mouse will be in the same roughly the same spot of the picture.
(Purely illustrative, I don't care if you use dolphins, ducks or madonna for the image)
I do not wish to use canvas, and so far I've tried something like this:
HTML
<img src="whatever">
JS
function zoom(e){
var deltaScale = deltaScale || -e.deltaY / 1000;
var newScale = scale + deltaScale;
var newWidth = img.naturalWidth * newScale;
var newHeight = img.naturalHeight * newScale;
var x = e.pageX;
var y = e.pageY;
var newX = x * newWidth / img.width;
var newY = y * newHeight / img.height;
var deltaX = newX - x;
var deltaY = newY - y;
setScale(newScale);
setPosDelta(-deltaX,-deltaY);
}
function setPosDelta(dX, dY) {
var imgPos = getPosition();
setPosition(imgPos.x + dX, imgPos.y + dY);
}
function getPosition() {
var x = parseFloat(img.style.left);
var y = parseFloat(img.style.top);
return {
x: x,
y: y
}
}
function setScale(n) {
scale = n;
img.width = img.naturalWidth * n;
img.height = img.naturalHeight * n;
}
What this attempts to do is calculate the x,y coordinates of the dolphin's eye before and after the zoom, and after calculating the distance between those two points, substracts it from the left,top position in order to correct the zoom displacement, with no particular success.
The zoom occurs naturally extending the image to the right and to the bottom, so the correction tries to pull back to the left and to the top in order to keep the mouse on that damn dolphin eye! But it definitely doesn't.
Tell me, what's wrong with the code/math? I feel this question is not too broad, considering I couldn't find any solutions besides the canvas one.
Thanks!
[EDIT] IMPORTANT
CSS transform order matters, if you follow the selected answer, make sure you order the transition first, and then the scale. CSS transforms are executed backwards (right to left) so the scaling would be processed first, and then the translation.

Here is an implementation of zooming to a point. The code uses the CSS 2D transform and includes panning the image on a click and drag. This is easy because of no change in scale.
The trick when zooming is to normalize the offset amount using the current scale (in other words: divide it by the current scale) first, then apply the new scale to that normalized offset. This keeps the cursor exactly where it is independent of scale.
var scale = 1,
panning = false,
xoff = 0,
yoff = 0,
start = {x: 0, y: 0},
doc = document.getElementById("document");
function setTransform() {
doc.style.transform = "translate(" + xoff + "px, " + yoff + "px) scale(" + scale + ")";
}
doc.onmousedown = function(e) {
e.preventDefault();
start = {x: e.clientX - xoff, y: e.clientY - yoff};
panning = true;
}
doc.onmouseup = function(e) {
panning = false;
}
doc.onmousemove = function(e) {
e.preventDefault();
if (!panning) {
return;
}
xoff = (e.clientX - start.x);
yoff = (e.clientY - start.y);
setTransform();
}
doc.onwheel = function(e) {
e.preventDefault();
// take the scale into account with the offset
var xs = (e.clientX - xoff) / scale,
ys = (e.clientY - yoff) / scale,
delta = (e.wheelDelta ? e.wheelDelta : -e.deltaY);
// get scroll direction & set zoom level
(delta > 0) ? (scale *= 1.2) : (scale /= 1.2);
// reverse the offset amount with the new scale
xoff = e.clientX - xs * scale;
yoff = e.clientY - ys * scale;
setTransform();
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#document {
width: 100%;
height: 100%;
transform-origin: 0px 0px;
transform: scale(1) translate(0px, 0px);
}
<div id="document">
<img style="width: 100%"
src="https://i.imgur.com/fHyEMsl.jpg"
crossOrigin="" />
</div>

This is an implementation that is closer to your original idea using top and left offsets and modifying the width attribute of the image instead of using the css transform in my other answer.
var scale = 1.0,
img = document.getElementById("image"),
deltaX = 0,
deltaY = 0;
// set the initial scale once the image is loaded
img.onload = function() {
scale = image.offsetWidth / image.naturalWidth;
}
img.onwheel = function(e) {
e.preventDefault();
// first, remove the scale so we have the native offset
var xoff = (e.clientX - deltaX) / scale,
yoff = (e.clientY - deltaY) / scale,
delta = (e.wheelDelta ? e.wheelDelta : -e.deltaY);
// get scroll direction & set zoom level
(delta > 0) ? (scale *= 1.05) : (scale /= 1.05);
// limit the smallest size so the image does not disappear
if (img.naturalWidth * scale < 16) {
scale = 16 / img.naturalWidth;
}
// apply the new scale to the native offset
deltaX = e.clientX - xoff * scale;
deltaY = e.clientY - yoff * scale;
// now modify the attributes of the image to reflect the changes
img.style.top = deltaY + "px";
img.style.left = deltaX + "px";
img.style.width = (img.naturalWidth * scale) + "px";
}
window.onresize = function(e) {
document.getElementById("wrapper").style.width = window.innerWidth + "px";
document.getElementById("wrapper").style.height = window.innerHeight + "px";
}
window.onload = function(e) {
document.getElementById("wrapper").style.width = window.innerWidth + "px";
document.getElementById("wrapper").style.height = window.innerHeight + "px";
}
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
div {
overflow: hidden;
}
<div id="wrapper" style="position:relative;">
<img id="image" style="width:100%;position:absolute;top:0px;left:0px;"
src="https://i.imgur.com/fHyEMsl.jpg"
crossOrigin="" />
</div>

I liked the both posts from fmacdee. I factored the code he created out to be a reusable version that can be called on any image.
just call:
var imageScaler = new ImageScaler(document.getElementById("image"));
imageScaler.setup();
and include this code somewhere in your project:
var ImageScaler = function(img)
{
this.img = img;
this.scale = this.getImageScale();
this.panning = false;
this.start = {x: 0, y: 0};
this.delta = {x: 0, y: 0};
};
ImageScaler.prototype =
{
constructor: ImageScaler,
setup: function()
{
this.setupEvents();
},
setupEvents: function()
{
var img = this.img;
var callBack = this.onScale.bind(this);
var touchDown = this.touchDown.bind(this),
touhcMove = this.touchMove.bind(this),
touchUp = this.touchUp.bind(this);
img.onwheel = callBack;
img.onmousedown = touchDown;
img.onmousemove = touhcMove;
img.onmouseup = touchUp;
},
getImageScale: function()
{
var img = this.img;
return img.offsetWidth / img.naturalWidth;
},
getMouseDirection: function(e)
{
return (e.wheelDelta ? e.wheelDelta : -e.deltaY);
},
getOffset: function(e)
{
var scale = this.scale,
delta = this.delta;
// first, remove the scale so we have the native offset
return {
x: (e.clientX - delta.x) / scale,
y: (e.clientY - delta.y) / scale
};
},
scaleElement: function(x, y, scale)
{
var img = this.img;
img.style.top = y + "px";
img.style.left = x + "px";
img.style.width = (img.naturalWidth * scale) + "px";
},
minScale: 0.2,
updateScale: function(delta)
{
// get scroll direction & set zoom level
var scale = (delta > 0) ? (this.scale *= 1.05) : (this.scale /= 1.05);
// limit the smallest size so the image does not disappear
if (scale <= this.minScale)
{
this.scale = this.minScale;
}
return this.scale;
},
touchDown: function(e)
{
var delta = this.delta;
this.start = {x: e.clientX - delta.x, y: e.clientY - delta.y};
this.panning = true;
},
touchMove: function(e)
{
e.preventDefault();
if (this.panning === false)
{
return;
}
var delta = this.delta,
start = this.start;
delta.x = (e.clientX - start.x);
delta.y = (e.clientY - start.y);
console.log(delta, start)
this.scaleElement(delta.x, delta.y, this.scale);
},
touchUp: function(e)
{
this.panning = false;
},
onScale: function(e)
{
var offset = this.getOffset(e);
e.preventDefault();
// get scroll direction & set zoom level
var delta = this.getMouseDirection(e);
var scale = this.updateScale(delta);
// apply the new scale to the native offset
delta = this.delta;
delta.x = e.clientX - offset.x * scale;
delta.y = e.clientY - offset.y * scale;
this.scaleElement(delta.x, delta.y, scale);
}
};
I made a fiddle to view the results: http://jsfiddle.net/acqo5n8s/12/

Related

How to limit this move function on zoomed image without exceed the image, I make limits at 0x,0y and I need the same at x,y max limits

How can I limit this move function on zoomed image without exceed the image, I make limits at 0x,0y and I need the same at x,y max limits
var scale = 1,
panning = false,
pointX = 0,
pointY = 0,
start = { x: 0, y: 0 },
zoom = document.getElementById("zoom");
function setTransform() {
zoom.style.transform = "translate(" + pointX + "px, " + pointY + "px) scale(" + scale + ")";
}
function centerMap() {
if(pointY>=0){
pointY = 0;
}
if(pointX>=0){
pointX = 0;
}
var offX= document.getElementById("img").offsetWidth
var offY = document.getElementById("img").offsetHeight
}
zoom.onmousedown = function (e) {
e.preventDefault();
start = { x: e.clientX - pointX, y: e.clientY - pointY };
panning = true;
}
zoom.onmouseup = function (e) {
panning = false;
}
zoom.onmousemove = function (e) {
console.log(e);
e.preventDefault();
if (!panning) {
return;
}
pointX = (e.clientX - start.x);
pointY = (e.clientY - start.y);
centerMap();
console.log(pointX,pointY, e.clientY,scale,);
setTransform();
}
zoom.onwheel = function (e) {
e.preventDefault();
var xs = (e.clientX - pointX) / scale,
ys = (e.clientY - pointY) / scale,
delta = (e.wheelDelta ? e.wheelDelta : -e.deltaY);
(delta > 0) ? (scale += 0.2) : (scale -= 0.2);
if(scale<1){
scale=1;
}
pointX = e.clientX - xs * scale;
pointY = e.clientY - ys * scale;
centerMap();
setTransform();
}
This is the JS code.
In function centerMap I move the image auto to not seeing the white background, but in right and bottom I didn't find the solve, I tried to make calculations with image coords, but I need a resolvation in any resolutions
Ex: https://shorturl.at/jlpw2

How to zoom on a point with JavaScript?

My web project needs to zoom a div element around the mouse position as anchor while mouse wheeling, I was inspired by #Tatarize 's answer at Zoom in on a point (using scale and translate), but I can't implement it exactly, it can't zoom and translate around the mouse position, can any one help?
window.onload = function() {
const STEP = 0.05;
const MAX_SCALE = 10;
const MIN_SCALE = 0.01;
const red = document.getElementById('red');
const yellow = red.parentNode;
let scale = 1;
yellow.onmousewheel = function (event) {
event.preventDefault();
let mouseX = event.clientX - yellow.offsetLeft - red.offsetLeft;
let mouseY = event.clientY - yellow.offsetTop - red.offsetTop;
const factor = event.wheelDelta / 120;
const oldScale = scale;
scale = scale + STEP * factor;
scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
const scaleChanged = scale - oldScale;
const offsetX = -(mouseX * scaleChanged);
const offsetY = -(mouseY * scaleChanged);
console.log(offsetX, offsetY);
red.style.transform = 'translate(' + offsetX + 'px, ' + offsetY + 'px)' + 'scale(' + scale + ')';
}
}
.yellow {
background-color: yellow;
width: 400px;
height: 200px;
}
.red {
background-color: red;
width: 100px;
height: 50px;
}
<div class="yellow">
<div id="red" class="red"></div>
</div>
Really incredible, I actually did it.
window.onload = () => {
const STEP = 0.99;
const MAX_SCALE = 5;
const MIN_SCALE = 0.01;
const red = document.getElementById("red");
const yellow = red.parentNode;
let scale = 1;
const rect = red.getBoundingClientRect();
const originCenterX = rect.x + rect.width / 2;
const originCenterY = rect.y + rect.height / 2;
yellow.onwheel = (event) => {
event.preventDefault();
const factor = event.deltaY;
// If current scale is equal to or greater than MAX_SCALE, but you're still zoom in it, then return;
// If current scale is equal to or smaller than MIN_SCALE, but you're still zoom out it, then return;
// Can not use Math.max and Math.min here, think about it.
if ((scale >= MAX_SCALE && factor < 0) || (scale <= MIN_SCALE && factor > 0)) return;
const scaleChanged = Math.pow(STEP, factor);
scale *= scaleChanged;
const rect = red.getBoundingClientRect();
const currentCenterX = rect.x + rect.width / 2;
const currentCenterY = rect.y + rect.height / 2;
const mousePosToCurrentCenterDistanceX = event.clientX - currentCenterX;
const mousePosToCurrentCenterDistanceY = event.clientY - currentCenterY;
const newCenterX = currentCenterX + mousePosToCurrentCenterDistanceX * (1 - scaleChanged);
const newCenterY = currentCenterY + mousePosToCurrentCenterDistanceY * (1 - scaleChanged);
// All we are doing above is: getting the target center, then calculate the offset from origin center.
const offsetX = newCenterX - originCenterX;
const offsetY = newCenterY - originCenterY;
// !!! Both translate and scale are relative to the original position and scale, not to the current.
red.style.transform = 'translate(' + offsetX + 'px, ' + offsetY + 'px)' + 'scale(' + scale + ')';
}
}
.yellow {
background-color: yellow;
width: 200px;
height: 100px;
margin-left: 50px;
margin-top: 50px;
position: absolute;
}
.red {
background-color: red;
width: 200px;
height: 100px;
position: absolute;
}
<div class="yellow">
<div id="red" class="red"></div>
</div>
.onmousewheel is deprecated. Use .onwheel instead.
Also, onwheel event doesn't have wheelDelta property. Use deltaY.
My code given there is to change the viewbox with regard to a zoom point. You are moving the rectangle based on some math that doesn't fit that situation.
The idea is to pan the zoom box with regard to the change in the scale. You are changing the position and location of a rectangle. Which is to say you need to simulate the new position of the red rectangle as if the yellow rectangle were a viewport. Which means that when we zoom in, we are zooming in at a translateX translateY position of a particular scale factor. We then need to translate the value of the zoom point into the right scene space. Then adjust the position of the red rectangle as if it were in that scene space.
Here's the code with some corrections, though I'm clearly missing a few elements. The big thing is the lack of preservation of the translateX translateY stuff. You overwrite it so it ends up just preserving the zoom and screwing up the translateX, translateY stuff back to zero when it's a relative offset of the viewport.
In functional code, zooming in in the rectangle will make the red rectangle fill the entire scene space.
window.onload = function() {
const STEP = 0.05;
const MAX_SCALE = 10;
const MIN_SCALE = 0.01;
const red = document.getElementById('red');
const yellow = document.getElementById('yellow');
const svgArea = document.getElementById('svgArea');
let viewportTranslateX = 0;
let viewportTranslateY = 0;
let viewportScale = 1;
svgArea.onwheel = function (event) {
event.preventDefault();
console.log("mouse coords", event.clientX, event.clientY);
let zoompointX = (event.clientX + (viewportTranslateX / viewportScale)) * viewportScale;
let zoompointY = (event.clientY + (viewportTranslateY / viewportScale)) * viewportScale;
console.log("zoom point prezoom", zoompointX, zoompointY);
const factor = event.deltaY / 120;
const oldScale = viewportScale;
viewportScale = viewportScale * (1 + STEP * factor);
viewportScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, viewportScale));
const scaleChanged = viewportScale - oldScale;
const offsetX = -(zoompointX * scaleChanged);
const offsetY = -(zoompointY * scaleChanged);
console.log("scale", scaleChanged, offsetX, offsetY);
viewportTranslateX += offsetX;
viewportTranslateY += offsetY;
zoompointX = (event.clientX + (viewportTranslateX / viewportScale)) * viewportScale;
zoompointY = (event.clientY + (viewportTranslateY / viewportScale)) * viewportScale;
console.log("zoompoint postzoom", zoompointX, zoompointY);
var x = viewportTranslateX;
var y = viewportTranslateY;
var width = (svgArea.getAttribute("width") * viewportScale);
var height = (svgArea.getAttribute("height") * viewportScale);
svgArea.setAttribute("viewBox", x + " " + y + " " + width + " " + height);
console.log("viewport", x, y, width, height, viewportScale);
}
}
<svg id="svgArea" width=400 height=200 viewBox="0,0,400,200">
<rect id="yellow" width=400 height=200 fill="yellow"/>
<rect id="red" width=100 height=50 fill="red"/>
</svg>

Zoom and pan problems

So i'm attempting to create a script that Zooms in a image, centered at mouse x, y, and with panning.
And it almost works, almost...
When you zoom and pan in the image, it kinda jumps a little, so my math is off, as always.
If u could point me in the right direction it would be awesome, my mind is going numb from trying to subtract different offsets, and it's kinda like a big blur atm !
JSBin Example
/**
MouseDown: Pan image
ScrollWheel: Zoom In image
*/
var $doc = $(".document");
var scale = 1;
var panning = false;
var start = {x:0, y:0}
var offset = {left:0, top: 0}
$(window).bind("mousedown", (e) => {
e.preventDefault();
start = {x: e.clientX, y: e.clientY};
updateOffset();
panning = true;
})
.bind("mouseup", (e) => {
updateOffset();
panning = false;
})
.bind("mousemove", (e)=> {
e.preventDefault();
if(!panning) return;
var x = (e.clientX - start.x) + offset.left;
var y = (e.clientY - start.y) + offset.top;
$doc.css({
"transform": "translate("+ (x) +"px, "+ (y) +"px) scale(" +scale +")"
});
})
.bind("mousewheel", (e)=>{
e.preventDefault();
// get scroll direction & set zoom level
(e.originalEvent.wheelDelta /120 > 0) ? (scale *= 1.2) : (scale /= 1.2)
var x = e.clientX - offset.left;
var y = e.clientY - offset.top;
var originX = x
var originY = y
var translateX = offset.left;
var translateY = offset.top;
$doc.css({
"transform-origin": originX+ "px " + originY + "px",
"transform": "translate("+ translateX +"px, "+ translateY +"px) scale("+scale+")"
})
updateOffset();
});
// Helpers --------------------------------------------------------
// graps the transform styles from the element
function getMatrix($el) {
if(!$el.css("transform")) {
return false;
}
var arr = $el.css("transform").match(/\((.*)\)/)[1].split(",");
return {
scale: parseInt(arr[0]),
tx: parseInt(arr[4]),
ty: parseInt(arr[5])
}
}
function updateOffset () {
var m = getMatrix($doc)
offset = {
top: m.ty,
left: m.tx
};
}
You need to compensate for the change in scale when calculating the offset:
.bind("mousewheel", (e)=>{
//
// Zoom
//
e.preventDefault();
// take the scale into account with the offset
var xs = (e.clientX - offset.left) / scale;
var ys = (e.clientY - offset.top) / scale;
// get scroll direction & set zoom level
(e.originalEvent.wheelDelta /120 > 0) ? (scale *= 1.2) : (scale /= 1.2)
// reverse the offset amount with the new scale
var x = e.clientX - xs * scale;
var y = e.clientY - ys * scale;
$doc.css({
"transform": "translate("+ x +"px, "+ y +"px) scale("+scale+")"
})
updateOffset();
});
Oh, and you have to use parseFloat instead of parseInt in your getMatrix() call or it just loses accuracy over time!
// graps the transform styles from the element
function getMatrix($el) {
if(!$el.css("transform")) {
return false;
}
var arr = $el.css("transform").match(/\((.*)\)/)[1].split(",");
return {
scale: parseFloat(arr[0]),
tx: parseFloat(arr[4]),
ty: parseFloat(arr[5])
}
}

Drag Bound with Konva.Layer

I am using KonvaJs in my project. I need to implement drag bound to Konva.Layer. My layer has so many other shapes and images. I need to restrict the movement of layer up to 50% of it's width and height. The way I have done in this plunkr. The problem arises when user zoom-in or zoom-out the layer using mouse wheel. After the zoom, I don't know why the drag bound is behaving differently. Seems like I am not able to do the Math correctly. I need to have the same behavior i.e. the way movement of layer is restricted when user does not perform zoom. This is what I am doing:
//... a helper object for zooming
var zoomHelper = {
stage: null,
scale: 1,
zoomFactor: 1.1,
origin: {
x: 0,
y: 0
},
zoom: function(event) {
event.preventDefault();
var delta;
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
if (event.originalEvent.detail > 0) {
//scroll down
delta = 0.2;
} else {
//scroll up
delta = 0;
}
} else {
if (event.originalEvent.wheelDelta < 0) {
//scroll down
delta = 0.2;
} else {
//scroll up
delta = 0;
}
}
var evt = event.originalEvent,
mx = evt.clientX - zoomHelper.stage.getX(),
my = evt.clientY - zoomHelper.stage.getY(),
zoom = (zoomHelper.zoomFactor - delta),
newscale = zoomHelper.scale * zoom;
zoomHelper.origin.x = mx / zoomHelper.scale + zoomHelper.origin
.x - mx / newscale;
zoomHelper.origin.y = my / zoomHelper.scale + zoomHelper.origin
.y - my / newscale;
zoomHelper.stage.setOffset({
x: zoomHelper.origin.x,
y: zoomHelper.origin.y
});
zoomHelper.stage.setScale({
x: newscale,
y: newscale
});
zoomHelper.stage.draw();
zoomHelper.scale *= zoom;
preCalculation();
}
};
// Code goes here
var w = window.innerWidth;
var h = window.innerHeight;
var height, minX, minY, maxX, maxY;
var stage = new Konva.Stage({
container: 'container',
width: w,
height: h
});
zoomHelper.stage =stage;
var layer = new Konva.Layer({
draggable: true,
dragBoundFunc: function(pos) {
console.log('called');
var X = pos.x;
var Y = pos.y;
if (X < minX) {
X = minX;
}
if (X > maxX) {
X = maxX;
}
if (Y < minY) {
Y = minY;
}
if (Y > maxY) {
Y = maxY;
}
return ({
x: X,
y: Y
});
}
});
stage.add(layer);
function preCalculation(){
// pre-calc some bounds so dragBoundFunc has less calc's to do
height = layer.getHeight();
minX = stage.getX() - layer.getWidth() / 2;
maxX = stage.getX() + stage.getWidth() - layer.getWidth() / 2;
minY = stage.getY() - layer.getHeight() / 2;
maxY = stage.getY() + stage.getHeight() - layer.getHeight() / 2;
console.log(height, minX, minY, maxX, maxY);
}
preCalculation();
var img = new Image();
img.onload = function() {
var floorImage = new Konva.Image({
image: img,
width: w,
height: h
});
layer.add(floorImage);
layer.draw();
};
img.src = 'https://s.yimg.com/pw/images/coverphoto02_h.jpg.v3';
$(stage.container).on('mousewheel DOMMouseScroll', zoomHelper.zoom);
While using dragBoundFunc you have to return absolute position of layer. As you are changing attributes of top node (stage) it can be hard to maintain absolute position. So you can try to set bound function inside 'dragmove' event:
layer.on('dragmove', function() {
var x = Math.max(minX, Math.min(maxX, layer.x()));
var y = Math.max(minY, Math.min(maxY, layer.y()));
layer.x(x);
layer.y(y);
});
http://plnkr.co/edit/31MUmOjXBUVuaHVJsL3c?p=preview

Ease in/out camera movement for canvas

I have a function which moves a camera to a position that the user clicks on the canvas. But currently the movement is linear. I would like it to use an ease in/out transition but am having difficulty understanding how to implement it with my current function.
This is the code I have:
animate.mouseEvent = function(e,el){
var mousePos = mouse.relativePosition(e,el);
var parent = el;
if(e.button == 0){
function update(){
if(centerX == mousePos.x && centerY == mousePos.y){
clearInterval(parent.timer);
//center X / Y is canvas width & height halved
}
var difx = centerX - mousePos.x,
dify = centerY - mousePos.y,
distance = Math.sqrt(difx*difx + dify*dify),
normalX = difx/distance,
normalY = dify/distance,
speed = 1, //currently linear [replace with ease in/out]
x = normalX * speed,
y = normalY * speed;
updateOffsets(x,y);
mousePos.x += x;
mousePos.y += y;
}
parent.timer = setInterval(update,1);
}
}
animate.updateOffsets = function(x,y){
canvas.offsetX -= x;
canvas.offsetY -= y;
}
So i was wondering how i would implement an ease in/out from my current linear method. Using the below function:
Math.easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};
I don't fully know how to implement it or what exactly it would be returning for me to calculate the new offset with. Was hoping some one could explain.
This should give you an idea. I used another easing function, you can substitute your own. Example
var element = document.getElementById('moving');
var bg = document.getElementById('background');
bg.addEventListener('click', function(evt) {
animate({
x: evt.offsetX,
y: evt.offsetY
});
}, false);
function getPosition() {
return {
x: element.offsetLeft,
y: element.offsetTop
};
}
function setPosition(x, y) {
element.style.left = x + 'px';
element.style.top = y + 'px';
}
function easing(x) {
return 0.5 + 0.5 * Math.sin((x - 0.5) * Math.PI);
}
function animate(target) {
var initial = getPosition();
var initialX = initial.x;
var initialY = initial.y;
var targetX = target.x;
var targetY = target.y;
var deltaX = targetX - initialX;
var deltaY = targetY - initialY;
var timeStart = timestamp();
var timeLength = 800;
var timer = setInterval(update, 10);
function timestamp() {
return Date.now();
}
function stop() {
clearInterval(timer);
}
function update() {
var t = (timestamp() - timeStart) / timeLength;
if (t > 1) {
fraction(1);
stop();
} else {
fraction(easing(t));
}
}
function fraction(t) {
setPosition(initialX + t * deltaX, initialY + t * deltaY);
}
}
EDIT
Applied to the fiddle you provided:
var element = document.getElementById('background');
var ctx = element.getContext("2d");
var camera = {};
// camera.offset marks the position seen at the upper left corner
// (It would be better if it marked the center)
// Let these be negative, so (0,0) scene position is at the center of the image
camera.offsetX = -element.width/2;
camera.offsetY = -element.height/2;
var obj = {};
obj.x = 50;
obj.y = 50;
element.addEventListener('click', animate, false);
function easing(x) {
return 0.5 + 0.5 * Math.sin((x - 0.5) * Math.PI);
}
function animate(evt){
// Transform click position from screen coordinates to scene coordinates
var targetX = evt.offsetX + camera.offsetX - element.width / 2,
targetY = evt.offsetY + camera.offsetY - element.height / 2;
var initialX = camera.offsetX,
initialY = camera.offsetY;
var deltaX = targetX - initialX,
deltaY = targetY - initialY;
var timeStart = Date.now();
var timeLength = 800;
var timer = setInterval(update, 10);
function stop(){
clearInterval(timer);
}
function update(){
var t = (Date.now() - timeStart) / timeLength;
if (t > 1) {
fraction(1);
stop();
} else {
fraction(easing(t));
}
}
function fraction(t){
camera.offsetX = initialX + t * deltaX,
camera.offsetY = initialY + t * deltaY;
}
}
function draw(){
ctx.clearRect(0,0,element.width,element.height);
ctx.fillStyle = 'red';
ctx.fillRect((element.width/2)-2,(element.height/2)-2,4,4);
ctx.fillStyle = 'blue';
// ===> Here the size and position arguments were swapped
ctx.fillRect(obj.x-camera.offsetX,obj.y-camera.offsetY, 20, 20);
}
// Consider using requestAnimationFrame
setInterval(draw, 10);

Categories