Pan and Zoom (on mouse-wheel) based on cursor location - javascript

The purpose of my code is to be able to pan and zoom a div element. It works perfectly fine except that the zooming is done from the center (which is normal). I need the div to zoom based on the cursor location (kind of like google maps zoom works) so I used transform-origin to shift the zoom from the center to the cursor's location. It works totally fine, except that whenever I change the mouse position to zoom again, the div element 'jumps' a few pixels and after this 'jump' it works (zoom and panning) perfectly fine again.
I think the reason is that I am not calculating the new distances correctly (after zooming) since the origin of the div changes.
Anyone can help on this one please?
NB: If I zoom out then zoom in to the exact same scale, there will be no 'jump'.
You can have a look at my code here in case I wasn't clear enough:
window.onload = function () {
initializeImage();
};
function initializeImage() {
var recElements = document.getElementsByClassName("rec");
var rec = recElements[0];
rec.panning = {};
rec.imageZoom = 100;
rec.stoprecMove = function () {
this.panning.xMouseDown = null;
this.panning.yMouseDownn = null;
};
rec.recMove = function() {
var dx = 0;
var dy = 0;
var scale = this.imageZoom * 0.01;
var pos = this.getBoundingClientRect();
dx = (this.panning.xMove - this.panning.xMouseDown) / scale;
dy = (this.panning.yMove - this.panning.yMouseDown) / scale;
dx = startOffsetX + dx;
dy = startOffsetY + dy;
var xZoom = this.xZoom;
var yZoom = this.yZoom;
var xTransform = xZoom - dx;
var yTransform = yZoom - dy;
var dxInPixel = dx + "px";
var dyInPixel = dy + "px";
this.style.transform = "translate(" + dxInPixel + "," + dyInPixel + ")" + "scale(" + scale + ")";
this.style.transformOrigin = xTransform + "px" + " " + yTransform + "px";
this.lastX = dx;
this.lastY = dy;
};
rec.onmousewheel = function (e) {
this.panning.xMove = 0;
this.panning.yMove = 0;
this.panning.xMouseDown = 0;
this.panning.yMouseDown = 0;
this.xZoom = e.clientX;
this.yZoom = e.clientY;
if (typeof this.lastX === "undefined") {
startOffsetX = 0;
startOffsetY = 0;
}
else {
startOffsetX = this.lastX;
startOffsetY = this.lastY;
}
var zoom = rec.imageZoom;
var delta = event.detail ? event.detail * -120 : event.wheelDelta;
zoom += delta / 15;
if (zoom > 300) zoom = 300; else if (zoom < 30) zoom = 30;
rec.imageZoom = zoom;
this.recMove();
};
rec.onmousedown = function (e) {
var xMouseDown = e.clientX;
var yMouseDown = e.clientY;
console.log("x: " + xMouseDown + " y: " + yMouseDown);
this.panning.xMouseDown = xMouseDown;
this.panning.yMouseDown = yMouseDown;
if (typeof this.lastX === "undefined") {
startOffsetX = 0;
startOffsetY = 0;
}
else {
startOffsetX = this.lastX;
startOffsetY = this.lastY;
}
e.preventDefault();
};
rec.onmousemove = function (e) {
if (this.panning.xMouseDown && this.panning.yMouseDown) {
this.panning.xMove = e.clientX;
this.panning.yMove = e.clientY;
this.recMove();
e.preventDefault();
}
};
rec.onmouseup = function () {
this.stoprecMove();
};
rec.onmouseout = function () {
this.stoprecMove();
};
}
.rec {
width: 200px;
height: 100px;
background-image: repeating-linear-gradient(to right, yellow, gray 10%, green 15%, yellow 30%, gray 40%, green 45%, yellow 60%, gray 70%, green 75%, yellow 90%, gray 100%);
position: fixed;
top: 0;
left: 0;
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<script src="script.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
</head>
<body>
<div class = "rec"></div>
</body>
</html>

Related

Movable selection on a canvas

Here below is how to draw a "selection rectangle" on a <canvas> with drag-and-drop, see How to draw a selection rectangle with drag and drop on a HTML canvas?.
Is there a simple way to detect the selection rectangle on hover at a distance of a few pixels, and allow to move the selection rectangle with drag-and-drop?
var c1 = document.getElementById("c1"), c2 = document.getElementById("c2");
var ctx1 = c1.getContext("2d"), ctx2 = c2.getContext("2d");
ctx2.setLineDash([5, 5]);
var origin = null;
window.onload = () => { ctx1.drawImage(document.getElementById("img"), 0, 0); }
c2.onmousedown = e => { origin = {x: e.offsetX, y: e.offsetY}; };
window.onmouseup = e => { origin = null; };
c2.onmousemove = e => {
if (!!origin) {
ctx2.strokeStyle = "#ff0000";
ctx2.clearRect(0, 0, c2.width, c2.height);
ctx2.beginPath();
ctx2.rect(origin.x, origin.y, e.offsetX - origin.x, e.offsetY - origin.y);
ctx2.stroke();
}
};
#img { display: none; }
#canvas-container { position: relative; }
canvas { position: absolute; left: 0; top: 0; }
#c1 { z-index: 0; }
#c2 { z-index: 1; }
<img id="img" src="https://i.imgur.com/okSIKkW.jpg">
<div id="canvas-container">
<canvas id="c1" height="200" width="200"></canvas>
<canvas id="c2" height="200" width="200"></canvas>
</div>
Storing the current selection
To be able to move an existing selection, you have to save its state. Right now, your code "forgets" about it after drawing it once.
You can save your selection in a variable on mouseup like so:
const dx = origin.x - mouse.x;
const dy = origin.y - mouse.y;
selection = {
left: Math.min(mouse.x, origin.x),
top: Math.min(mouse.y, origin.y),
width: Math.abs(dx),
height: Math.abs(dy)
}
Intersection check
Your selection is a rectangle. You can check whether the mouse is intersecting the rectangle like so:
const intersects = (
mouse.x >= selection.left &&
mouse.x <= selection.left + selection.width &&
mouse.y >= selection.top &&
mouse.y <= selection.top + selection.height
);
If you want to add some padding around the rectangle, you can change the checks to be, for example, mouse.x >= selection.left - PADDING.
Make or Move
You now have to support 2 types of interactions:
Making new selections, and
Moving existing selections
This is where my implementation gets a bit messy, but you can probably refactor it yourself 🙂
I didn't change much in the making-selections code, other than saving the selection to a variable.
When moving selections, you take the dx and dy of your mouse drag and add them to the selection's original position (ox, oy):
const dx = origin.x - mouse.x;
const dy = origin.y - mouse.y;
selection.left = ox - dx;
selection.top = oy - dy;
Here's everything in a runnable snippet:
var c1 = document.getElementById("c1"),
c2 = document.getElementById("c2");
var ctx1 = c1.getContext("2d"),
ctx2 = c2.getContext("2d");
ctx2.setLineDash([5, 5]);
var origin = null;
let selection = null;
let selectionHovered = false;
let interaction = null;
let ox = null;
let oy = null;
window.onload = () => { ctx1.drawImage(document.getElementById("img"), 0, 0); }
c2.onmousedown = e => {
origin = {x: e.offsetX, y: e.offsetY};
if (selectionHovered) {
interaction = "MOVE_SELECTION";
ox = selection.left;
oy = selection.top;
} else {
interaction = "MAKE_SELECTION";
}
};
window.onmouseup = e => {
interaction = null;
origin = null;
};
c2.onmousemove = e => {
const x = e.offsetX;
const y = e.offsetY;
if (!interaction) {
selectionHovered = (
selection &&
x >= selection.left &&
x <= selection.left + selection.width &&
y >= selection.top &&
y <= selection.top + selection.height
);
} else {
const dx = origin.x - x;
const dy = origin.y - y;
// Update
switch (interaction) {
case "MOVE_SELECTION":
selection.left = ox - dx;
selection.top = oy - dy;
break;
case "MAKE_SELECTION":
selection = {
left: Math.min(x, origin.x),
top: Math.min(y, origin.y),
width: Math.abs(dx),
height: Math.abs(dy)
}
break
default:
// Do nothing
}
// Set selectionHovered
if (selection) {
} else {
selectionHovered = false;
}
}
// Draw
if (selection) drawSelection(selection);
};
function drawSelection({ top, left, width, height }) {
// Draw rect
ctx2.strokeStyle = "#ff0000";
ctx2.clearRect(0, 0, c2.width, c2.height);
ctx2.beginPath();
ctx2.rect(left, top, width, height);
ctx2.stroke();
// Set mouse
c2.style.cursor = selectionHovered ? "move" : "default";
}
#img { display: none; }
body { margin: 0; }
#canvas-container { position: relative; }
canvas { position: absolute; left: 0; top: 0; }
#c1 { z-index: 0; }
#c2 { z-index: 1; }
<img id="img" src="https://i.imgur.com/okSIKkW.jpg">
<div id="canvas-container">
<canvas id="c1" height="200" width="200"></canvas>
<canvas id="c2" height="200" width="200"></canvas>
</div>

Can I snap this (blue) div on the highlighted square on the canvas?

When you drag the blue div, I'd like it to 'snap' into the middle of the green highlighted square when the mouse button is released. I cannot seem to find a way to read the coordinates from the box[] (Path2D()) interface. So is there a simple way to achieve this? Should I save the coordinates of the squares individually? Or can I somehow still get the points out of the Path2D interface?
const board = document.getElementById("board");
const ctxB = board.getContext("2d");
var Grid = false;
const boxsize = 64;
const amountOfrows = 8;
const amountOfHorBoxes = 7;
const totalAmountOfBoxes = amountOfrows * amountOfHorBoxes;
board.width = boxsize * 7.5;
board.height = boxsize * 8;
var addHorBox = 0;
var addVertBox = 0;
let boxes = [];
function drawGrid(){
Grid=true;
// for the amout of rows
for (let rowcount = 0; rowcount < amountOfrows; rowcount++) {
ctxB.lineWidth = 1;
ctxB.strokeStyle = "black";
ctxB.fillStyle = "white";
// filling the rows
if(rowcount%2==0){
for (let boxcount = 0; boxcount < amountOfHorBoxes; boxcount++) {
let box = new Path2D();
box.rect(addHorBox, addVertBox, boxsize, boxsize);
boxes.push(box);
ctxB.fill(box);
ctxB.stroke(box);
addHorBox+=boxsize;
}
}
addHorBox=0;
addVertBox+=boxsize;
}
}
MoveUnit(document.getElementById("unit"));
function MoveUnit(unit){
const rect = board.getBoundingClientRect();
const checkX = unit.clientWidth/2 - rect.left;
const checkY = unit.clientHeight/2 - rect.top;
var initialX;
var initialY;
var tile;
unit.onmousedown = mouseDown;
function mouseDown(e){
e = e || window.event;
e.preventDefault();
initialX = e.clientX;
initialY = e.clientY;
document.onmouseup = mouseUp;
document.onmousemove = moveMouse;
}
function moveMouse(e){
e = e || window.event;
e.preventDefault();
unit.style.top = (unit.offsetTop + e.clientY - initialY) + "px";
unit.style.left = (unit.offsetLeft + e.clientX - initialX) + "px";
boxes.forEach(box => {
if (ctxB.isPointInPath(box, unit.offsetLeft + checkX, unit.offsetTop + checkY)) {
ctxB.lineWidth = 2;
ctxB.fillStyle = 'green';
ctxB.fill(box);
ctxB.stroke(box);
tile=box;
}else{
ctxB.lineWidth = 1;
ctxB.strokeStyle = "black";
ctxB.fillStyle = 'white';
ctxB.fill(box);
ctxB.stroke(box);
}
});
// saving new mousepos after moving the unit
initialX = e.clientX;
initialY = e.clientY;
}
function mouseUp(){
document.onmousemove = false;
}
}
function loop(timestamp){
// draw once
if(Grid==false) drawGrid();
requestAnimationFrame(loop);
}
loop();
#board{
background-color: #999;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#unit{
background-color: rgb(134, 162, 224);
position: absolute;
cursor: pointer;
z-index: 1;
width: 30px;
height: 30px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="testing.css"/>
<title>Gridding</title>
</head>
<body>
<div id="unit"></div>
<canvas id="board"></canvas></div>
<script src="testing.js"></script>
</body>
</html>
In those Path2D instances we can add some useful data that can do what you need:
let box = new Path2D();
box.rect(...
box.data = { row, column }
then on the function mouseUp we use that to 'snap' into the middle
unit.style.top = ((box.data.column + 0.5) * boxsize) + "px";
unit.style.left = ((box.data.row + 0.5) * boxsize) + "px";
I reduced a lot of your code on my example below, I'm focusing just on the question, you should do the same when asking questions, that helps others get to the point faster and gives you a better chance of a quick answer.
const board = document.getElementById("board");
const ctxB = board.getContext("2d");
const unit = document.getElementById("unit");
const boxsize = 32;
board.width = board.height = boxsize * 4;
let boxes = [];
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
let box = new Path2D();
box.rect(r * boxsize, c * boxsize, boxsize -0.5, boxsize -0.5);
box.data = { r, c }
boxes.push(box);
}
}
var position = { x: -1, y: -1 }
function mouseDown(e) {
document.onmouseup = mouseUp;
document.onmousemove = moveMouse;
position = { x: e.clientX, y: e.clientY}
}
function mouseUp() {
document.onmousemove = false;
boxes.forEach(box => {
if (ctxB.isPointInPath(box, position.x, position.y)) {
unit.style.top = ((box.data.c + 0.5) * boxsize) + "px";
unit.style.left = ((box.data.r + 0.5) * boxsize) + "px";
}
});
}
function moveMouse(e) {
unit.style.top = (unit.offsetTop + e.clientY - position.y) + "px";
unit.style.left = (unit.offsetLeft + e.clientX - position.x) + "px";
position = { x: e.clientX, y: e.clientY}
}
function loop(timestamp) {
ctxB.clearRect(0, 0, board.width, board.height)
boxes.forEach(box => {
ctxB.fillStyle = ctxB.isPointInPath(box, position.x, position.y)? 'green' : 'white'
ctxB.fill(box);
ctxB.stroke(box);
});
requestAnimationFrame(loop);
}
loop();
unit.onmousedown = mouseDown;
#unit {
background-color: blue;
position: absolute;
width: 20px;
height: 20px;
}
<canvas id="board"></canvas>
<br>
<div id="unit"></div>
In my code I'm separating the mouse events from the drawing,
we draw only on the loop.
the events change state variables.
Also noticed that in some cases two boxes got highlighted at the same time changing the size of the rect by a tiny bit fixed that boxsize -0.5

