How to make SVG map zoomable and scrollable? - javascript

I am working on a website with a custom SVG map on the home page.
I already created a map with interactive points (usual link tags), which works pretty well. But the problem here is that map is big and needs to be larger then a screen size, especially on small devices.
I want map to be zoomable (without zooming all the page) and scrollable by dragging mouse (on desktop), also it needs to be centered by default.
I tried to use JqueryUI draggable method to achieve scrolling, and it works, but have some kind of conflict with the method I use to center it. And also absolutely stucks when I try to limit the draggable area.
Here is the latest example I have: https://codepen.io/khomutovspace/pen/jOVzbMe
Code:
$(document).ready(function () {
var outerContent = $(".map");
var innerContent = $(".map > svg");
outerContent.scrollTop((innerContent.height() - outerContent.height()) / 2);
});
$(function () {
$(".map").draggable();
});
body {
margin: 0;
}
.content {
position: fixed;
top: 0;
left: 0;
z-index: 1;
padding: 15px;
}
.navigation {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
.map {
min-width: 100vw;
min-height: 100vh;
overflow: hidden;
}
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="content">
<div class="navigation">
<div>
<a>Title</a>
</div>
<div>
<a>Item 1</a>
<a>Item 2</a>
<a>Item 3</a>
</div>
</div>
</div>
<div class="map">
<svg>…
</div>

If you can use another lib than jQuery UI, you can try PanZoom.
const mapElement = $(".map .svg")[0];
const panzoom = Panzoom(mapElement, { contain: 'outside', startScale: 1.5 });
mapElement.parentElement.addEventListener('wheel', panzoom.zoomWithWheel);
mapElement.parentElement.addEventListener('wheel', function (event) {
if (!event.shiftKey) return;
panzoom.zoomWithWheel(event);
})
For example : https://codepen.io/adrienlamotte/pen/bGgPgqj

Related

Include my Javascript animation on all the html body so it stays while scrolling the page

Hi i'm learning html/css and javascript and I think I'm having an issue with my html structure. Basically what I want to do is that my particles animation stays on the website while scrolling the page. I have a Javascript file that does a getElementById('particles') to run the canvas on a div but it only stays on the first page.
I tried to move the "particles" div as a main div that will contain all the sections but it didn't work.
Here's the repository of the files if anyone is interested: https://github.com/DanielVillacis/DanielVillacis.github.io
Here's my html structure :
document.addEventListener('DOMContentLoaded', function() {
particleground(document.getElementById('particles'), {
dotColor: '#FFFFFF',
lineColor: '#FFFFFF'
});
var intro = document.getElementById('intro');
intro.style.marginTop = -intro.offsetHeight / 2 + 'px';
}, false);
html,
body {
width: 100%;
height: 100vh;
}
canvas {
display: inline-block;
vertical-align: baseline;
}
header,
section {
display: block;
}
#particles {
width: 100%;
height: 100vh;
overflow: hidden;
position: relative;
}
.container {
scroll-snap-type: y mandatory;
overflow-y: scroll;
height: 100vh;
}
section {
height: 100vh;
width: 100%;
display: inline-block;
scroll-snap-align: start;
}
<body>
<div class="container">
<main role="main">
<section class="intro">
<div id="particles">
<header class="splash" id="splash" role="banner">
<div id="intro">
</div>
</header>
</div>
</section>
<section class="AboutMe">
<div class="introduction">
</div>
</section>
<section class="box">
<div class="projectContainer">
</div>
</section>
<section class="Contact">
<h2 class="ContactTitle">Contact</h2>
<div class="contactLinks">
</div>
</section>
</main>
</div>
</body>
Use the CSS position: fixed; property.
With position set to fixed, your canvas is positioned relative to the viewport and hence would remain even while scrolling.
.pg-canvas {
display: block;
position: fixed;
top: 0;
left: 0;
pointer-events: none;
width: 100%;
height: 100vh;
}
You have put the particles (which are shown on a canvas) into a section which will scroll out of view.
The particles library you are using places this canvas just before the element you have given it, which has id particles.
You can fix just the canvas by adding position: fixed to the canvas selector in your style sheet (watch out if you have other canvases to give a more definite selector).
This will work in many cases to fix the canvas with the particles to the viewport. But note this description from MDN
The element is removed from the normal document flow, and no space is
created for the element in the page layout. It is positioned relative
to the initial containing block established by the viewport, except
when one of its ancestors has a transform, perspective, or filter
property set to something other than none (see the CSS Transforms
Spec), in which case that ancestor behaves as the containing block.
(Note that there are browser inconsistencies with perspective and
filter contributing to containing block formation.) Its final position
is determined by the values of top, right, bottom, and left.
You are OK at the moment because you move intro with top but if that were a translate you’d have to put the canvas out of intro.

