Zoom page with mouse wheel, click and drag to pan - javascript

How would I go about making a scroll-and-pan effect with the mouse wheel for the entire page? Basically like any 2D editor with the scroll to zoom, click and drag to pan but for the entire body.
I haven't been able to find anything online about it.

If you don't know where to start, you will very soon hit another wall, because knowing where to start is the easy part. Here is a rough guide:
Add event listener to the wheel event (learn how](https://stackoverflow.com/a/51276012/104380))
Start with an initial (current) zoom value and calculate the delta from the wheel event and update your zoom value.
Use the zoom value to manipulate the scale of the page in some form. You can use CSS transform: scale() for that, on the body element.
Add event listener for the mousemove event (learn how](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event))
Act upon the detected mouse move, and calculate how much you need to pan the zoomed body element in order to reach any of the 4 edges, and not beyond.
panning may be done via CSS transform: translate(x,y)

Implementation I made based off of vsyncs answer:
window.onload = init;
let root;
let content;
let zoom = 1.0;
let translateY = 0;
let translateX = 0;
let middleMouseDown = false;
function init(){
content = document.getElementById("content");
root = document.getElementById("root");
root.addEventListener("wheel", (event)=>{
zoom += event.wheelDelta / 1000;
if(zoom > 2.0){zoom = 2.0}
else if (zoom < 0.5) {zoom = 0.5}
console.log(zoom);
transform();
})
root.addEventListener("mousedown", (event)=>{
event.preventDefault();
middleMouseDown = (event.button == 1);
})
root.addEventListener("mouseup", (event)=>{
if(event.button == 1){
middleMouseDown = false;
}
})
root.addEventListener("mousemove", (event)=>{
// console.log(event);
if(middleMouseDown){
translateY += event.movementY;
translateX += event.movementX;
transform();
}
})
}
function transform(){
content.style.transform = `scale(${zoom}, ${zoom}) translate(${translateX}px, ${translateY}px)`;
}
body{
margin: 0px;
}
#root{
width: 400px;
height: 400px;
border: 1px solid red;
overflow: scroll;
}
#content{
width: 200px;
height: 200px;
border: 1px solid black;
}
<html lang="en">
<head>
<link rel="stylesheet" href="index.css">
<script src="main.js"></script>
</head>
<body>
<div id="root">
<div id="content">
</div>
</div>
</body>
</html>

Related

Attach mouse movement to a div with Iframe inside

In my JS code, I've added a EventListener on mousemove & touchmove, which makes it so that my div follows the cursor. (as such)
CSS:
<style>
body {
padding: 0;
margin: 0;
height: 100vh;
background: linear-gradient(135deg, #8fc7f1, #7173f5);
overflow: hidden;
}
#my-div {
width: 300px;
height: 250px;
background-color: #ffffff;
position: absolute;
}
</style>
HTML:
<div id="my-div">
<h1>Hello</h1>
</div>
JAVASCRIPT:
let myDiv = document.getElementById("my-div");
//Detect touch device
function isTouchDevice() {
try {
//We try to create TouchEvent. It would fail for desktops and throw error
document.createEvent("TouchEvent");
return true;
} catch (e) {
return false;
}
}
const move = (e) => {
//Try, catch to avoid any errors for touch screens (Error thrown when user doesn't move his finger)
try {
//PageX and PageY return the position of client's cursor from top left of screen
var x = !isTouchDevice() ? e.pageX : e.touches[0].pageX;
var y = !isTouchDevice() ? e.pageY : e.touches[0].pageY;
} catch (e) {}
//set left and top of div based on mouse position
myDiv.style.left = x - 150 + "px";
myDiv.style.top = y - 120 + "px";
// myDiv.style.opacity = 0;
// console.log(myDiv.getBoundingClientRect())
};
//For mouse
document.addEventListener("mousemove", (e) => {
move(e);
console.log("Mouse")
});
//For touch
document.addEventListener("touchmove", (e) => {
move(e);
});
JSFIDDLE CODE
What I'm trying to do is to add a iframe inside my div and the div should still follow the cursor, but when I add the iframe inside the div, then the EventListener stops working and my div stops following the cursor. I'm not sure what the issue is. (as such)
<div id="my-div">
<iframe src="https://example.com/" width="300" height="250">
</div>
JSFIDDLE CODE WITH IFRAME
Any help and suggestion is appreciated!
You forgot the closing iframe tag
<div id="my-div">
<iframe src="https://example.com/" width="300" height="250"></iframe>
</div>
I think you should change the position of your div to relative and the position of your iframe to absolute.

