I’m trying to get multiple headings on a Shopify homepage to be animated on a svg textPath. My code loads the headings and they each sit on their own svg curved path. However, only one heading is animated on scroll. How do I get each of the headings to be animated on scroll? Do I need multiple window.addEventListener(‘scroll’, onScroll-n) functions for each heading?
<script>
console.clear();
var textPath = document.querySelector('#text-path');
var textContainer = document.querySelector('#text-container');
var path = document.querySelector( textPath.getAttribute('href') );
var pathLength = path.getTotalLength();
console.log(pathLength);
function updateTextPathOffset(offset){
textPath.setAttribute('startOffset', offset);
}
updateTextPathOffset(pathLength);
function onScroll(){
requestAnimationFrame(function(){
var rect = textContainer.getBoundingClientRect();
var scrollPercent = rect.y / window.innerHeight;
console.log(scrollPercent);
updateTextPathOffset( scrollPercent * 2 * pathLength );
});
}
window.addEventListener('scroll',onScroll);
</script>
<svg id="text-container" viewBox="0 0 1000 200" xmlns="http://www.w3.org/2000/svg">
<path id="text-curve" d="M0 100s269.931 86.612 520 0c250.069-86.612 480 0 480 0" fill="none"/>
<text y="40">
<textPath id="text-path" href="#text-curve" startOffset="200">
{{ section.settings.title }}
</textPath>
</text>
</svg>
You can do the align animation with SVG SMIL:
https://developer.mozilla.org/en-US/docs/Web/SVG/SVG_animation_with_SMIL
Set the values dynamically from your Scroll Event
<svg width="450" height="180">
<path id="P1" pathLength="200" d="M0 100s270 87 520 0c250-87 480 0 480 0" fill="none" stroke-width="2" stroke="red"/>
<text>
<textPath id="P2" href="#P1">
TEXT
<animate
attributeName="startOffset"
attributeType="XML"
values="0;80"
keyTimes= "0;1"
dur="2s"
fill="freeze"
repeatCount="1"/>
</textPath>
</text>
<animateMotion>
<mpath href="#P2" />
</animateMotion>
</svg>
Related
So I have a client logo and I want to animate it on scroll. Let's say that the logo is DANIEL. As the user scrolls down the page i want the spacing between the letters to expand so it would end up being D A N I E L.
I have seen how to do this with regular text but this will be as I said an SVG logo. I have been searching around buy didn't find anything. Also needs to be mobile friendly. Any tips out there?
This is actually very simple. You just need to watch for scroll events, then update the <text> element based on how far down the page you have scrolled.
I've achieved the letter spacing using the letter-spacing presentation attribute.
window.addEventListener("scroll", function() {
document.getElementById("mytext").setAttribute("letter-spacing", (window.scrollY / window.outerHeight) + "em");
});
body {
height: 2000px;
}
svg {
position: fixed;
}
<svg viewBox="0 0 600 100">
<text id="mytext" x="300" y="70" font-size="50" text-anchor="middle">DANIEL</text>
</svg>
Update
For other non-text elements, like <path> etc, you will need to take a slightly different approah. You'll need to physically move them with a transform.
Here is a quick demo. You'll need to tweak it to get the objects to move where you want them.
var EXPAND_AMOUNT = 40;
window.addEventListener("scroll", function() {
var scrollPercent = window.scrollY / window.outerHeight;
document.getElementById("obj1").setAttribute("transform", "translate("+(scrollPercent * 3 * -EXPAND_AMOUNT)+",0)");
document.getElementById("obj2").setAttribute("transform", "translate("+(scrollPercent * 2 * -EXPAND_AMOUNT)+",0)");
document.getElementById("obj3").setAttribute("transform", "translate("+(scrollPercent * -EXPAND_AMOUNT)+",0)");
document.getElementById("obj4").setAttribute("transform", "translate("+(scrollPercent * EXPAND_AMOUNT)+",0)");
document.getElementById("obj5").setAttribute("transform", "translate("+(scrollPercent * 2 * EXPAND_AMOUNT)+",0)");
document.getElementById("obj6").setAttribute("transform", "translate("+(scrollPercent * 3 * EXPAND_AMOUNT)+",0)");
});
body {
height: 2000px;
}
svg {
position: fixed;
}
<svg viewBox="0 0 600 100">
<rect id="obj1" x="165" y="25" width="40" height="50"/>
<rect id="obj2" x="210" y="25" width="40" height="50"/>
<rect id="obj3" x="255" y="25" width="40" height="50"/>
<rect id="obj4" x="300" y="25" width="40" height="50"/>
<rect id="obj5" x="345" y="25" width="40" height="50"/>
<rect id="obj6" x="390" y="25" width="40" height="50"/>
</svg>
I'm having some difficulties with centering a piechart I created in D3 in it's parents div. I've set up a JS-fiddle right here: https://jsfiddle.net/86czgLnu/
The problem is that it overflows it's parents dimensions and I want it to scale to fit and be in the center of the parent. I found documentation about preserveAspectRatio and viewBox but wasn't able to fix it with those.
All the code is in the fiddle
Thanks for having a look.
Unfortunately, you can't use percentages inside translate. The best idea would be using JS (D3, in your case) to get the size of the container and translate the group appropriately, by half its width.
Here is your SVG with absolute values for the translate:
<div class="js-graph ct-chart">
<svg width="100%" height="100%">
<g transform="translate(50,50) scale(0.2)">
<path class="slice" fill="#8c564b" d="M1.3532347130578253e-14,-221A221,221,0,1,1,-119.28791713380294,186.04137396256502L0,0Z" original-title=""></path>
<path class="slice" fill="#c49c94" d="M-119.28791713380294,186.04137396256502A221,221,0,0,1,-210.51725450349846,-67.25686252204494L0,0Z"></path>
<path class="slice" fill="#e377c2" d="M-210.51725450349846,-67.25686252204494A221,221,0,0,1,-4.059704139173476e-14,-221L0,0Z"></path>
</g>
</svg>
</div>
An alternative to simulate a translate by percentage is using an inner SVG, as described in this answer by Robert Longson. However, it won't work, because the pie chart is drawn at the origin:
<div class="js-graph ct-chart">
<svg width="100%" height="100%">
<svg x="50%" y="50%">
<g transform="scale(0.2)">
<path class="slice" fill="#8c564b" d="M1.3532347130578253e-14,-221A221,221,0,1,1,-119.28791713380294,186.04137396256502L0,0Z" original-title=""></path>
<path class="slice" fill="#c49c94" d="M-119.28791713380294,186.04137396256502A221,221,0,0,1,-210.51725450349846,-67.25686252204494L0,0Z"></path>
<path class="slice" fill="#e377c2" d="M-210.51725450349846,-67.25686252204494A221,221,0,0,1,-4.059704139173476e-14,-221L0,0Z"></path>
</g>
</svg>
</svg>
</div>
PS: scale is part of the "transform" attribute.
In its simplest form, I want to make a loading page like this website.
I want to use a custom SVG logo (that I have made in illustrator) and horizontally fill the logo as the page loads.
Like a SVG clip mask progress bar (or something).
Please, please help me!
Thanks, Jon
The simplest way to do this is with a gradient fill.
<linearGradient id="progress">
<stop id="stop1" offset="0" stop-color="black"/>
<stop id="stop2" offset="0" stop-color="grey"/>
</linearGradient>
You just need to change the value of offset on both <stop> elements to be the percentage you want - either 0->1 or "0%"->"100%". For example:
An example function might be:
function setProgress(amt)
{
amt = (amt < 0) ? 0 : (amt > 1) ? 1 : amt;
document.getElementById("stop1").setAttribute("offset", amt);
document.getElementById("stop2").setAttribute("offset", amt);
}
This approach works for any SVG element, whether it be text, as in the demo below, or a complicated logo made of paths.
function setProgress(amt)
{
amt = (amt < 0) ? 0 : (amt > 1) ? 1 : amt;
document.getElementById("stop1").setAttribute("offset", amt);
document.getElementById("stop2").setAttribute("offset", amt);
}
// Simple test of setProgress().
// We step up from 0 to 1 using timeouts
val = 0;
doTimeout();
function doTimeout() {
setProgress(val);
val += 0.01;
if (val <= 1) {
setTimeout(doTimeout, 30);
}
}
text {
font: 'Times Roman', serif;
font-size: 125px;
fill: url(#progress);
}
<svg width="650" height="150">
<defs>
<linearGradient id="progress">
<stop id="stop1" offset="0" stop-color="black"/>
<stop id="stop2" offset="0" stop-color="grey"/>
</linearGradient>
</defs>
<text x="20" y="120">TILL JANZ</text>
</svg>
Could anyone suggest what is the best way to implement frame-based animation in svg, based on JPEG's?
One example which I've found is this:
<svg version="1.1" baseProfile="tiny" id="svg-root"
width="100%" height="100%" viewBox="0 0 480 360"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="320" height="240" xlink:href="test1.jpg">
<animate id='frame_0' attributeName='display' values='inline;none'
dur='0.5s' fill='freeze' begin="0s" repeatCount="indefinite"/>
</image>
<image width="320" height="240" xlink:href="test2.jpg">
<animate id='frame_1' attributeName='display' values='none;inline'
dur='0.5s' fill='freeze' begin="0.5s" repeatCount="indefinite" />
</image>
</svg>
It works for 2 frames, but doesn't really scale. I would like to have something which can handle 100 frames and more.
It's much easier:
<svg version="1.1" baseProfile="tiny" id="svg-root"
width="100%" height="100%" viewBox="0 0 480 360"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="320" height="240" xlink:href="test1.jpg">
<animate attributeName="xlink:href"
values="test1.jpg;test2.jpg"
begin="0s" repeatCount="indefinite" dur="1s"/>
</image>
</svg>
An alternative approach,
If your animation is working, but it's just a matter of too much production to get the files setup, you can use a template to generate your SVG.
Use something like Grunt.Js to read all the images in a directory and then, have an underscore template build the SVG frames the way you already have them setup from an array of the filepaths.
These code snippets might not work out of the box, but it's pretty close.
Here the grunt file grabs the files in the folder, check if they're images then pushes them to an array.
// gruntfile.js //
var fs = require('fs');
var path = require('path');
var _ = require("underscore");
grunt.registerTask('Build Animated SVG', function () {
var template = grunt.file.read("/path to SVG underscore template/"); //see SVG section below.
var frames = [];
var pathtoframes = "mypath";
var mySVG = "mysvg.svg";
fs.readdirSync(path.resolve(pathtoframes)).forEach(function (file) {
if (filetype == "svg" || filetype == "png" || filetype == "jpg" || filetype == "gif") {
var frame = {};
frame.src = pathtoframes + "/" + file;
frames.push(frame);
}
});
var compiledSVG = _.template(template, {frames:frames});
grunt.file.write(path.resolve(pathtoframes) + "/compiled_" + mySVG, compiledSVG);
});
This template will get read in by the grunt file, and underscore will loop through each file and write that into a big string. Grunt then saves that out as an SVG that you can load.
<!-- SVG underscore template -->
<svg version="1.1" baseProfile="tiny" id="svg-root"
width="100%" height="100%" viewBox="0 0 480 360"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<% _.each(frames, function(frame, index){ %>
<image width="320" height="240" xlink:href="<%= frame.src %>">
<animate
id='frame_<%= index %>'
attributeName='display'
values='none;inline'
dur='0.5s'
fill='freeze'
begin="0.5s"
repeatCount="indefinite"
/>
</image>
<% } %>
</svg>
https://michaelsboost.github.io/SVGAnimFrames/
You can easily use my library SVGAnimFrames for this. Simply by calling 1 line of code...
SVGAnimFrames("#bounce svg", "repeat", "40", "0");
Refer to the Github repo to learn how to use it.
Ideally your best bet is to use a spritesheet and animate frame by frame with that which minimizes unnecessary http requests.
The newest:
I find the question is the opera! It can work in the firefox!But I need opera to use it,does it have any solution? PS:waiting the jquery or opera's uncoming version?
I want to ask why $("g[id^=trans]") cannot work,but the $("g") can?How to fix it?
The load method of jQuery SVG plugin must work under the server such as Apache.(I don't know why?But it will not work if not under the Apache)
The javascript
<html><head><script type="text/javascript" src="http://code.jquery.com/jquery.js">
</script><script type="text/javascript" src="http://keith-wood.name/js/jquery.svg.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#svgload").svg();
var svg = $('#svgload').svg('get');
$('#loadsvg').click(function() {
svg.load('red.svg',{onLoad:loadDone});
});function loadDone(svg, error) {
svg.text(10, 20, error || 'Loaded into ' + this.id);
}
$('#transform').click(function(){
//alert($("g").attr("transform"));
alert("before="+svg.toSVG());
$("g[id^=trans]").attr("transform","translate(20,5)");//this cannot work!How to fix it?
//$("g").attr("transform","translate(20,5)");//this can work!
alert("after="+svg.toSVG());
});
});</script></head><body>
<div id="svgload" width="200px" height="200px" >
</div>
<button id="loadsvg" type="button">loadsvg</button>
<button id="transform" type="button">transform</button></body></html>`
The SVG:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<g id="trans001">
<rect x="35" y="50" width="20" height="20" fill="#F3D70A" stroke-width="4"/>
<line x1="20" y1="40" x2="80" y2="40" stroke-width="5"/>
<circle cx="20" cy="60" r="10" fill="red" stroke="blue" stroke-width="5"/></g>
<g id="trans002"><rect width="35" height="20" x="20" y="90" fill="blue">
</rect></g>
</svg>
Your jQuery selector is wrong - try:
$('g[id^="trans"]').attr("transform","translate(20,5)");
Note that the quotes on the attribute value are mandatory (http://api.jquery.com/attribute-starts-with-selector/)