Stop SVG scroll animation from reversing - javascript

In the site that I am currently building I have an SVG path which is basically a straight line. When the user scrolls down the page, this path draws down the page until the user hits the bottom of the page.
The code I am using for this is taken from this page https://www.w3schools.com/howto/howto_js_scrolldrawing.asp
and can be seen here:
<svg style="min-height: 113.5%;" version="1.1" id="line_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="4px" height="113.5%" xml:space="preserve">
<path style="max-height: 113.5%;" id="blue_line" fill="freeze" stroke-width="4" stroke="#3b7fdc" d="M0 0 v2884 400" />
</svg>
<script>
// Get the id of the <path> element and the length of <path>
var triangle = document.getElementById("blue_line");
var length = triangle.getTotalLength();
// The start position of the drawing
triangle.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
triangle.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
triangle.style.strokeDashoffset = length - draw;
}
This works really well, however, currently, when the user scrolls back up the page, the SVG path 'reverses' back up the page with it. My desired effect is for the svg to only draw down the page and to not reverse when the user scrolls back up the page.
Does anyone know how to edit the script above to achieve this desired behaviour?

I've made a slight change to the script and it now does what you need...
<script>
// Get the id of the <path> element and the length of <path>
var triangle = document.getElementById("triangle");
var length = triangle.getTotalLength();
// The start position of the drawing
triangle.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
triangle.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
var lastScrollpercent = 0;
function myFunction() {
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
if (scrollpercent < lastScrollpercent) return;
lastScrollpercent = scrollpercent;
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
triangle.style.strokeDashoffset = length - draw;
}
</script>
To summarise, I added lastScrollpercent and set it to 0. When the page is scrolled it works out the current percentage that it's been scrolled and only draws anything if that is currently more than lastScrollpercent, which it then updates (it's the value of scrollpercent last time anything was drawn). If the current scroll percent is less then it just returns (does nothing). That way, if you scroll up then it stops drawing, and if you then scroll down again it will only continue from where it left off.

Related

Revealing SVG on section scroll