How to disable scroll function?

I have problem with "double scrolling"; This is screen from my app:
As you can see, the space for adding tables is surrounded by a scroll bar.
I just need the scroll function to zoom in and out of my diagram, not to move it up and down. The current situation is that if I run my function that zooms in and out on the diagram, it also scrolls up or down. It makes such a double scroll which makes no sense.
Is it possible to turn off only the "scroll" function without turning off the scroll bars on the sides?
This is some code (my event wheel)(i am using library "MindFusion Diagramming"):
document.addEventListener('wheel', function (e) {
var zoom = diagram.getZoomFactor();
zoom -= e.deltaY / 35;
if(zoom > 70 && zoom < 200 )
{
diagram.setZoomFactor(zoom);
}
//e.preventDefault();
});
And this is an error when I uncomment e.preventDefault ()
My divs (content is an area with scrollbars):
<div id="content" style="position: static; width: 1600px; height: 700px;" >
<!-- The Overview component is bound to the canvas element below -->
<div style="position: absolute; right: 120px; width: 200px;
height: 200px; border: 1px solid #ffffff; background-color: #c0c0c0;">
<canvas id="overview" width="200" height="200">
</canvas>
</div>
<!-- The Diagram component is bound to the canvas element below -->
<div style="position: static; width: 100%; height: 100%; overflow: auto;">
<canvas id="diagram" width="2100" height="2100">
This page requires a browser that supports HTML 5 Canvas element.
</canvas>
</div>
</div>
Try,
document.addEventListener('wheel', function (e) {
e.preventDefault();
var zoom = diagram.getZoomFactor();
zoom -= e.deltaY / 35;
if(zoom > 70 && zoom < 200 )
{
diagram.setZoomFactor(zoom);
}
}, { passive : false});
reference: what are passive event listeners
I assume you overrode the scroll event to achieve your zoom functionality. You would need to call the preventDefault function on the event object you get in your event listener.
Edit:
Your event listener is passive because scroll event listeners are usually disruptive to user experience. To register it as non-passive:
document.addEventListener('wheel', function (e) {
var zoom = diagram.getZoomFactor();
zoom -= e.deltaY / 35;
if(zoom > 70 && zoom < 200 )
{
diagram.setZoomFactor(zoom);
}
e.preventDefault();
}, {passive: false});

Change image shown in fixed div when another div is in viewport

I have a fixed div containing an image that scrolls with the user from the top of the page. As new content divs enter the viewport I want the image to change.
I found a related piece of code that will change the image based on how far a user scrolls in pixels. This works, but only if the viewport is a specific size, else the image changes too early/late:
Example
I'm trying to modify this so that the change is instead based on when another div comes into view so that it works no matter the screen size (content div heights are set with relative units). I think this can be done if the other divs positions are saved to a variable and then used in place of the pixel values in the above code. However I can't seem to get this right, probably because I've not calculated the other div positions correctly.
$("#display1").fadeIn(1000);
$(window).scroll(function() {
var pos = $(window).scrollTop();
var first = $("#first").offset();
var second = $("#second").offset();
if (pos < first) {
hideAll("display1");
$("#display1").fadeIn(1000);
}
if (pos > first && pos < second) {
hideAll("display2");
$("#display2").fadeIn(1000);
}
etc...
});
function hideAll(exceptMe) {
$(".displayImg").each(function(i) {
if ($(this).attr("id") == exceptMe) return;
$(this).fadeOut();
});
}
You should try
getBoundingClientRect()
JS method, since It gets the position of the elements relative to the viewport. Check this answer: https://stackoverflow.com/a/7557433/4312515
Here is a quick proof of concept of changing a background image based on an element getting into view.
There are three divs. When the third div reaches the bottom of the viewport it will change the color of the background. When the third divs scroll out of the view again the background color is reset to its initial color.
Normally you should debounce the scroll event to prevent slowing down the UI. For this example I didn't debounce the event so you get a better sense of when the background is changed.
const
card3 = document.getElementById('card3'),
background = document.getElementById('background');
let
isCardVisible = false;
function checkDivPosition() {
const
cardTopPosition = card3.getBoundingClientRect().top,
viewportHeight = document.documentElement.clientHeight,
isInView = cardTopPosition - viewportHeight < 0;
if (isInView && !isCardVisible) {
background.style.backgroundColor = 'rebeccapurple';
isCardVisible = true;
} else if (!isInView && isCardVisible) {
background.style.backgroundColor = 'orange';
isCardVisible = false;
}
}
function onWindowScroll(event) {
checkDivPosition();
}
window.addEventListener('scroll', onWindowScroll);
body {
margin: 0;
}
.background {
height: 100vh;
opacity: .2;
position: fixed;
transition: background-color .3s ease-out;
width: 100vw;
}
.card {
border: 1px solid;
height: 100vh;
width: 100vw;
}
.card + .card {
margin-top: 5vh;
}
<div id="background" class="background" style="background-color:orange"></div>
<div class="card">
Card 1
</div>
<div class="card">
Card 2
</div>
<div id="card3" class="card">
Card 3.
</div>

