How to fill svg when scrolling - javascript

I have svg line:
<svg class="filling" width="500" height="10" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path data-over-line="" d="M90,5 L500,5" stroke="#e2e2e2" fill="transparent" stroke-width="4" style="stroke-dashoffset: 0px;"></path>
</svg>
I need when scroll the page it is gradually filled with a different color. How to do it?

I am not sure what shape you ultimately want for your scrollbar, but here is a simple solution. We draw a blue line on top of your grey line to indicate scroll progress. The length of the line is determined by calculating how far done the page we have scrolled.
If you ultimately want to have the scrollbar be a shape other than a line or a rectangle, you will need to take a different approach.
SVG (modified a little):
<svg class="filling" width="500" height="10" version="1.1" xmlns="http://www.w3.org/2000/svg">
<line x1="90" y1="5" x2="500" y2="5" stroke="#e2e2e2" fill="transparent" stroke-width="4" />
<line x1="90" y1="5" x2="90" y2="5" stroke="blue" fill="transparent" stroke-width="4" id="scrollprogress" />
</svg>
JS:
window.onscroll = function (event) {
var offset = window.pageYOffset;
var wheight = window.innerHeight;
var html = document.documentElement;
var docheight = Math.max(document.body.scrollHeight, document.body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);
var progress = offset / (docheight - wheight);
// Give the line a CSS gradient based on scroll position
document.getElementById("scrollprogress").setAttribute("x2", 90 + progress * 410);
}
Demo fiddle

Related

SVG getBoundingClientRect() + "transform: rotate()" is bugged in Chrome in a very specific way

getBoundingClientRect() works fine on all SVG elements except in the circumstance when an element is rotated in Chrome.
Below I have drawn 2 lines, the one on the left is diagonal and the right line starts vertical, as represented by the black lines. The green box is a visual representation of the getBoundingClientRect(). I then apply the same transform rotate to both lines at the same time and then update the bounding box visual.
As you can see from the following, the left bounding box does not represent the actual bounds, however, the right one does.
The problem is I need the true client bounds of the line in whatever orientation it is. This works fine in Firefox. Does anyone know of another way to get or calculate the bounds when a line, specifically, has a transform rotate?
I have posted a copy of this here: getBoundingClientRect() + "transform: rotate()" is bugged in Chrome?. To help people with a similar problem, but then decided I'd actually ask the community.
Chrome: v100.0.4896.75
This bug has now been reported to the cromium bugs board here: https://bugs.chromium.org/p/chromium/issues/detail?id=1314959
UPDATE: I've added the same 2 static lines with a direct transform attribute with a rotation of 40deg, and 2 div's representing the 'getBoundingClientRect'. The left box is clearly not showing the smallest rectangle which contains the entire element.
UPDATE 2 Included a BBox Visual in blue. getBBox() simply calculates the bounds of a child relative to the SVG element itself, prior to any rotation transforms. getBoundingClientRect does calculate the transform correctly, but is using the BBox, which has it's own issues here: https://bugs.chromium.org/p/chromium/issues/detail?id=377665
let l = [1,2,3,4,5,6].map(d => document.getElementById(`line${d}`)),
vis = [1,2,3,4,5,6].map(d => document.createElement(`div${d}`)),
bboxvis = [1,2,3,4,5,6].map(d => document.createElement(`div${d}`)),
r = 0;
vis.map((element) => {
document.body.appendChild(element)
element.style.position = "absolute"
element.style.border = "1px solid green"
element.style.background = `#0FF0002e`
})
bboxvis.map((element) => {
document.body.appendChild(element)
element.style.position = "absolute"
element.style.border = "1px solid blue"
element.style.background = `#0000FF2e`
})
let updateBounds = (element, displayElement, bboxvisEl) => {
let rect = element.getBoundingClientRect(),
svgBounds = element.parentElement.getBoundingClientRect(),
bbox = element.getBBox(),
d = document.documentElement;
displayElement.style.top = rect.y+d.scrollTop+"px"
displayElement.style.left = rect.x+d.scrollLeft+"px"
displayElement.style.width = rect.width+"px"
displayElement.style.height = rect.height+"px"
bboxvisEl.style.top = bbox.y+svgBounds.y+d.scrollTop + "px"
bboxvisEl.style.left = bbox.x+svgBounds.x+d.scrollLeft +"px"
bboxvisEl.style.width = bbox.width+"px"
bboxvisEl.style.height = bbox.height+"px"
}
[3,4,5].forEach(i => {
updateBounds(l[i], vis[i], bboxvis[i]);
bboxvis[i].style.transform = `rotate(40deg)`;
})
setInterval(() => {
[0,1,2].forEach(i => {
l[i].setAttribute("transform", `rotate(${r} 80 80)`);
bboxvis[i].style.transform = `rotate(${r}deg)`;
updateBounds(l[i], vis[i], bboxvis[i]);
})
r++;
if(r===360) r=0;
}, 20)
.container {
width:140px;
height:140px;
display:inline-block;
}
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="40" y1="40" x2="120" y2="120" stroke="black" stroke-width="2"></line>
<line x1="40" y1="40" x2="120" y2="120" id="line1" stroke="red" stroke-width="2"></line>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="80" y1="30" x2="80" y2="130" stroke="black" stroke-width="2"></line>
<line x1="80" y1="30" x2="80" y2="130" id="line2" stroke="red" stroke-width="2"></line>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="60" y1="30" x2="100" y2="130" stroke="black" stroke-width="2"></line>
<line x1="60" y1="30" x2="100" y2="130" id="line3" stroke="red" stroke-width="2"></line>
</svg>
<br/>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="40" y1="40" x2="120" y2="120" id="line4" stroke="red" stroke-width="2" transform="rotate(40 80 80)"></line>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="80" y1="30" x2="80" y2="130" id="line5" stroke="red" stroke-width="2" transform="rotate(40 80 80)"></line>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="container">
<line x1="60" y1="30" x2="100" y2="130" id="line6" stroke="red" stroke-width="2" transform="rotate(40 80 80)"></line>
</svg>

