I have a SVG line that triggers on scroll and goes down the page as i scroll. I want the trigger to be at a specific location on the page, but for now, even though the SVG is located further down the page, it starts "growing" as soon as the user starts scrolling (which result in no animation when i arrive at that location, since the SVG is already all scrolled out).
Here's the js of that particular SVG:
var path = document.querySelector('#star-path');
var pathLength = path.getTotalLength();
path.style.strokeDasharray = pathLength + ' ' + pathLength;
path.style.strokeDashoffset = pathLength;
window.addEventListener('scroll', () => {
var scrollPercentage = (document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
var drawLength = pathLength * scrollPercentage;
path.style.strokeDashoffset = pathLength - drawLength;
});
And here is the html snippet...
<svg class="svg" width="10" viewBox="0 0 2 150" id="star-svg">
<path id="star-path" fill="none" stroke="black" stroke-width="2" d="M1 0V150" />
</svg>
... as well as the css:
.svg {
position: absolute;
top: 2900px;
left: 1400px;
text-align: center;
overflow: visible;
}
Which element corresponds to that trigger, and what should i do to "delay" it (i am quite new at SVG animations)?
Thanks in advance for your help
Related
I use position() and getBoundingClientRect() to determine if svg object is visible in viewport. Works perfectly well in Chrome, Firefox and EDGE but not in Safari.
obj.position() always returns {top: 0, left: 0} in Safari.
What could be wrong?
options.elements.forEach(function (item) {
let obj = jQuery("#" + item);
if (!!obj && !!obj[0]) {
let position = obj.position();
let box = obj[0].getBoundingClientRect()
if (!!position) {
if (position.left <= left + width &&
position.left + box.width >= left &&
position.top <= top + height &&
position.top + box.height >= top)
onscreen.push(item);
}
}
});
Here is part of HTML. I omitted contents of 'd' of svg
<div id="svgmap">
<svg id="svg">
<g id="R918">
<path d="...." transform="translate(-0.08 0.25)"//>
</g>
<g id="C902J">
<path d="...." transform="translate(-0.08 0.25)"/>
</g>
<g id="C9002I">
<path d="" transform="translate(-0.08 0.25)"/>
</g>
</svg>
Ok, so I didn't figure out why position() is not working in Safari in this particular case but I realized that I don't need to user position() at all. I can get the same data from getBoundingClientRect().left and getBoundingClientRect().top
For a project, I need to create a set of masked images connected to eachother by a single point on each image, and the position of that point is animated randomly (within a restricted range) on scroll.
I have managed to get this working well in Chrome, however Firefox is very buggy and Safari doesn't like the mask at all, let alone the animation. Don't even get me started on Edge/IE11. Any way around this?
Codepen
HTML
<div class="c-nodes-bg c-nodes-bg--set-of-3">
<img id="nodes-bg-3i-1" width="100%" src="http://via.placeholder.com/1500x2800" style="clip-path: url("#clipPolygon");" class="moving">
<img id="nodes-bg-3i-2" width="100%" src="http://via.placeholder.com/1500x2800" style="clip-path: url("#clipPolygon2");" class="moving">
<img id="nodes-bg-3i-3" width="100%" src="http://via.placeholder.com/1500x2800" style="clip-path: url("#clipPolygon3");" class="moving">
</div>
<svg width="0" height="0" >
<clipPath id="clipPolygon" clipPathUnits="objectBoundingBox">
<polygon id="poly1" points="0.3 0.35,1 0.28,1 0, 0.6 0">
<animate id="poly1Anim" attributeName="points" dur="600ms" to="" fill="freeze" />
</polygon>
</clipPath>
<clipPath id="clipPolygon2" clipPathUnits="objectBoundingBox">
<polygon id="poly2" points="0.0 0.26, 0.3 0.35, 0.6 0.7, 0.0 0.70">
<animate id="poly2Anim" attributeName="points" dur="600ms" to="" fill="freeze" />
</polygon>
</clipPath>
<clipPath id="clipPolygon3" clipPathUnits="objectBoundingBox">
<polygon id="poly3" points="0.3 1, 1 1, 1 0.7, 0.6 0.7">
<animate id="poly3Anim" attributeName="points" dur="600ms" to="" fill="freeze" />
</polygon>
</clipPath>
</svg>
CSS
.c-nodes-bg {
position: absolute;
top: 0px;
right: 0;
left: 0;
z-index: 10;
/**
Since the background images are positioned absolute as they sit behind site content,
add in a spacer to force the height of the site to be the same as the images
*/
}
.c-nodes-bg--contact #map-canvas {
width: 100%;
height: 50vw;
}
.c-nodes-bg__spacer {
width: 100%;
height: calc(100% - 22px);
display: block;
}
.c-nodes-bg--set-of-3 img {
display: block;
width: 100%;
}
.c-nodes-bg--set-of-3 img:nth-child(2), .c-nodes-bg--set-of-3 img:nth-child(3) {
position: absolute;
top: 0;
left: 0;
}
--webkit-clip-path: url("#clipPolygon");
clip-path: url("#clipPolygon");
--webkit-clip-path: url("#clipPolygon2");
clip-path: url("#clipPolygon2");
--webkit-clip-path: url("#clipPolygon3");
clip-path: url("#clipPolygon3");
JS
var animPoints = {
init: function() {
var isScrolling;
var scrollingDone = false;
// Init backgrounds on scroll, but wait for a period of time after scroll starts before init
window.addEventListener('scroll', function ( event ) {
window.clearTimeout( isScrolling );
isScrolling = setTimeout(function() {
scrollingDone = true;
}, 300);
// Only start the animation if the previous one has finished
if(scrollingDone === true){
animPoints.randomiseBackgrounds();
scrollingDone = false;
}
}, false);
},
randomiseBackgrounds: function(){
var poly1 = document.getElementById("poly1"); // Points element within SVG
var poly1Points = poly1.getAttribute("points"); // Points attribute containing position values
var poly1PointsArr = poly1Points.split(","); // Convert points into array for ease of manipulation
var poly1PointPair1Arr = poly1PointsArr[0].split(" "); // We only need the first point's coordinates
var poly1Anim = document.getElementById("poly1Anim"); // Get the animation element within the SVG
var poly2 = document.getElementById("poly2"); // Points element within SVG
var poly2Points = poly2.getAttribute("points"); // Points attribute containing position values
var poly2PointsArr = poly2Points.split(" "); // We only need the second point's coordinates
var poly2Anim = document.getElementById("poly2Anim"); // Get the animation element within the SVG
var poly3 = document.getElementById("poly3"); // Points element within SVG
var poly3Points = poly3.getAttribute("points"); // Points attribute containing position values
var poly3PointsArr = poly3Points.split(" "); // We only need the fourth point's coordinates
var poly3Anim = document.getElementById("poly3Anim"); // Get the animation element within the SVG
// Update X axis for point 1
poly1PointPair1Arr[0] = Math.random() * (0.35 - 0.25) + 0.25;
// ..and also assign that to a point on the second poly so they are connected
poly2PointsArr[2] = poly1PointPair1Arr[0];
poly2PointsArr[4] = Math.random() * (0.63 - 0.56) + 0.56;
// ..and assign point 4 (X axis) on polygon 3 to be equal to point 3 on polygon 2 so they are connected also
poly3PointsArr[6] = poly2PointsArr[4];
// Update Y axis and also assign that to a point on the second poly so they are connected
poly1PointPair1Arr[1] = Math.random() * (0.36 - 0.32) + 0.32;
// ..and also assign that to a point on the second poly so they are connected
poly2PointsArr[3] = poly1PointPair1Arr[1];
poly2PointsArr[5] = Math.random() * (0.73 - 0.66) + 0.66;
// ..and assign point 4 (Y axis) on polygon 3 to be equal to point 3 on polygon 2 so they are connected also
poly3PointsArr[7] = poly2PointsArr[5];
// Throw the updated coordinates back into their respective attribute arrays
poly1PointsArr[0] = poly1PointPair1Arr.join();
// Convert modified array of points back to string, and update var
poly1Points = poly1PointsArr.join();
poly2Points = poly2PointsArr.join();
poly3Points = poly3PointsArr.join();
// Update the 'to' attribute with the new values
poly1Anim.setAttribute('to', poly1PointsArr);
poly2Anim.setAttribute('to', poly2Points);
poly3Anim.setAttribute('to', poly3Points);
// Play the animation!
poly1Anim.beginElement();
poly2Anim.beginElement();
poly3Anim.beginElement();
resetPoints = setTimeout(function() {
poly1.setAttribute('points', poly1PointsArr);
poly2.setAttribute('points', poly2Points);
poly3.setAttribute('points', poly3Points);
}, 600);
},
};
animPoints.init();
var scrollTimer = -1;
Is it possible to animate the fill of a inline svg path, to change as the mouse moves around the page ?
In the example in the code snippet below, the x and y positions of the mouse are monitored using the "mousemove" event and the screenX and screenY properties of the event object. Red and green colors values are set depending on those coordinates, and an rgb fill color string is constructed and imposed on an SVG path element.
var path = document.querySelector("path");
document.body.addEventListener("mousemove", function(evt) {
var xRatio = Math.min(1, evt.clientX / document.body.offsetWidth );
var yRatio = Math.min(1, evt.clientY / document.body.offsetHeight);
var red = parseInt(255 * xRatio);
var green = parseInt(255 * yRatio);
var color = "rgb(" + red + ", " + green + ", 128)";
document.querySelector("path").setAttribute("fill", color);
});
body {
border: solid black 1px;
padding: 0.5em;
}
p {
margin: 0;
}
<body>
<p>Move your mouse over the image to see the colour-change effect.</p>
<svg width="400" height="150">
<path d="M10,75 C200,-100 300,120 390,75 200,250 100,30 10,75" stroke="black" stroke-width="4" fill="none" />
</svg>
</body>
I am currently using velocity.js to control some animation on an SVG drawing I am creating. It is interactive with the user, so when a click happens in certain parts, the picture might grow to the right or down. So far everything is working perfectly until the picture gets too big for the SVG box.
In those situations, I simply resize the viewBox to scale the image.
svgDoc.setAttribute("viewBox", "0 0 " + svgDocWidth + " " + svgDocHeight);
This works, but it doesn't look great because it isn't animated. It just jumps to the new size. Is there a way to animate with velocity.js a viewBox change?
I've tried this approach:
$viewBox = $(svgDoc.viewBox);
$viewBox.velocity({height: svgDocHeight, width: svgDocWidth});
But that doesn't do anything.
Is this beyond what velocity.js can support?
Solution on 2015-11-21
#Ian gave the solution I eventually used. It ended up looking like this:
var origHeight = this.svgDoc.viewBox.animVal.height;
var origWidth = this.svgDoc.viewBox.animVal.width;
var docHeight = this.SvgHeight();
var docWidth = this.SvgWidth();
if ((origHeight != docHeight) || (origWidth != docWidth))
{
var deltaHeight = docHeight - origHeight;
var deltaWidth = docWidth - origWidth;
$(this.svgDoc).velocity(
{
tween: 1
},
{
progress: function(elements, complete, remaining, start, tweenValue)
{
var newWidth = origWidth + complete * deltaWidth;
var newHeight = origHeight + complete * deltaHeight;
elements[0].setAttribute('viewBox', '0 0 ' + newWidth + ' ' + newHeight);
}
});
}
You can animate a viewBox smoothly via SMIL. Like this...
<svg width="100%" height="100%" viewBox="0 0 200 200">
<animate attributeName="viewBox" to="0 0 400 400" dur="5s" fill="freeze" />
<circle cx="100" cy="100" r="100" fill="green" />
</svg>
I think SMIL is being deprecated in Chrome (please correct me if I'm wrong), so I'm guessing there will be increasing reliance on other methods.
There's no reason you can't use velocity, but with your own calculations and the progress/tweenValue parameter, eg something a bit like this...
$("#mysvg").velocity(
{ tween: 200 },
{ progress: animViewbox }
)
function animViewbox (elements, complete, remaining, start, tweenValue) {
elements[0].setAttribute('viewBox', '0 0 ' + tweenValue + ' ' + tweenValue);
}
jsfiddle
What is the proper way to get the dimensions of an svg element?
http://jsfiddle.net/langdonx/Xkv3X/
Chrome 28:
style x
client 300x100
offset 300x100
IE 10:
stylex
client300x100
offsetundefinedxundefined
FireFox 23:
"style" "x"
"client" "0x0"
"offset" "undefinedxundefined"
There are width and height properties on svg1, but .width.baseVal.value is only set if I set the width and height attributes on the element.
The fiddle looks like this:
HTML
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="1" fill="red" />
<circle cx="150" cy="50" r="40" stroke="black" stroke-width="1" fill="green" />
<circle cx="250" cy="50" r="40" stroke="black" stroke-width="1" fill="blue" />
</svg>
JS
var svg1 = document.getElementById('svg1');
console.log(svg1);
console.log('style', svg1.style.width + 'x' + svg1.style.height);
console.log('client', svg1.clientWidth + 'x' + svg1.clientHeight);
console.log('offset', svg1.offsetWidth + 'x' + svg1.offsetHeight);
CSS
#svg1 {
width: 300px;
height: 100px;
}
Use the getBBox function:
The SVGGraphicsElement.getBBox() method allows us to determine the coordinates of the smallest rectangle in which the object fits. [...]
http://jsfiddle.net/Xkv3X/1/
var bBox = svg1.getBBox();
console.log('XxY', bBox.x + 'x' + bBox.y);
console.log('size', bBox.width + 'x' + bBox.height);
FireFox have problemes for getBBox(), i need to do this in vanillaJS.
I've a better Way and is the same result as real svg.getBBox() function !
With this good post : Get the real size of a SVG/G element
var el = document.getElementById("yourElement"); // or other selector like querySelector()
var rect = el.getBoundingClientRect(); // get the bounding rectangle
console.log( rect.width );
console.log( rect.height);
I'm using Firefox, and my working solution is very close to obysky. The only difference is that the method you call in an svg element will return multiple rects and you need to select the first one.
var chart = document.getElementsByClassName("chart")[0];
var width = chart.getClientRects()[0].width;
var height = chart.getClientRects()[0].height;
SVG has properties width and height. They return an object SVGAnimatedLength with two properties: animVal and baseVal. This interface is used for animation, where baseVal is the value before animation. From what I can see, this method returns consistent values in both Chrome and Firefox, so I think it can also be used to get calculated size of SVG.
This is the consistent cross-browser way I found:
var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
var svgCalculateSize = function (el) {
var gCS = window.getComputedStyle(el), // using gCS because IE8- has no support for svg anyway
bounds = {
width: 0,
height: 0
};
heightComponents.forEach(function (css) {
bounds.height += parseFloat(gCS[css]);
});
widthComponents.forEach(function (css) {
bounds.width += parseFloat(gCS[css]);
});
return bounds;
};
From Firefox 33 onwards you can call getBoundingClientRect() and it will work normally, i.e. in the question above it will return 300 x 100.
Firefox 33 will be released on 14th October 2014 but the fix is already in Firefox nightlies if you want to try it out.
Use .getAttribute()!
var height = document.getElementById('rect').getAttribute("height");
var width = document.getElementById('rect').getAttribute("width");
var x = document.getElementById('rect').getAttribute("x");
alert("height: " + height + ", width: " + width + ", x: " + x);
<svg width="500" height="500">
<rect width="300" height="100" x="50" y="50" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" id="rect"/>
</svg>
A save method to determine the width and height unit of any element (no padding, no margin) is the following:
let div = document.querySelector("div");
let style = getComputedStyle(div);
let width = parseFloat(style.width.replace("px", ""));
let height = parseFloat(style.height.replace("px", ""));