locomotive-scroll toggle header onscroll

I'm trying out locomotive-scroll for the first time and it's been good so far until I had to try to toggle the header style onscroll because the eventlistener isn't working. I've looked into gsap scrolltrigger and intersection observer but I'm having a difficult time figuring things out because I have little knowledge on the area.
How can I make this work / do something like this on locomotive scroll?
let header = document.querySelector("header");
function toggleHeader(ev) {
if (window.pageYOffset > 300) {
header.classList.add("header--active");
} else {
header.classList.remove("header--active");
}
}
window.addEventListener('scroll', function() {
toggleHeader();
});
HTML/CSS/JS
<div data-scroll-container=''>
<div class='header'>
Header
</div>
<section data-scroll-section=''></section>
<section data-scroll-section=''></section>
</div>
.header {
height: 80px;
background: #f00;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
position: fixed;
width: 100%;
left: 0;
top: 0;
transition: ease 0.4s;
}
.header--active {
height: 40px;
background-color: #000;
}
section {
height: 500px;
border-bottom: 1px solid #f00;
}
const scroller = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true
})
a functional sample:
var el = document.getElementById("test");
var wheel = "nothing";
var scroll = new LocomotiveScroll({
el: el,
smooth: true,
repeat: true,
getDirection: true
});
scroll.on("call", (fun, dir, obj) => {
console.log("call", fun, dir);
});
scroll.on("scroll", (obj) => {
if (obj.direction == wheel) return;
wheel = obj.direction;
console.log("scroll--", obj.direction);
console.log(obj.currentElements['0'],obj.currentElements['1'], Object.keys(obj.currentElements));
});
<script src="https://cdn.jsdelivr.net/npm/locomotive-scroll#4.1.0/dist/locomotive-scroll.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/locomotive-scroll#4.1.0/dist/locomotive-scroll.min.css">
<div id="test" data-scroll-container=''>
<div class='header'>
Header
</div>
<section data-scroll-section=''>
<div data-toggle data-toggle-call="text1">
<h2 class="intro__title" data-scroll data-scroll-call="text0">If I scroll to the second intro section (AND the first intro is in the viewport) it will change to a black background. But if I scroll up it will not change to a white background as the section did not get out from the viewport and the last call
was with a black background.</h2>
<p>You have to completely pass through a section to be able to reverse back the colors.<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></p>
</div>
<div data-toggle data-toggle-call="text2">
<h2 class="intro__title" data-scroll data-scroll-call="text1">If I scroll to the second intro section (AND the first intro is in the viewport) it will change to a black background. But if I scroll up it will not change to a white background as the section did not get out from the viewport and the last call
was with a black background.</h2>
<p>You have to completely pass through a section to be able to reverse back the colors.<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></p>
</div>
</section>
</div>

Inconsistent height of google traffic map