Draw rhombus inscribed in a group with circle SVG

I have multiple <g> elements and all of them have a circle:
<g class="my-group">
<circle r="40" cx="10" cy="10"></circle>
</g>
In some cases I need to draw a rectange with rounded corners instead of circle, it should be presented as rhombus. I'm trying to draw it like this:
<g class="my-group">
<rect x="-16" y="-30" width="60" height="60" fill="red" transform="rotate(45)" rx="4"></rect>
</g>
But this is a wrong way to hardcode coordinates and width/height of rect.
How I can calculate width and height of rotated rect to inscribe it in circle, so group will have the same width and height 80px.
Simple pythagorean theorem. Rectangle width (or hypotenuse) equals the square root of two lots of the radius squared.
const radius = document.querySelector('#circle').getAttribute('r')
const rectWidth = Math.sqrt(radius * radius * 2)
const square = document.querySelector('#square')
square.setAttribute('x', -rectWidth/2 + 'px')
square.setAttribute('y', -rectWidth/2 + 'px')
square.setAttribute('width', rectWidth + 'px')
square.setAttribute('height', rectWidth + 'px')
svg {
width: 100vmin;
height: 100vmin;
}
<svg viewbox="0 0 100 100">
<circle id="circle" fill="green" r="40" cx="50" cy="50"></circle>
<rect id="square" fill="red" transform="translate(50, 50) rotate(45)"></rect></svg>

Moving image on scroll through svg path

