Scaling an SVG with complex animation paths - javascript

I have this code for an SVG on my page. It's a track and I have a car that scrolls along the path as you go down the page.
<div class="my-svg-container">
<svg viewBox="-220 -219 1250 2212.5" style="
position: absolute;
" class="my-svg" preserveAspectRatio="xMinYMin meet"><defs>
<style>.cls-1,.cls-10,.cls-11,.cls-12,.cls-13,.cls-14,.cls-15,.cls-16,.cls-17,.cls-3,.cls-4,.cls-5,.cls-6,.cls-7,.cls-8,.cls-9{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{stroke:#2d2d2d;stroke-width:100px;}.cls-10,.cls-11,.cls-12,.cls-13,.cls-14,.cls-15,.cls-16,.cls-17,.cls-4,.cls-5,.cls-6,.cls-7,.cls-8,.cls-9{stroke:#fad000;stroke-width:9px;}.cls-4{stroke-dasharray:12 11.54;}.cls-5{stroke-dasharray:12 10.64;}.cls-6{stroke-dasharray:12 12.26;}.cls-7{stroke-dasharray:12 13.27;}.cls-8{stroke-dasharray:12 12.67;}.cls-9{stroke-dasharray:12 10.99;}.cls-10{stroke-dasharray:12 12.24;}.cls-11{stroke-dasharray:12 13.42;}.cls-12{stroke-dasharray:12 12.63;}.cls-13{stroke-dasharray:12 10.47;}.cls-14{stroke-dasharray:12 11.73;}.cls-15{stroke-dasharray:12 11.95;}.cls-16{stroke-dasharray:12 12.17;}</style>
<clipPath id="clip-path" transform="translate(4349 2519.5)"><rect class="cls-1" x="-4349" y="-2519.5" width="868.18" height="1964.5"></rect></clipPath></defs><title>Road(whole)</title>
<g class="cls-2">
<path id="theMotionPath" class="cls-3" d="M709.08 0.54v306s7,91-106,91h-461s-92-8-92,96v444s-6,78,64,78h606s98-10,98,90v394s5,84-84,84h-522s-90-3-90,90v290" ></path>
<line class="cls-4" x1="709.08" y1="17.54" x2="709.08" y2="294.23"></line>
<path class="cls-5" d="M-3640.93-2196.91c-3.89,25-21,69.87-93.68,74.09" transform="translate(4349 2519.5)"></path>
<line class="cls-6" x1="584.81" y1="397" x2="154.21" y2="397"></line>
<path class="cls-7" d="M-4226.14-2121.52c-25.35,3.77-67.35,19.56-72.3,82.4" transform="translate(4349 2519.5)"></path>
<line class="cls-8" x1="50.08" y1="511.67" x2="50.08" y2="924.67"></line>
<path class="cls-9" d="M-4298.32-1565.54c2.45,21,12.58,54.8,51.94,60.28" transform="translate(4349 2519.5)"></path>
<line class="cls-10" x1="132.32" y1="1015" x2="707.96" y2="1015"></line>
<path class="cls-11" d="M-3609.53-1504.15c26.79,2.72,72.47,16.34,78,77" transform="translate(4349 2519.5)"></path>
<line class="cls-12" x1="818.08" y1="1123.63" x2="818.08" y2="1486.69"></line>
<path class="cls-13" d="M-3532-1004.08c-3.44,22.94-17.31,62.35-71.73,67.11" transform="translate(4349 2519.5)"></path>
<line class="cls-14" x1="716.35" y1="1583" x2="223.99" y2="1583"></line>
<path class="cls-15" d="M-4154.73-934.92c-24.59,4.26-66.45,19.86-71.61,76.46" transform="translate(4349 2519.5)"></path>
<line class="cls-16" x1="122.13" y1="1691.17" x2="122.13" y2="1950.92"></line>
<path class="cls-17" d="M-4226.87-562.5
v6m.13-296
q-.14,2.93-.13,6
v6
m96-96
h-6
s-2.23-.07-6,.16
m533.95-.29q-2.92.14-6,.13
h-6
m90-90
v6
s.13,2.24,0,6
m-.09-406
c.08,2,.12,4,.12,6
v6
m-104-96
h6
s2.22-.23,6-.27
m-618,.07
c1.94.13,3.93.2,6,.2h6m-70-84v6s-.18,2.26-.12,6m.22-456
q-.11,2.94-.1,6v6m98-102h-6s-2.23-.19-6-.13m473,0q-2.94.09-6,.09
h-6m112-97v6a57.48,57.48,0,0,1,0,6m0-312v6" transform="translate(4349 2519.5)"></path></g>
<g class="shakyimage">
<image speed="2" width="5%" height="5%" x="-65" y="-30" xlink:href="https://cdn3.iconfinder.com/data/icons/cars-28/512/Car_5-512.png" id="dot" ></image>
</g>
<g >
<image width="45%" height="11%" x="423" y="-100" xlink:href="https://cdn0.iconfinder.com/data/icons/gpsmapicons/red/gpsmapicons07.png">
</g>
<g >
<image width="30%" height="10%" x="-66" y="1782.17" xlink:href="https://cdn0.iconfinder.com/data/icons/gpsmapicons/red/gpsmapicons07.png">
</g>
</svg>
</div>
This functions very well as-is. My main issue is I have built the page to align with the track. This means that the end of the track perfectly aligns with the last section of the page. The problem is, when you make the page smaller the track shrinks in size.
Currently I have tried doing this:
.my-svg-container{
display: inline-block;
position: relative;
width: 100%;
padding-bottom: 38.5%; /* depends on svg ratio, for my zebra height/width = 1.2 so padding-bottom = 50% * 1.2 = 60% */
vertical-align: middle; /* top | middle | bottom ... do what you want */
}
.my-svg{ /* svg into : object, img or inline */
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%; /* only required for <img /> */
}
However this seems to have no effect on the responsiveness.
So is it possible to basically make this scale, but retain its position on the page? The height seems to be the main issue; positioning seems to be intact, but the height shrinks so everything isn't aligned any more.
Thanks! Let me know if you need more info

You are scaling the SVG image proportionally, which means that when the width changes (to adapt to the page width), the height must also change to keep the image proportions.
In part this is what you want, because scaling the image disregarding proportions would cause your traffic sign or the car to become stretched. On the other hand you want to keep the road height fixed, so here are your alternatives:
Alternative 1 — don't scale the image
This may seem counter to what you're looking for but it's definitely worth considering. You're not scaling the text around which the image revolves, so why scaling the image at all? Just keep both centered with constant size.
Alternative 2 — scale parts of your SVG separately
You can split your SVG into the parts you want with fixed proportion (like the traffic sign and vertical parts of roads) and variable proportion (like horizontal roads). You can use the preserveAspectRatio and viewBox attributes to. There's an example here (works on Chrome but not on current Firefox).
Alternative 3 — scale in discrete steps
If you really want the image to become bigger on a larger display, but don't feel like going through the complexity of alternative 2, you can prepare several SVGs for several width / height ratios and use media queries to display the appropriate one for the appropriate width interval.

Try changing your SVG to
preserveAspectRatio="xMinYMin slice"
It should mean that the SVG stays the same height as your content, but that the right hand side of the SVG gets clipped off as the page gets narrower.
If you want both sides (left and right) to get clipped off at the same rate, so the SVG remains centred, then use:
preserveAspectRatio="xMidYMin slice"

Related

Blur a specific portion of an HTML img element strictly via CSS (or JS + SVG)?

Say we have a single image tag, and we need to blur a specific part of it (for example, for censorship purposes). In the example, we'll say our target image is 100px by 100px, and we want to censor the rectangle at x=25, y=40 with width=20, height=35. How can this be done using CSS?
This is straightforward if you have a parent container, as you can simply render an offset portion of the same image, with a clip or a backdrop-filter applied. However, this is not reliable for my use case, as I cannot guarantee the positioning attributes of the parent and the image itself, so I may not be positioning the element correctly.
<div id="parent">
<img src="/path/to/img.png">
<!-- This -->
<img
id="blurred"
src="/path/to/img.png"
style="clip: rect(25, 40, 20, 35); filter: blur(8px);"
/>
<!-- Or this -->
<div style="left: 25px; top: 40px; width: 20px; height: 35px; backdrop-filter: blur(8px);" />
</div>
On the other hand, if you just have the image, with no access to the parent, CSS might be the only way. There is clip-path and mask.
With clip-path, you're removing everything outside the path, but we want to do the opposite by blurring everything inside the path. You can finesse the path clip the target "portion" while leaving the remainder of the image, but this doesn't seem to support the filter, you're left with an empty white block where you want your blur to be (I haven't had much luck figuring out how to apply the blur here). While this achieves the censorship purpose, it doesn't achieve the UX desired with a blur.
<!-- Reference SVG clip path -->
<svg height="100%" width="100%" viewBox="0 0 100 100">
<defs>
<clipPath id="clip">
<path d="M 0,0 l 100,0 0,100 -100,0 Z M 25,40 l 20,0 0,35 -20,0 Z" />
</clipPath>
</defs>
</svg>
<!-- Clipped image -->
<img src="/path/to/img.png" style="clip-path: url(#clip);" />
A mask has a similar constraint, as we are drawing the image through the mask, which means that we can create a mask of the filtered and blurred parts of the image, and "draw those through the mask" onto the target image. However, this more or less defeats the purpose, as we need to re-render the image in the SVG tag separately, and is brittle if the user resizes the page / image, for example.
Appreciate any suggestions. This has had me stumped for a few days now.

how to create d3 radial with dynamic radios

I created a radial with two tiers of options. I did in a way that isn't really dynamic and isn't really responsive to screen size. I now need it to be both of those things. Here is what it looks like when on the screen size I designed it for.
I created a working demo on sandbox that has the dimensions set how I need to use it on. This is what it looks like.
Here is link WORKING DEMO
any help is appreciated. Also keep in mind the outer tiers can have less or more options. it would be great if the blue toggle button would always align at the bottom of the radial like under the En of Energy Loss
I would consider using an SVG ViewBox in order to maintain consistency. What this basically does is create a consistent scalable SVG, mapping the size and coordinates of its container into a consistent range inside the SVG.
For example:
<div height="400px" width="400px">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="100%" height="100%" stroke="red" fill-opacity="0"/>
<circle r="4" cx="10" cy="10"/>
</svg>
</div>
So it basicalley creates a mapping from the 400x400 dimensions of the div, into the 100x100 of the svg, so the circle positioned at (10, 10) inside the svg will actually be in coordinates (40, 40) of the div

SVG - make viewBox(0, 0, 100%, 100%) with percentages

How can I make SVG viewBox user coordinate system the same as the viewport coordinates system provided by SVG itself (height="100%" and width="100%")?
I need this special case for a project I'm doing, SVG element should be responsive, but still we need to keep height and width 100% on the SVG itself.
So, I need something like this:
<svg height="100%" width="100%" viewBox="0, 0, 100%, 100%">
<circle cx="25" cy="25" r="20" stroke="black" strokeWidth="1" fill="black" />
</svg>
.. but the viewBox attribute doesn't accept percentages.
%/px is not allowed in the viewBox, those are the maximum coordinates.
By default the SVG content is contained to the SVG size.
If you want the content to stretch to 100%, disable the aspect ratio using preserveAspectRatio="none".
You can also use preserveAspectRatio="slice" to make the content cover the SVG (like background-size: cover).
<svg height="100%" width="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
There are some good articles about this: https://css-tricks.com/scale-svg/ and https://alligator.io/svg/preserve-aspect-ratio/

SVG animate path to rotate around its center

I want to animate an image for The Center for Humane Technology for use on a html landing page. The image looks like this and contains 28 gears positioned in a heart shape. I would like to have each gear to rotate continuously around its center, either clockwise or counter-clockwise.
I have read the other SO posts that deal with similar issues, but the solutions do not work for me. When I add e.g. an animateTransform to the shape of a gear, specifying its center coordinates, then it rotates in a wide circle around its center, not staying into position. I am confused.
I have the orginal artwork (created by nivedita) from EPS to SVG, which resulted in rather large paths. This is the resulting SVG image.
First I was planning to use AnimateJS for the animation, but inline SVG animation markup may also do the trick (maybe better).
Reading other SO submissions, I tried rotating gear1 by calculating its center from these coordinates:
X: 42.623
Y: 309.810
Width: 60.796
Height: 60.774
Resulting in following transform:
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" id="gears-of-the-heart" width="333.92" height="526.28">
<g id="canvas" transform="matrix(1.25 0 0 -1.25 0 526.27)">
<g id="heart">
<path d="M58.42 265.7a6.41 6.41 ..." id="gear1" fill="#1e2227">
<animateTransform attributeType="xml" attributeName="transform" type="rotate"
from="0 73.111 340.197" to="360 73.111 340.197" additive="sum" dur="3s" repeatDur="indefinite"/>
</path>
<!-- The other 27 gears here -->
</g>
<!-- More SVG elements (unrelated) -->
</g>
</svg>
Its not working. Is this because of the transform on the parent canvas group?
Two questions:
What am I doing wrong?
Is there an easier way to animate this, so I don't have to calculate center for all gears (I know I could use javascript for coordinates calc)?

lock element to the SVG element

I have the following SVG of a phone:
<svg width="897px" height="452px" viewBox="0 0 897 452" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<g id="iphone" sketch:type="MSLayerGroup" stroke="#7E89A3" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M130,257.964 C130,266.797 122.809,273.956 113.938,273.956 L16.063,273.956 C7.192,273.956 0.001,266.797 0.001,257.964 L0.001,16.073 C0.001,7.24 7.192,0.081 16.063,0.081 L113.938,0.081 C122.809,0.081 130,7.24 130,16.073 L130,257.964 L130,257.964 Z"
id="bezel" stroke-width="2" fill="white" sketch:type="MSShapeGroup"></path>
<rect id="screen" fill="#ddd"
sketch:type="MSShapeGroup" x="9" y="36" width="111.93" height="199.084"></rect>
<path d="M77,25.746 C77,26.381 76.561,26.893 76.02,26.893 L55.918,26.893 C55.376,26.893 54.938,26.38 54.938,25.746 L54.938,23.166 C54.938,22.531 55.377,22.019 55.918,22.019 L76.02,22.019 C76.561,22.019 77,22.532 77,23.166 L77,25.746 L77,25.746 Z" id="speaker"
sketch:type="MSShapeGroup"></path>
<circle id="camera" sketch:type="MSShapeGroup" cx="66" cy="12" r="3"></circle>
<ellipse id="lock" sketch:type="MSShapeGroup" cx="65.04" cy="254.001" rx="10.04" ry="10.001"></ellipse>
</g>
</svg>
Which looks like following:
I will be using AngularJS to dynamically generate <ul> with elements on the phone screen, where generated elements will be interactive (users will be able to e.g. click on them).
The challenge however is, how to lock the size of my div element (which will hold the ul element), so that it always has the size of the screen? I want this phone to be center aligned on my page, but as far as I know, the SVG size will adapt to the actual window size.
Is there a way how to dynamically poisition my div element to be only on the phone's screen?
P.S. I can see that my SVG contains element with id screen so maybe somehow detect the position of this element?
I would recommend placing an absolutely positioned div over the SVG element. You can calculate the dimensions of the screen image by using the getBoundingClientRect() method. The code is simple:
var ui = document.getElementById("ui");
var screen = document.getElementById("screen");
var dimensions = screen.getBoundingClientRect();
ui.style.left = dimensions.left + "px";
ui.style.top = dimensions.top + "px";
ui.style.width = dimensions.width + "px";
ui.style.height = dimensions.height + "px";
You can see a working example here: https://jsfiddle.net/hxe9nb3n/
For a start it doesn't have to adapt to the size of the window. That behaviour is under your control.
Alternatively, you can embed HTML inside an SVG using the <foreignObject> element. That way the embedded HTML will adapt to whatever size the SVG becomes. There are many examples of how to do that in SO.

Categories

Resources