How to move multiple elements on mousemove

How can I use Javascript to select multiple elements and move them at the same time? I'm trying to make something like this without using jQuery: https://jenniferdewalt.com/caterpillar.html
// JavaScript source code
var container = document.getElementById("container");
//MAKE NEW DIV
//container.innerHTML = '<div id="circle" class="circle"></div>';
console.log(container.innerHTML);
var mouseX = 0;
var mouseY = 0;
var positionX = 0;
var positionY = 0;
var speed = 50;
var i;
var m = 0;
var newCircle;
//EVENTS / TRIGGERS
window.addEventListener("mousemove", mouseCoords);
setInterval(circlePosition, 30);
//Make Circles
makeCircles(20);
function makeCircles(num) {
for (i = 0; i < num; i++) {
container.innerHTML += '<div id="circle' + i + '" class="circle"></div> ';
//Circle with NEW ID
var newCircle = document.getElementById("circle" + i);
//This Circle Random Color;
var ranColor = genColor();
newCircle.style.backgroundColor = ranColor;
//This Circle Random Size;
var newSize = genSize();
newCircle.style.width = newSize + "px";
newCircle.style.height = newSize + "px";
}
}
console.log(container.innerHTML);
//FUNCTIONS.
//MOUSE COORDS
function mouseCoords(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
//CIRCLEPOSITION
function circlePosition() {
//Circle go's to Last Mouse position
var circleX = document.getElementById("circle0");
var circleY = document.getElementById("circle0");
positionX += (mouseX - positionX) / speed;
positionY += (mouseY - positionY) / speed;
circleX.style.left = positionX + "px";
circleY.style.top = positionY + "px";
}
// MOST IMPORTANT PART RELATED TO QUESTION
//Random Color
function genColor() {
var RGBarr = [];
for (c = 0; c < 3; c++) {
var newNum = Math.floor(Math.random() * 256);
RGBarr.push(newNum);
}
var newRGB = 'rgb(' + RGBarr[0] + ',' + RGBarr[1] + ',' + RGBarr[2] + ')';
return newRGB;
}
//Random Size
function genSize() {
var ranSize = Math.floor(Math.random() * 100) + 1;
return ranSize;
}
body {
background-color: black;
}
.circle {
position: absolute;
top: 0;
left: 0;
background-color: white;
width: 100px;
height: 100px;
border-radius: 50%;
}
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<link href="./HOWTOMOVEAFUCKINGCIRCLE.css" type="text/css" rel="stylesheet" />
<meta charset="utf-8" />
<title></title>
</head>
<body>
<div id="container"></div>
<!--Javscript-->
<script src="./HOWTOMOVEAFUCKINGCIRCLE.js"></script>
</body>
</html>
I'm Self taught so would really apreciate your help, if u can't be asked to help me with this project, please consider giving me some other example how i can move multiple elements at the same time. Greetings from the Netherlands.

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

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/

HTML5 detect a click on a moving circle element

Right now I have a circle that goes from the bottom to the top of a canvas.
I attached a click event to the canvas and it console logs the coordinates of the click.
The ball also console logs the coordinates of where it is.
How can I add a circle element to 'elements' and how do I compare the canvas click event to the circle diameters coordinates.
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var ballRadius = 10;
var x = canvas.width/6;
var y = canvas.height-30;
var dx = 0;
var dy = -2;
canvas.addEventListener('click', function(event) {
var x = event.pageX - elemLeft;
var y = event.pageY - elemTop;
console.log("CLICKED: (x,y) ", x, y);
elements.forEach(function(element) {
if (y > canvas.y && y < canvas.y + canvas.ballRadius && x > canvas.x && x < canvas.x + canvas.ballRadius) {
alert('clicked an element');
}
});
}, false);
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
console.log("Ball moving: (x,y) ", x, ", ", y)
if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if(y + dy > canvas.height-ballRadius || y + dy < ballRadius) {
dy = -dy;
}
x += dx;
y += dy;
}
setInterval(draw, 100);
https://jsfiddle.net/aL9amevj/
You can use css to render a circle, .animate() to animate element, check if animation animation.currentTime is less than half of animation.effect.computedTiming.duration / 2 .finished to reset element to original state.
.circle {
position: relative;
border-radius: 50%;
width: 50px;
height: 50px;
background: blue;
left: calc(100vw - 100px);
top: calc(100vh - 100px);
}
<button>click</button>
<div class="circle" style="animation-fill-mode:forwards"></div>
<script>
var score = 0;
var button = document.querySelector("button");
var circle = document.querySelector(".circle");
button.addEventListener("click", function() {
this.setAttribute("disabled", "disabled");
var curr = circle.animate([{
left: "0px",
top: "0px"
}], {
duration: 10000,
iterations: 1
});
circle.onclick = (e) => {
console.log(e.pageX, e.pageY);
var t = curr.currentTime <
curr.effect.computedTiming.duration / 2;
if (t) {
score += 1;
}
curr.cancel();
circle.style.display = "none";
circle.onclick = null;
alert((t ? "added 1 " : "did not add 1 ")
+ "to score: " + score);
}
curr.finished.then(() => {
circle.onclick = null;
setTimeout(function() {
button.removeAttribute("disabled");
}, 10)
}).catch(e => {
console.log(e);
button.removeAttribute("disabled");
circle.style.display = "block";
})
});
</script>

Categories