I'm using the function in this JSFiddle to fetch the percentage of an element seen within the viewport on scroll (capped at 100 once user scrolls beyond it):
// Track percentage of section seen within viewport
// Assign target section to var
const element = document.getElementById('section--example');
// Calculate percentage of section in viewport
const percentageSeen = () => {
// Get the relevant measurements and positions
const viewportHeight = window.innerHeight;
const scrollTop = window.scrollY;
const elementOffsetTop = element.offsetTop;
const elementHeight = element.offsetHeight;
// Calculate percentage of the element that's been seen
const distance = (scrollTop + viewportHeight) - elementOffsetTop;
const percentage = Math.round(distance / ((viewportHeight + elementHeight) / 100));
// Restrict range between 0 — 100
return Math.min(100, Math.max(0, percentage));
}
Then, I'm attempting to use that percentageSeen value in the following function to animate an SVG path into view on scroll:
// Get a reference to the <path>
var path = document.querySelector('#path--example');
// Get length of SVG path
var pathLength = path.getTotalLength();
// Create dashes to match the length of the SVG path
path.style.strokeDasharray = pathLength + ' ' + pathLength;
// Initially offset dashes to hide SVG entirely
path.style.strokeDashoffset = pathLength;
path.getBoundingClientRect();
// Animate SVG on section scroll
window.addEventListener('scroll', function(e) {
// Fetch percentage seen
var scrollPercentage = percentageSeen;
// Length to offset the dashes
var drawLength = pathLength * scrollPercentage;
// Draw in reverse
path.style.strokeDashoffset = pathLength - drawLength;
});
I've created a JSFiddle here with my progress — I can log the visible percentage to the console without issue, but that value doesn't seem to be accepted when used in the above scroll function. I can also reveal the SVG based on page height (commented out in the JSFiddle), but not the section height. Can anyone help point me in the right direction that'll allow the use of the percentageSeen var in the scroll function?
The main problem is that percentageSeen is a percentage. When you multiply pathLength by percentageSeen you get a value that is 100 times larger than the value you want.
Also, you were calling the function without parentheses, which meant that scrollPercentage did not store the result, but the function itself.
I think you want the element to appear as the user scrolls. In that case, it is simpler to set strokeDasharray to 0 pathLength and then drawLength (pathLength-drawLenght).
You are capping percentageSeen at 100, which means that the last capping procedure is not necessary (also, you set the cap at 0.99 and you should have set it to 99).
I have forked the fiddle with a working example. As you can see, percentageSeen is never lower than 38 or larger than 61. You may want to fix the calculation.
// Animation — Stitch [Scroll]
// Reference: https://css-tricks.com/scroll-drawing/
// --------------------------------------------------
// Track percentage of animation container (section) in viewport
// Assign target section to var
const element = document.getElementById('section--example');
// Log percentage to console
const logPercentageSeen = () => {
//console.log(percentageSeen());
}
// Re-run 'logPercentageSeen' on scroll
window.addEventListener('scroll', logPercentageSeen);
// Calculate percentage of section in viewport
const percentageSeen = () => {
// Get the relevant measurements and positions
const viewportHeight = window.innerHeight;
const scrollTop = window.scrollY;
const elementOffsetTop = element.offsetTop;
const elementHeight = element.offsetHeight;
// Calculate percentage of the element that's been seen
const distance = (scrollTop + viewportHeight) - elementOffsetTop;
const percentage = Math.round(distance / ((viewportHeight + elementHeight) / 100));
// Restrict the range to between 0 and 100
return Math.min(100, Math.max(0, percentage));
}
// Log the initial value before any scrolling has happened
logPercentageSeen();
// Get a reference to the <path>
var path = document.querySelector('#path--example');
// Get length of path
var pathLength = path.getTotalLength();
// Make very long dashes (the length of the path itself)
path.style.strokeDasharray = '0 ' + pathLength;
// Offset the dashes so the it appears hidden entirely
path.style.strokeDashoffset = 0;
// Jake Archibald says so
// https://jakearchibald.com/2013/animated-line-drawing-svg/
path.getBoundingClientRect();
// When the page scrolls...
window.addEventListener('scroll', function(e) {
// What % down is it?
var scrollPercentage = percentageSeen();
// This runs, but is tied to the page height, NOT the section height
// var scrollPercentage = (document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Length to offset the dashes
var drawLength = pathLength * scrollPercentage / 100;
// Draw in reverse
var rest = pathLength - drawLength;
path.style.strokeDasharray = drawLength + ' ' + rest;
// When complete, remove the dash array, otherwise shape isn't quite sharp
// Accounts for fuzzy math
});
<section id="section--example">
<h1>Section heading</h1>
<p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</p>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="300" height="300"
viewBox="0 0 900 512" style="enable-background:new 0 0 900 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:1.24;stroke-miterlimit:10;}
</style>
<path id="path--example" class="st0" d="M30.5,58.3c0,0,60.4-124,372,4.3c109.4,45,230.4,82.8,350.2,76.8c33.5-1.7,76.9-4.2,101.8-28.6
c25.6-25.1,17.3-60.8-11-79.5c-63.6-42.1-221,5.4-354.8,74.4c-40.3,21.1-81.4,40.7-122.9,59.4c-57.5,25.9-116,53.7-179.2,62.3
C153.5,232,67.6,238,61.5,189.2c-5.9-47,80-58.5,111.4-54.4c41,5.4,112.1,14,282.5,100.3c58.8,29.8,123.4,52.2,189.8,55.2
c29.2,1.3,91.5,2.8,104.1-31.5c8.1-22.1-9.3-42.5-30-46.9c-23.5-5-49.5,1.5-72.1,8c-45,12.9-88.3,32.2-130.6,52.3
c-26.8,12.8-53.4,26.2-79.5,40.5c0,0-87.3,49.6-140.2,49.6c-17.4,0-39.6-1.3-53.5-13.5c-13.1-11.6-13-30.9,2-41.3
c36.8-25.8,86,3.2,119.9,20.7c25.1,13,49.8,26.9,73.9,41.7c0,0,45.3,28,96,28c13.6,0,30.7-2.8,32-19.8c1.1-15.6-16.7-25.9-30-28
c-22.5-3.5-43.6,8.8-58,25.2c-41,46.4-18.3,114.3,25.9,133.7"/>
</svg>
<p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</p>
</section>

Scale logo on scroll with jQuery, dependant on scroll percentage

I want to scale down logo in the header as user scrolling down the page.
When he hits a certain breakpoint, scaling should stop.
Also, scale can't be more or less specified hardcoded sizes.
Live case:
The header is separated into two section - top and bottom. The bottom part is sticky (pos: fixed) and the top part gets faded out (pos: absolute).
Logo should be smoothly scaled, so it's big (140px height) when all header elements are visible and small (50px height) when it's sticky
So, I can't figure out how to calculate a number within a range of limit[0] and limit[1] that is dependant on the percentage of scroll distance between 0 and 100 (100 is a header top height in my case)
Currently, it's scaling 1 to 0, but I need to scale 1 to 0.357
===
Short video (10s) to demonstrate a problem
https://monosnap.com/file/p5P7GMkn3BKKMu9glrnmBiO5X6951z
Codepen
https://codepen.io/dpmango/pen/EOXVww
===
$(window).on('scroll', function(e) {
var vScroll = _window.scrollTop();
var $logo = $('[js-header-logo]');
var $top = $('[js-header-top]');
var topHeight = $top.outerHeight();
var logoLimits = [1, 0.357] // [140, 50] // scale factor
var scrollPercent = 1 - (vScroll / topHeight) // 1 -> 0
var calcedScale = scrollPercent // what is logic ?
// limit rules
if ( vScroll > topHeight ){
calcedScale = logoLimits[1]
}
if ( vScroll < 0 ){
calcedScale = logoLimits[0]
}
// set values to DOM
$logo.css({
"transform": 'scale('+ calcedScale +')'
})
});

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});
}
}
})
})

jQuery Circles animation on circle path

