I am working with a svg element and I want to animate the height increasing upwards of these elements. But they are growing downward from the top probably due to SVG coordinate system. How can I make it go upwards from the bottom?
window.onload = function () {
var x1 = document.querySelector('svg').viewBox.baseVal.height;
var a = document.getElementById('wrapper1');
var bgArray = [];
for (var i = 1; i < a.children.length; i++) {
bgArray.push((a.children[i].getBBox().height / x1) * 100);
};
bgArray.forEach((x, i) => a.children[i + 1].style.setProperty("--h1", x + '%'));
bgArray.forEach((x, i) => a.children[i + 1].style.setProperty("--del", (i + 1) + 's')); //for staggered
}
.r1,
.r2 {
visibility: hidden;
animation: moveHeight 2s ease-in var(--del) 1 forwards;
}
#keyframes moveHeight {
0% {
visibility: visible;
height: 0%;
}
100% {
visibility: visible;
height: var(--h1);
}
}
<!DOCTYPE html>
<html>
<body>
<link rel="stylesheet" href="style.css">
</link>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
<g class="wrapper1" id="wrapper1" >
<rect x="10" y="20" width="120" height="120" stroke="black" fill="none"></rect>
<rect class="r1" id="r1" x="10" y="80" width="20" height="60" stroke="none" fill="orange"></rect>
<rect class="r2" id="r2" x="31" y="100" width="20" height="40" stroke="none" fill="green"></rect>
<!---->
</g>
</svg>
<script src="index.js"></script>
</body>
</html>
You could draw the bars but cover them and then gradually uncover them.
This covers them by adding white bars and shrinking those.
window.onload = function() {
var x1 = document.querySelector('svg').viewBox.baseVal.height;
var a = document.getElementById('wrapper1');
var bgArray = [];
for (var i = 1; i < a.children.length; i++) {
bgArray.push((a.children[i].getBBox().height / x1) * 100);
};
bgArray.forEach((x, i) => a.children[i + 1].style.setProperty("--h1", x + '%'));
bgArray.forEach((x, i) => a.children[i + 1].style.setProperty("--del", (i + 1) + 's')); //for staggered
}
.shrink {
animation: moveHeight 2s ease-in var(--del) 1 forwards;
}
#keyframes moveHeight {
0% {
height: var(--h1);
}
100% {
height: 0;
}
}
<!DOCTYPE html>
<html>
<body>
<link rel="stylesheet" href="style.css">
</link>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
<g class="wrapper1" id="wrapper1" >
<rect x="10" y="20" width="120" height="120" stroke="black" fill="none"></rect>
<rect class="r1" id="r1" x="10" y="80" width="20" height="60" stroke="none" fill="orange"></rect>
<rect class="r1 shrink" x="10" y="80" width="20" height="60" stroke="none" fill="white"></rect>
<rect class="r2" id="r2" x="31" y="100" width="20" height="40" stroke="none" fill="green"></rect>
<rect class="r2 shrink" x="30" y="99" width="22" height="41" stroke="none" fill="white"></rect>
<!---->
</g>
</svg>
<script src="index.js"></script>
</body>
</html>
Note, at some zoom levels a screen pixel of the underneath bars can be left showing (giving a faint vertical line at the start). To get round that the second white bar has been moved one left and made one wider (and similarly for height). Somewhat hacky so I hope someone can come up with a more straightforward solution.
I have looked into this brilliant answer but I had trouble visualizing and adapting it to my scenario. So after I figured out, I am trying to visually explain and show how to adapt to different scenarios.
The basic markup is following
<!DOCTYPE html>
<html>
<body>
<link rel="stylesheet" href="style.css">
</link>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
<rect class="wrapper1" id="wrapper1" x="10" y="345" width="120" height="180" stroke="teal" fill="none"></rect>
<line class="upper clone" x1="10" y1="80" x2="220" y2="80" style="stroke:rgb(110, 31, 194);stroke-width:1" />
<g class="elements">
<line class="upper" x1="10" y1="80" x2="220" y2="80" style="stroke:rgb(87, 202, 91);stroke-width:1"/>
<rect class="r0" id="r0" x="10" y="80" width="83" height="180" stroke="brown" fill="none"></rect>
<rect class="r1" id="r1" x="10" y="80" width="20" height="60" stroke="none" fill="orange"></rect>
<rect class="r2" id="r2" x="31" y="80" width="20" height="10" stroke="none" fill="green"></rect>
<rect class="r3" id="r3" x="52" y="80" width="20" height="100" stroke="none" fill="blue"></rect>
<rect class="r4" id="r4" x="73" y="80" width="20" height="140" stroke="none" fill="magenta"></rect>
<line class="lower" x1="10" y1="260" x2="220" y2="260" style="stroke:rgb(87, 202, 91);stroke-width:1"/>
</g>
<line class="lower clone" x1="10" y1="260" x2="220" y2="260" style="stroke:rgb(218, 149, 22);stroke-width:1" />
<g class="wrapper2" id="wrapper2">
<rect x="140" y="0" width="50" height="700" stroke="red" fill="none"></rect>
</g>
</svg>
<script src="index2.js"></script>
</body>
</html>
For ease of understanding, I have created the two lines called upper and lower which are the start and end of all the rects. I have also cloned those lines where the transform will not apply.
The thumb rule of converting svg coordinate system to cartesian coordinate is to
use this transform="translate(0, maxY) scale(1, -1)".
The maxY is actually max total of (y+height). In this case it is 260. now this value can be manipulated to correctly place the rects prior to animation
<g class="elements" transform="translate(0,260) scale(1, -1)"> positions
<g class="elements" transform="translate(0,340) scale(1, -1)"> 260+rectY(80)=340 positions
<g class="elements" transform="translate(0,425) scale(1, -1)"> 260+rectY(80)+(wrapperY 345 -260) positions
and lastly positions at the bottom of the wrapper
<g class="elements" transform="translate(0,605) scale(1, -1)"> 260+rectY(80)+(wrapperY 345 -260)+wrapperHeight 180 = 605
Putting everything together with javascript and css
//converting svg coordinates to cartesian coordinates
// transform="translate(0, maxY) scale(1, -1)".
// or transform="scale(1, -1) translate(0, -maxY)".
window.onload = function app() {
var bgArray1 = []; //to collect only height
var bgArray2 = []; //to collect height + y
var wrapperElement = document.getElementById('wrapper1');
var wrapperY = wrapperElement.getBBox().y //what is the y coordinate of wrapper rect
var wrapperHeight = wrapperElement.getBBox().height;
var getRect = document.querySelectorAll('[class^="r"]');
for (var i = 0; i < getRect.length; i++) {
var _bBox = getRect[i].getBBox(); // getting the bBox for each rect
var _height = _bBox.height + _bBox.y //getting each rect's height
var _y = _bBox.y // getting each rect's y coordinate
var _heightAndY = _height + _y //total of Y+height of each rect
bgArray1.push(_height);
bgArray2.push(_heightAndY);
}
var _maxHeight = Math.max(...bgArray1); // use this if you want to position the rect at (y=0)
var _maxTotal = Math.max(...bgArray2); // use this if you want to position the rect at (y=0)
var _wrapperTop = _maxTotal + (wrapperY - _maxHeight); //use this if you want to postion the rect at the the
//top of the wrapper
var _wrapperBottom = _wrapperTop + wrapperHeight //use this if you wantto position the rects at the
//bottom of the wrapper
bgArray1.forEach((x, i) => getRect[i].setAttribute("transform", `translate(0,${_wrapperBottom}) scale(1,-1)`));
bgArray1.forEach((x, i) => getRect[i].style.setProperty("--del", (i + 1) + 's'))
}
[class^="r"] {
visibility: hidden;
animation: moveHeight 2s ease-out var(--del) 1 forwards;
}
#keyframes moveHeight {
0% {
visibility: visible;
height: 0
}
100% {
visibility: visible;
height: 100;
}
}
<!DOCTYPE html>
<html>
<body>
<link rel="stylesheet" href="style.css">
</link>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
<rect class="wrapper1" id="wrapper1" x="10" y="345" width="120" height="180" stroke="teal" fill="none"></rect>
<line class="upper clone" x1="10" y1="80" x2="220" y2="80" style="stroke:rgb(110, 31, 194);stroke-width:1" />
<g>
<line class="upper" x1="10" y1="80" x2="220" y2="80" style="stroke:rgb(87, 202, 91);stroke-width:1"/>
<rect class="r0" id="r0" x="10" y="80" width="83" height="180" stroke="brown" fill="none"></rect>
<rect class="r1" id="r1" x="10" y="80" width="20" height="60" stroke="none" fill="orange"></rect>
<rect class="r2" id="r2" x="31" y="80" width="20" height="10" stroke="none" fill="green"></rect>
<rect class="r3" id="r3" x="52" y="80" width="20" height="100" stroke="none" fill="blue"></rect>
<rect class="r4" id="r4" x="73" y="80" width="20" height="140" stroke="none" fill="magenta"></rect>
<line class="lower" x1="10" y1="260" x2="220" y2="260" style="stroke:rgb(87, 202, 91);stroke-width:1"/>
</g>
<line class="lower clone" x1="10" y1="260" x2="220" y2="260" style="stroke:rgb(218, 149, 22);stroke-width:1" />
<g class="wrapper2" id="wrapper2">
<rect x="140" y="0" width="50" height="700" stroke="red" fill="none"></rect>
</g>
</svg>
<!--<script src="index.js"></script>-->
<script src="index2.js"></script>
</body>
</html>
Pure svg solution
The idea is to first hide the rectangles behind a masking strip.
Then raise them to the top by animating the y attribute of the rectangle
<animate id="anR1" xlink:href="#r1" attributeName="y" begin="Layer_1.click"
dur="1s" from="140" to="80" repeatCount="1" fill="freeze" />
<!DOCTYPE html>
<html>
<body>
<link rel="stylesheet" href="style.css">
</link>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
<g class="wrapper1" id="wrapper1" >
<rect x="10" y="20" width="120" height="120" stroke="black" fill="none"></rect>
<rect class="r1" id="r1" x="10" y="140" width="20" height="60" stroke="none" fill="orange"></rect>
<rect class="r2" id="r2" x="31" y="140" width="20" height="40" stroke="none" fill="green"></rect>
<rect class="r3" id="r3" x="52" y="140" width="20" height="80" stroke="none" fill="dodgerblue"></rect>
<!-- masking strip -->
<rect x="10" y="140" width="120" height="120" fill="white"></rect>
</g>
<animate id="anR1" xlink:href="#r1" attributeName="y" begin="Layer_1.click" dur="0.5s" from="140" to="80" repeatCount="1" fill="freeze" />
<animate id="anR2" xlink:href="#r2" attributeName="y" begin="anR1.end+0.25s" dur="0.5s" from="140" to="100" repeatCount="1" fill="freeze" />
<animate id="anR3" xlink:href="#r3" attributeName="y" begin="anR2.end+0.25s" dur="0.5s" from="140" to="60" repeatCount="1" fill="freeze" />
</svg>
<script src="index.js"></script>
</body>
</html>
I am making and animation the objetive is change the xlink:href inside a SVG. (this is for change a shape), and change class respect to their position inside.
This is my SVG
<svg viewBox="-20 -20 600 200" id="main">
<defs id="test">
<rect width="80" height="80" id="circle" fill="red" class="first" />
<rect width="80" height="80" id="square" fill="pink" class="second" />
<rect width="80" height="80" id="cross" fill="blue" class="third" />
</defs>
<g id="load-area">
<use x="0" xlink:href="#circle" />
<use x="100" xlink:href="#square" />
<use x="200" xlink:href="#cross" />
</g>
</svg>
The class in every rectelement, has a different animation-delay according to position (first execute at 0s, second at 2s, third at 4s and so on).
With JS I change every <use> at #load-area
main.children['load-area'].children[0].setAttribute("xlink:href", getFigure(random()));
And it works, the shape changes but, suppose when it gets three times the id #cross then all elements have third CSS class.
I need change CSS class inside every children of <use>, How can I do that?
Below an element tree :
I get all <use> with: main.children['load-area'].children but it does not have child element, as I show u below:
You can solve this using CSS variables that you combine with nth-child selector and you no more need the classes.
Here is a basic example
rect {
animation:change 3s var(--d,0s) infinite;
}
#keyframes change {
0% {
opacity:1;
}
33%,100% {
opacity:0;
}
}
#load-area > use:nth-child(1) {--d:0s}
#load-area > use:nth-child(2) {--d:1s}
#load-area > use:nth-child(3) {--d:2s}
/*#load-area > use:nth-child(N) {--d:Xs}*/
<svg viewBox="-20 -20 600 200" id="main">
<defs id="test">
<rect width="80" height="80" id="circle" fill="red" />
<rect width="80" height="80" id="square" fill="pink" />
<rect width="80" height="80" id="cross" fill="blue" />
</defs>
<g id="load-area">
<use x="0" xlink:href="#circle" />
<use x="100" xlink:href="#square" />
<use x="200" xlink:href="#cross" />
</g>
</svg>
<svg viewBox="-20 -20 600 200" id="main">
<g id="load-area">
<use x="0" xlink:href="#square" />
<use x="100" xlink:href="#circle" />
<use x="200" xlink:href="#cross" />
</g>
</svg>
If the number is unknown or very big you can easily use a JS loop:
var e = document.querySelectorAll('#load-area use');
for(var i=0;i<e.length;i++) {
e[i].style.setProperty('--d',i+"s");
}
rect {
animation:change 3s var(--d,0s) infinite;
}
#keyframes change {
0% {
opacity:1;
}
33%,100% {
opacity:0;
}
}
<svg viewBox="-20 -20 600 200" id="main">
<defs id="test">
<rect width="80" height="80" id="circle" fill="red" />
<rect width="80" height="80" id="square" fill="pink" />
<rect width="80" height="80" id="cross" fill="blue" />
</defs>
<g id="load-area">
<use x="0" xlink:href="#circle" />
<use x="100" xlink:href="#square" />
<use x="200" xlink:href="#cross" />
</g>
</svg>
document.getElementsByTagName("use")[0].setAttribute("xlink:href", "#circle");
element.setAttribute(attributename, attributevalue)
Here is how you can change element attributes
I have to build this SVG and right now I am facing the issue is that it wasn't fitting on my page. I have to put this SVG in the column of my table and size of the column is fixed but my SVG is not fitting properly under that.
It should fit in any column of the table and the size should be also perfectly fitted on that column.
Size of the SVG should be dynamic.
const SVG_NS = 'http://www.w3.org/2000/svg';
let colors = [
"rgb(255,0,0)",
"rgb(255,0,0)",
"rgb(255,128,0)",
"rgb(255,128,0)",
"rgb(255,128,0)",
"rgb(255,128,0)",
"rgb(255,128,0)",
"rgb(102,204,0)",
"rgb(102,204,0)",
"rgb(102,178,255)"
];
let angle = Math.atan2(215,430);
let n = 0;
let offset = 10;// distance between the border of the triangle and the start of the rectangle
for(let y = 40; y < 430; y+= 40){
let halfW = Math.tan(angle)*y - offset;
let o = {
x:430/2 - halfW,
y: y,
width: 2*halfW,
height: 30,
fill:colors[n]
}
drawRect(o, polys);
n++;
}
function drawRect(o, parent) {
let rect = document.createElementNS(SVG_NS, 'rect');
for (var name in o) {
if (o.hasOwnProperty(name)) {
rect.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(rect);
return rect;
}
.boxed {
border: 1px solid black ;
width: 500px;
height: 130px;
box-sizing: border-box;
padding: 40px;
<svg width="600" height="500">
<g transform="translate(0,40)">
<rect width="360" height="30" style="fill:rgb(255,0,0);" />
</g>
<g transform="translate(0,80)">
<rect width="350" height="30" style="fill:rgb(255,0,0);" />
</g>
<g transform="translate(0,120)">
<rect width="350" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,160)">
<rect width="300" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,200)">
<rect width="300" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,240)">
<rect width="300" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,280)">
<rect width="300" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,320)">
<rect width="300" height="30" style="fill:rgb(102,204,0);" />
</g>
<g transform="translate(0,360)">
<rect width="300" height="30" style="fill:rgb(102,204,0);" />
</g>
<g transform="translate(0,400)">
<rect width="300" height="30" style="fill:rgb(102,178,255);" />
</g>
<svg id="polys" height="500" width="500" viewBox="0 0 500 500" x="160" >
<polygon points="215,0, 0,440 430,440 215,0" style="fill:rgb(255,255,204);stroke:black;stroke-width:2" />
</svg>
</svg>
You need to change the width and height of the svg element with viewBox. This way the SVG element will take all the available space inside the parent container. However if your container is 500px wide and 130px height, the SVG will overflow the container.
const SVG_NS = 'http://www.w3.org/2000/svg';
let colors = [
"rgb(255,0,0)",
"rgb(255,0,0)",
"rgb(255,128,0)",
"rgb(255,128,0)",
"rgb(255,128,0)",
"rgb(255,128,0)",
"rgb(255,128,0)",
"rgb(102,204,0)",
"rgb(102,204,0)",
"rgb(102,178,255)"
];
let angle = Math.atan2(215,430);
let n = 0;
let offset = 10;// distance between the border of the triangle and the start of the rectangle
for(let y = 40; y < 430; y+= 40){
let halfW = Math.tan(angle)*y - offset;
let o = {
x:430/2 - halfW,
y: y,
width: 2*halfW,
height: 30,
fill:colors[n]
}
drawRect(o, polys);
n++;
}
function drawRect(o, parent) {
let rect = document.createElementNS(SVG_NS, 'rect');
for (var name in o) {
if (o.hasOwnProperty(name)) {
rect.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(rect);
return rect;
}
.boxed {
border: 1px solid black ;
width: 500px;
height: auto;
box-sizing: border-box;
padding: 40px;
}
svg{border:1px solid; display:block;}
<div class="boxed">
<svg viewBox="0 0 600 500" height="130" >
<g transform="translate(0,40)">
<rect width="360"viewBox="0 0 600 500" height="30" style="fill:rgb(255,0,0);" />
</g>
<g transform="translate(0,80)">
<rect width="350" height="30" style="fill:rgb(255,0,0);" />
</g>
<g transform="translate(0,120)">
<rect width="350" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,160)">
<rect width="300" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,200)">
<rect width="300" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,240)">
<rect width="300" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,280)">
<rect width="300" height="30" style="fill:rgb(255,128,0);" />
</g>
<g transform="translate(0,320)">
<rect width="300" height="30" style="fill:rgb(102,204,0);" />
</g>
<g transform="translate(0,360)">
<rect width="300" height="30" style="fill:rgb(102,204,0);" />
</g>
<g transform="translate(0,400)">
<rect width="300" height="30" style="fill:rgb(102,178,255);" />
</g>
<svg id="polys" height="500" width="500" viewBox="0 0 500 500" x="160" >
<polygon points="215,0, 0,440 430,440 215,0" style="fill:rgb(255,255,204);stroke:black;stroke-width:2" />
</svg>
</svg>
</div>
However if you try put
I have simple SVG chart like this:
<svg id="ss" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="chart" width="1000" height="300" >
<g>
<rect fill="#aaaaaa" stroke-width="0" stroke="none" height="1" width="1000" y="200" x="0" /> </g>
<g class="bar">
<rect height="81.858" width="30" y="118.142" x="10" />
</g>
<g class="bar">
<rect height="111.6012" width="30" y="88.3988" x="55" />
</g>
<g class="bar">
<rect height="66.98639999999999" width="30" y="133.0136" x="100" />
</g>
</svg>
How to use pure javascript to detect mouse over the rect element? And knowing which rect element is being over?
What's wrong with directly assigning the event?
var rects = document.querySelectorAll( 'rect' );
for( var rect of rects ) {
rect.addEventListener( 'mouseover', cb );
}
function cb(){
// the "this" object is your reference to the rect hovered over
console.log( this );
}
I'm using scrollmagic to tween svg clippaths as you scroll. Usually with svgs(or divs or sections or whatever) the last thing you specify in your html is on top in terms of z-index, but you change that in css. I tried to do this with my svgs, so that while the first svg tweens, the other one scrolls up behind it. It seems like scrollmagic is preventing my z-indexing from working though. Any ideas?
http://codepen.io/kathryncrawford/pen/BoXOMJ
<div id="scene">
<svg id="svg1" height="500" width="800">
<image id="img1" xlink:href="http://placecage.com/800/500" x="0" y="0" width="800" height="500"/>
<defs>
<clipPath id="clip1">
<circle id="circle1" stroke="#000000" stroke-miterlimit="10" cx="400" cy="300" r="300" />
</clipPath>
</defs>
</svg>
</div>
<div id="scene2">
<svg id="svg2" height="500" width="800">
<image id="img2" xlink:href="http://fillmurray.com/800/500" x="0" y="0" width="800" height="500"/>
<defs>
<clipPath id="clip2">
<circle id="circle2" stroke="#000000" stroke-miterlimit="10" cx="400" cy="300" r="300" />
</clipPath>
</defs>
</svg>
</div>
CSS
#img1 {
clip-path: url(#clip1);
}
#img2 {
clip-path: url(#clip2);
}
#svg1, #circle1{
z-index: 2;
}
#svg2, #circle2{
z-index: 1;
}
Crap, I literally just figured it out after posting this. I changed the z-index css declaration to the z-index on the scene divs that wrap the svgs. That worked.
http://codepen.io/kathryncrawford/pen/BoXOMJ
#scene{
z-index: 2;
}
#scene2{
z-index: 1;
}