I am trying to adjust the height of a google traffic map using JQuery.
This is how the map is initialized (TrafficMapController):
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 11,
center: {lat: 50.1795159, lng: 9.040013600000066}
});
var trafficLayer = new google.maps.TrafficLayer();
trafficLayer.setMap(map);
}
The above is executed as it is described in the documentation:
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCXOgZ5cNqC4XNZIi4OjZzoMANgDnA3Tb0&callback=initMap"></script>
This is the adjustment of the height which is being executed after the map is loaded (DashboardController):
$(document).ready(function resizeGoogleMap() {
var bodyheight = $('body').height();
var headerHeight = $('#page-header').height();
$('#map').css('height', bodyheight - headerHeight);
})
Complete Html:
<html>
<head>
<script src="../frameworks/jquery/jquery-3.2.1.js"></script>
<script src="../frameworks/bootstrap/js/bootstrap.min.js"></script>
<script defer src="../js/TrafficMapController.js"></script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCXOgZ5cNqC4XNZIi4OjZzoMANgDnA3Tb0&callback=initMap"></script>
<script defer src="../js/DashboardController.js"></script>
<link rel="stylesheet" href="../frameworks/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="../css/dashboard.css">
</head>
<body>
<div class="container-fluid">
<div class="row page-header" id="page-header">
<div class="col-md-4 col-lg-4"></div>
<div class="col-md-4 col-lg-4">
<p>Info-Dashboard</p>
</div>
<div class="col-md-4 col-lg-4" id="refresh">
<p id="a">Aktualisierung in </p><p id="timer-count">300</p><p id="a"> Sekunden</p>
</div>
</div>
<div class="row">
<div class="col-md-4 col-lg-4 info-item1">
<div id="map"></div>
</div>
</div>
</div>
</body>
</html>
CSS:
body {
overflow: hidden;
}
.page-header {
background-color: #b3b3b3;
color: white;
text-align: center;
font-size: 40pt;
}
.info-item1 {
padding: 0;
height: auto;
}
#refresh {
color: white;
font-size: 20pt;
vertical-align: baseline;
display: inline;
}
#timer-count {
display: inline;
}
#a{
display: inline;
line-height: 77pt;
}
My problem is, that the resizing only works correctly sometimes and I am not able to figure out why.
Sometimes when the page is reloaded, the heigth of the map is correct and does fit on the page as desired, some other times the height is either too small or too big.
I would appreciate if someone could explain why this is happening.
Cheers.
I know you're trying to have the #map div height automatically calculate depending on the body height but keep things things in mind:
Always set the map div's height to something. Usually it's 100% or a default would work too. #map { height: 100% }
If you want to re-adjust the height when a user resizes your window, you need to place your resizeGoogleMap() not only on document ready, but also on a window resize event like window.onresize = function(event)
If you want to resize the map height to make it responsive for mobile, just use media queries. #media only screen and (max-width: 632px) { #map { height: 100px; } }
The code you gave is actually still not minimal and complete. When asking questions here, always try to provide fully working code in a minimal environment. You can get rid of your other links to controllers, etc. Here's the jsfiddle of some of your code in a minimal and complete example.

el.scrollIntoViewIfNeeded() scrolls too far up

el.scrollIntoViewIfNeeded() scrolls to el if it's not inside of the visible browser area. In general it works fine but I'm having problems with using it with a fixed header.
I made an example snippet: (The method doesn't work in Firefox, so neither does the demo) https://jsfiddle.net/ahugp8bq/1/
In the beginning all three colored divs are displayed below the fixed header. But if you click "second" and then "first", the beginning of #first will be behind the header, which I don't want.
The problem seems to be that the position of #otherContainer (its padding-top) is pretty much ignored when scrolling up.
Actually, this is quite simple if you use the consistent and supported getBoundingClientRect().top + body.scrollTop way - all you now have to do is reduce the header from it, so just get it and calculate its height.
var header = document.getElementById('container')
var clicks = document.querySelectorAll('#container li');
var content = document.querySelectorAll('#otherContainer > div');
// Turn the clicks HTML NodeList into an array so we can easily foreach
Array.prototype.slice.call(clicks).forEach(function(element, index){
element.addEventListener('click', function(e){
e.preventDefault();
// Set the scroll to the top of the element (top + scroll) minus the headers height
document.body.scrollTop = content[index].getBoundingClientRect().top + document.body.scrollTop - header.clientHeight;
});
});
#container {
position: fixed;
background: yellow;
width: 100%;
height: 50px;
}
ul li {
display: inline;
cursor: pointer;
}
#otherContainer {
padding-top: 60px
}
#first, #second, #third {
height: 500px
}
#first {
background: red
}
#second {
background: green
}
#third {
background: blue
}
<div id="container">
<ul>
<li id="jumpToFirst">first</li>
<li id="jumpToSecond">second</li>
<li id="jumpToThird">third</li>
</ul>
</div>
<div id="otherContainer">
<div id="first"></div>
<div id="second"></div>
<div id="third"></div>
</div>