I'm trying to make big circle and move divs along the circle's circumference.
Each div must change the content inside the big circle.
The number of div(s) must be dependent on how many are fetched from database (from table category).
I tried to do this and modified the code by putting .eq() but the problem with .eq is that next circle will appear after that circle, all put in the same place. I want them all to appear at the same time like this without repeating functions
Updated your fiddle:
http://jsfiddle.net/wyW2D/1/
Used:
var t = -1.6;
var t2 = -1.6;
var x = 0;
var t = [-1.6, -1.6, -1.6], // starting angle in radians for each circle
delta = [0.05, 0.03, 0.02], // change in radians for each circle
// e.g the first moves fastest, the last
// slowest. if this gets too big, the
// movement won't look circular, since the
// animation is really a bunch of straight lines
finish = [1.4, 1.0, 0.6]; // the stopping point in radians for each
// circle. if the circle size changes, this
// may need to change
function moveit(i) {
t[i] += delta[i]; // move the angle forward by delta
var r = 300; // radius (the .inner div is 600 x 600)
var xcenter = -30; // center X position: this reproduces the .inner horizontal
// center but from the body element
var ycenter = 420; // center Y position: same here but vertical
// Basic trig, these use sin/cos to find the vert and horiz offset for a given
// angle from the center (t[i]) and a given radius (r)
var newLeft = Math.floor(xcenter + (r * Math.cos(t[i])));
var newTop = Math.floor(ycenter + (r * Math.sin(t[i])));
// Now animate to the new top and left, over 1ms, and when complete call
// the move function again if t[i] hasn't reached the finish.
$('div.circle'+(i+1)).animate({
top: newTop,
left: newLeft,
},1, function() {
if (t[i] < finish[i]) moveit(i);
});
// You can slow down the animation by increasing the 1, but that will eventually
// make it choppy. This plays opposite the delta.
}
// Start the ball rolling
$("document").ready(function(e) {
moveit(0);
moveit(1);
moveit(2);
});
This was a quick change to reduce the code to one function that used arrays (t, delta, finish) to keep track of the three circles. It could be improved to accept arbitrary circles, of any size, at any starting / ending angle.
Also, this kind of animation is much easier with CSS. It is simple to specify and has much better performance.

Script to draw simple SVG-path slows down the entire PC

I've got a svg-based loading animation for a web project, which draws a 360°-arc on hover. This takes about .8s to finish. After that a container slides in, see my fiddle: http://jsfiddle.net/s2XCL/2/
function drawCircle(arc) {
var i = 0;
var circle = document.getElementById(arc);
var angle = 0;
var radius = 75;
window.timer = window.setInterval(
function () {
angle -= 5;
angle %= 360;
var radians = (angle / 180) * Math.PI;
var x = 200 + Math.cos(radians) * radius;
var y = 200 + Math.sin(radians) * radius;
var e = circle.getAttribute("d");
if (i === 0) {
var d = e + " M " + x + " " + y;
} else {
var d = e + " L " + x + " " + y;
}
circle.setAttribute("d", d);
i++;
}, 10);
}
function removeCircle() {
// var circle = document.getElementById(arc);
// circle.style.display = "none";
}
The function is called inside of the svg-tag in the HTML-markup:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="xMidYMid" style="width:400px; height:400px;" onmouseover="drawCircle('arc2'); removeCircle();">
<path d="M200,200 " id="arc2" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="5"></path>
</svg>
This works as intended, but occupies an entire 3 GHz-core on both of my two testing machines. I am currently learning JS, so I can only guess what causes the performance lack.
I also want the svg to disappear after completing the animation or the mouse leaving the container, so if anyone got a tip on that, that'd be great.
For any fellow googlers, see here http://jsfiddle.net/s2XCL/4/ for the solution. Feel free to use the snippet aswell.
The problem is that you never clear your timeouts. This will cause your timer to keep running, and every time you move your mouse above the svg element, it will keep creating new timers. That means that if you move your mouse a bit too long, it'll keep adding more and more to the path tag, and it will create a lot of lag.
To fix this, simply add a clearInterval for your global timer variable:
clearInterval(timer);
You'll also want to make sure this only runs when you initially move your mouse on top of the svg, not every time you move within it, by changing the event handler to mouseenter, not mousemove. And finally, you would need to clear the path's d attribute, otherwise the circle won't ever be removed.
The function would have the following: (http://jsfiddle.net/s2XCL/3/)
clearInterval(window.timer||0); //clear the previous timer, or "timer 0" (nonexistent) when it's not defined yet
circle.setAttribute("d", "M200,200 "); //reset d attribute to default
window.timer = window.setInterval(//interval
Also, building a whole path each time is not a very efficient way to do it.
A better way would be to use the dasharray trick for animating drawing of a path. What you do is slowly increase the first number in stroke-dasharray until it is equal to the length of the path. In this case, that's the circumference of the circle.
stroke-dasharray="0 1000"
stroke-dasharray="5 1000"
stroke-dasharray="10 1000"
...etc...
Here's a fiddle showing this trick applied to your case.

Categories