How to prevent text selection from obstructing onMouseMove event?

I'm implementing a "resize handle" to change the width of my left navigation panel. It is a div that receives an onMouseDown() event, calculates the necessary widths and applies them to the right elements in the subsequent calls to onMouseMove(). But I'm having some problems.
1) The article element, to the right of the navigation panel and handle, does not activate the onMouseUp() if I release the mouse there. Is this because the onMouseDown() was activated in other element?
2) If I move the mouse fast to the right, I can't prevent the text in the article from being selected, even calling methods like preventDefault() and stopPropagation().
3) Even if there's no text in the article, the resizing only works if I move the mouse very slowly. If the mouse moves fast over the article element, the resize stops, unless I release the mouse button - in this case the resize goes smoothly (suggesting it was the text-selecting that was stopping the resize, even with no text at all). But if I release the mouse button, the resize should stop (see point 1).
I've seen some solutions using CSS user-select: none, but this would prevent any text from being selected inside article, which is obviously an overkill.
So, how can I make the resizing smooth, without selecting any text, when I move the mouse over any element in my document? (After pressing the button in the right div, of course.)
That's my HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>CSS Template</title>
</head>
<body>
<header>Header</header>
<main>
<nav id='nav'>
<div class='navcont' onmousemove='handMm(event)' onmouseup='handMu(event)'>
<p>nav 1</p>
<p>nav 2</p>
</div>
<div class='handle' onmousedown='handMd(event)' onmousemove='handMm(event)' onmouseup='handMu(event)'>
</div>
</nav>
<article id='article' onmousemove='handMm(event)' onmouseup='handMu(event)'>
</article>
</main>
<footer>Footer</footer>
</body>
</html>
That's my CSS:
html, body {
height:100%;
margin:0;
}
body {
display:flex;
flex-direction:column;
}
header {
text-align:center;
}
main {
flex:1;
display:flex;
min-height:0;
}
article {
background:#CCC;
width:auto;
overflow:auto;
padding:10px;
flex-grow:1;
}
nav {
width:300px;
height:auto;
overflow: hidden;
display:flex;
}
.navcont {
background:#8C8;
width:auto;
flex-grow:1;
}
.handle {
background:#333;
right:0px;
width:30px;
cursor:col-resize;
}
footer {
text-align:center;
}
That's my Javascript:
var mx,px,moving=false;
function handMd(e) {
mx = e.pageX;
px = document.getElementById('nav').clientWidth;
moving = true;
}
function handMm(e) {
if (moving) {
e.preventDefault();
e.stopPropagation();
e.cancelBubble = true;
e.returnValue = false;
var diff = e.pageX - mx;
document.getElementById('nav').style.width = (px + diff)+'px';
document.getElementById('article').style.width = (window.innerWidth-px-diff)+'px';
}
}
function handMu(e) {
moving = false;
}
And here is a working example: https://jsfiddle.net/45h7vq7u/
Another example, including Ryan Tsui's answer: https://jsfiddle.net/v1cmk2f6/1/ (the text-selection is gone, but the div still won't move smoothly, but only when moving fast to the right).
Catch the events of start moving and finish moving with your preferred method (onMouseDown and onMouseUp are fine). Add a CSS class to specify the moving state when the action starts. Remove the CSS class when the action finishes.
In your case, you may try the followings:
Add a new CSS class:
.moving {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
Extend your handMd() and handMu() functions:
function handMd(e) {
mx = e.pageX;
px = document.getElementById('nav').clientWidth;
moving = true;
document.getElementById('article').classList.toggle('moving', true); // Add this line. 'article' is the id of the element where you don't want the selection to occur.
}
function handMu(e) {
moving = false;
document.getElementById('article').classList.toggle('moving', false); // Add this line. 'article' is the id of the element where you don't want the selection to occur.
}
After analyzing how this was solved somewhere else, I came to the following changes.
HTML - only one event handler needed:
...
<main>
<nav id='nav'>
<div class='navcont'>
<p>nav 1</p>
<p>nav 2</p>
</div>
<div class='handle' onmousedown='handMd(event)'>
</div>
</nav>
<article id='article'>
</article>
</main>
...
Javascript - attach and detach the onmousemove event handler is way better than calling onmousemove every time the mouse moves (to only then test if the mouse button has been pressed). Also, the event is now attached to the document, not to each div on screen:
var mx,px,minW = 200;
function handMd(e) {
mx = e.pageX;
px = document.getElementById('nav').clientWidth;
document.addEventListener('mousemove',handMm);
document.addEventListener('mouseup',handMu);
document.getElementById('article').classList.toggle('moving',true);
document.getElementById('nav').classList.toggle('moving',true);
}
function handMm(e) {
var diff = e.pageX - mx;
if (px+diff >= minW && window.innerWidth-px-diff >= minW) {
document.getElementById('nav').style.width = (px+diff)+'px';
document.getElementById('article').style.width = (window.innerWidth-px-diff)+'px';
}
}
function handMu(e) {
document.removeEventListener('mousemove',handMm);
document.removeEventListener('mouseup',handMu);
document.getElementById('article').classList.toggle('moving',false);
document.getElementById('nav').classList.toggle('moving',false);
}
CSS - thanks to Ryan Tsui's answer, I eliminated the unwanted text selection while resizing:
.moving {
user-select:none;
-moz-user-select:none;
-webkit-user-select:none;
-ms-user-select:none;
}
I don't know how to stop the selection but I can tell you for some help that the event triggered when something is selected is onselect.

Drop Down menu -- onmouseout getting invoked on the child node when set in the parent node

Trying to mimic the browse catagories in the following link https://dev.twitter.com/discussions
onmouseover -- the container expands to fit the new items within itself -- but,moving the mouse within the container(expanded conainer) will result in the onmouseout getting invoked -- even when the mouse is within the container itself -- silly mistake or not trying hard to find out where and how i might be going wrong
Code --
<html>
<style type="text/css">
#container{
overflow: hidden;
height: auto;
width: 350px;
background-color: rgba(0,0,0,0.65);
}
.contents{
height: 30px;
width: 350px;
background-color: yellow;
float: left;
}
</style>
<script type="text/javascript" >
var foo = new Array("bar","santa","claus")
function fire(){
var contents = document.getElementById("contents")
if(contents.hasChildNodes())
return
for(i = 0 ; i < foo.length ; i++){
var tem=document.createElement("div");
tem.setAttribute("id",'cont'+i);
tem.setAttribute("class","contents");
tem.appendChild(document.createTextNode(foo[i]))
contents.appendChild(tem)
}
}
function unfire(evt){
if ((evt.target || evt.srcElement).id != "container")
return;
var contents = document.getElementById("contents");
while(contents.hasChildNodes())
contents.removeChild(contents.firstChild)
}
</script>
<div id="container" onmouseover="fire(event)" onmouseout="unfire(event)">
Move your mouse here
<div id="contents" ></div>
</div>
</html>
Sorry, I got my original answer completely wrong, I'm not sure what I was thinking. Of course, mouseout fires on a parent when the mouse moves to a child. In this case, you need to check the relatedTarget or toElement properties of the event object and check to see if that element is a descendant of the container.
You can check ancestry using contains() in Internet Explorer and compareDocumentPosition() in other browsers. For example, change onmouseout="unfire(event)" to onmouseout="unfire.call(this, event)" and add the following code to the unfire function:
var to = evt.relatedTarget || evt.toElement;
if((this.contains && this.contains(to)) || this.compareDocumentPosition(to) & 16)
return;

Categories