Side-Scrolling img div using .scrollWidth

I have simple html document that contains divs which hold a series of images:
<div id="container">
<div id="imagelist">
<a href="images/1.jpg"><img src="images/1b.jpg"/>
<a href="images/2.jpg"><img src="images/2b.jpg"/>
<a href="images/3.jpg"><img src="images/3b.jpg"/>
<a href="images/4.jpg"><img src="images/4b.jpg"/>
<a href="images/5.jpg"><img src="images/5b.jpg"/>
<a href="images/6.jpg"><img src="images/6b.jpg"/>
</div>
</div>
I would like to be able to scroll horizontall through the images when hovering over the left or right edge of the div (I have multiple #imagelists all stacked vertically)
I'm trying to use the .scrollWidth() function as such (this is in my script.js file):
var imglist = $('#imagelist');
$(imglist).mousemove(function(e) {
var percent = e.clientX / $(imglist).width();
$(imglist).scrollWidth($(imglist).width() * percent);
});
This doesn't work at all, of course! I've been trying to model this after some good examples I've seen, such as This. What should I alter to make my #imagelist scrollable?
Here's a way to do it using offset and relative positioning.
demo
The HTML looks similar to yours, with the exception that we create elements for the edges. The benifit is that we can style them with CSS, should you ever decide you want :hover styles (example in the demo).
<div class="imagecontainer">
<div class="imagelist">
<img src="http://placehold.it/400x300">
...
<img src="http://placehold.it/400x300">
</div>
<div class="edge right"></div>
<div class="edge left"></div>
</div>
The entire CSS is in the demo, this is just the essentials.
.imagecontainer {
width: 100%;
height: 400px;
overflow-x: hidden;
position: relative;
}
.imagelist {
/* Width allows up to 100 screenfuls, feel free to add a 0
Limiting can be done in the JavaScript */
width: 10000%;
height: 400px;
position: relative;
/* Give it a default left of negative to allow scrolling in either direction */
left: -500px; top: 0;
clear: right;
}
.imagelist img {
float: left;
}
.edge {
position: absolute; top: 0;
width: 50px; height: 100%;
}
.edge.left { left: 0; }
.edge.right { right: 0; }
The JavaScript is the fun part. We find the edges and watch for hover and leave events. Considering only one may be hovered at once (both practically and due to mouseenter), we simply have one timer pointer. This timer controls our animation, and is used to stop the animation (clearInterval) on mouseleave. 20 times per second we move the .imagelist 5 pixels in one direction. That's determined based on which edge we're hovering over.
Instead of using $('.imagelist') we use .parent().find('.imagelist') so that there may be any number of image lists on the page.
var timer = 0;
$('.edge').mouseenter(function(){
var $self = $(this);
var $imglist = $self.parent().find('.imagelist');
timer = setInterval(function(){
var amount, changed;
if ($self.hasClass("left"))
amount = -5;
else
amount = 5;
changed = $imglist.offset().left + amount;
$imglist.offset({left: changed});
}, 50)
}).mouseleave(function(){
clearInterval(timer);
});
It's a little rough, but you can polish it up to suit your needs.

Categories