I want to move object through svg path on scroll=) I was trying to add parts of path on scroll into path, but it still doesn't work. Help!!!=)
https://jsfiddle.net/YuriiBielozertsev/Ltx9ed0L/
<?xml version="1.0"?>
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Draw the outline of the motion path in grey, along with 2 small circles at key points -->
<path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110" stroke="green" stroke-width="2" fill="none" id="theMotionPath"/>
<circle cx="10" cy="110" r="3" fill="#000"/>
<circle cx="110" cy="10" r="3" fill="#000"/>
<!-- Red circle which will be moved along the motion path. -->
<circle cx="0" cy="" r="5" fill="red">
<!-- Define the motion path animation -->
<animateMotion dur="6s" repeatCount="indefinite">
<mpath xlink:href="#theMotionPath"/>
</animateMotion>
</circle>
</svg>
Something like this?
How this works:
When we get a scroll event we:
Calculate how far down the page we are
Convert this percentage to a position on the path using the <path> element functions getTotalLength() and getPointAtLength().
Reposition the dot so that it appears at this point.
function positionTheDot() {
// What percentage down the page are we?
var scrollPercentage = (document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Get path length
var path = document.getElementById("theMotionPath");
var pathLen = path.getTotalLength();
// Get the position of a point at <scrollPercentage> along the path.
var pt = path.getPointAtLength(scrollPercentage * pathLen);
// Position the red dot at this point
var dot = document.getElementById("dot");
dot.setAttribute("transform", "translate("+ pt.x + "," + pt.y + ")");
};
// Update dot position when we get a scroll event.
window.addEventListener("scroll", positionTheDot);
// Set the initial position of the dot.
positionTheDot();
.verylong {
height: 2000px;
}
svg {
position: fixed;
width: 200px;
height: 200px;
}
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Draw the outline of the motion path in grey, along with 2 small circles at key points -->
<path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110" stroke="green" stroke-width="2" fill="none" id="theMotionPath"/>
<circle cx="10" cy="110" r="3" fill="#000"/>
<circle cx="110" cy="10" r="3" fill="#000"/>
<!-- Red circle which will be moved along the motion path. -->
<circle cx="0" cy="0" r="5" fill="red" id="dot"/>
</svg>
<div class="verylong">
</div>

SVG animation reduces width of separate vector in file

I've been messing around with SVG path animations. My SVG has 2 layers in it; a grey egg and an orange egg. The orange egg animates over the top of the grey egg, this part works fine. However, both paths technically have the same width, but when I animate the orange egg, the grey egg's path looks like it's reduced by 50%.
Below is an image and the code.
var path = document.querySelector('#egg-orange path');
var length = path.getTotalLength();
var percent = length/100;
var desiredPercent = 50;
var currentPercent = percent * (100 - desiredPercent); // 50%
// Clear any previous transition
path.style.transition = path.style.WebkitTransition =
'none';
// Set up the starting positions
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 2s ease-in-out';
// Go!
path.style.strokeDashoffset = currentPercent;
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 104.7 144.3" enable-background="new 0 0 104.7 144.3" xml:space="preserve">
<g id="egg-grey">
<path fill="#FFFFFF" stroke="#414042" stroke-width="10" stroke-miterlimit="10" d="M99.3,92.2c0,25.7-20.8,46.5-46.5,46.5
S6.3,117.8,6.3,92.2S27.2,5.7,52.8,5.7S99.3,66.5,99.3,92.2z"/>
</g>
<g id="egg-orange">
<path fill="#FFFFFF" stroke="#F15A29" stroke-width="10" stroke-miterlimit="10" d="M99.3,92.2c0,25.7-20.8,46.5-46.5,46.5
S6.3,117.8,6.3,92.2S27.2,5.7,52.8,5.7S99.3,66.5,99.3,92.2z"/>
</g>
</svg>
I found out that the issue was because of the white fill on the orange egg. All i needed to do was add fill-opacity="0" and hey presto, all sorted!
<g id="egg-orange">
<path fill="#fff" fill-opacity="0" stroke="#F15A29" stroke-width="10" stroke-miterlimit="10" d="M99.3,92.2c0,25.7-20.8,46.5-46.5,46.5
S6.3,117.8,6.3,92.2S27.2,5.7,52.8,5.7S99.3,66.5,99.3,92.2z"/>
</g>
EDIT:
As Paul mentioned below, you can just set fill to none and not need to touch opacity.
<g id="egg-orange">
<path fill="none" stroke="#F15A29" stroke-width="10" stroke-miterlimit="10" d="M99.3,92.2c0,25.7-20.8,46.5-46.5,46.5
S6.3,117.8,6.3,92.2S27.2,5.7,52.8,5.7S99.3,66.5,99.3,92.2z"/>
</g>

SVG Path Overlay and Animate Out Another Path

I have an SVG of a dashed gray line. What I want to do is overlay that on top of a green SVG dashed line, and animate out the gray to reveal the green. Sorta like a meter moving from right to left.
I saw this example of how to make a dash line:
http://jsfiddle.net/ehan4/2/
and was able to do it but my line is already dashed.
I ended up doing this:
<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"
viewBox="0 0 666.9 123.8" enable-background="new 0 0 666.9 123.8" xml:space="preserve">
<path opacity="0.4" stroke-width="3" fill="none" stroke="#66CD00" stroke-linecap="round" stroke-miterlimit="5" stroke-dasharray="1,6" d="
M656.2,118.5c0,0-320.4-251-645.9-0.7" />
<path id="top" opacity="0.4" fill="none" stroke="#AEAEAE" stroke-linecap="round" stroke-miterlimit="5" stroke-dasharray="1,6" d="
M656.2,118.5c0,0-320.4-251-645.9-0.7"/>
</svg>
var path = document.querySelector('#top');
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition =
'none';
// Set up the starting positions
path.style.strokeDasharray = 1 + ' ' + 6;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 20s linear';
// Go!
path.style.strokeDashoffset = '0';
https://jsfiddle.net/ps5yLyab/
How can I overlay the two dash lines and animate out the gray?
You can do it with a clip path.
First we add a clipPath to the SVG.
<defs>
<clipPath id="myclip">
<rect id="cliprect" x="100%" y="0%" width="100%" height="100%"/>
</clipPath>
</defs>
This clip path is sized the same size as the SVG (width and height 100%) and starts with its x postion at the far right of the SVG (100%). So at the start it is not revealing anything.
Then every 10mS we reduce it's x coord by 1% (ie 100% -> 99% -> 98% etc). until it reached zero.
var cliprect = document.getElementById("cliprect");
var offsetX = 100;
var speed = 10;
function clipAdjust()
{
cliprect.setAttribute("x", offsetX+"%");
offsetX -= 1;
if (offsetX >= 0) {
window.setTimeout(clipAdjust, speed);
}
}
window.setTimeout(clipAdjust, speed);
Working demo below:
var path = document.querySelector('#top');
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition =
'none';
// Set up the starting positions
path.style.strokeDasharray = 1 + ' ' + 6;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 20s linear';
// Go!
path.style.strokeDashoffset = '0';
var cliprect = document.getElementById("cliprect");
var offsetX = 100;
var speed = 10;
function clipAdjust()
{
cliprect.setAttribute("x", offsetX+"%");
offsetX -= 1;
if (offsetX >= 0) {
window.setTimeout(clipAdjust, speed);
}
}
window.setTimeout(clipAdjust, speed);
<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"
viewBox="0 0 666.9 123.8" enable-background="new 0 0 666.9 123.8" xml:space="preserve">
<defs>
<clipPath id="myclip">
<rect id="cliprect" x="100%" y="0%" width="100%" height="100%"/>
</clipPath>
</defs>
<path opacity="0.4" fill="none" stroke="#AEAEAE" stroke-linecap="round"
stroke-miterlimit="5" stroke-dasharray="1,6" stroke-width="2"
d="M656.2,118.5c0,0-320.4-251-645.9-0.7"/>
<g clip-path="url(#myclip)">
<path stroke-width="3" fill="none" stroke="white"
stroke-linecap="round" stroke-miterlimit="5"
d="M656.2,118.5c0,0-320.4-251-645.9-0.7" />
<path id="top" opacity="0.4" stroke-width="3" fill="none" stroke="#66CD00"
stroke-linecap="round" stroke-miterlimit="5" stroke-dasharray="6,6"
d="M656.2,118.5c0,0-320.4-251-645.9-0.7" />
</g>
</svg>

Categories

Resources