Making inline SVG responsive in browsers without using javascript? - javascript

I've been reviewing articles like this one which are really good, but possibly dated, and I'm wondering if any progress has been made on a simple way to make inline svg responsive while maintaining the aspect ratio?
In other words if we have svg with a 1:1 aspect ratio inside a div container, and the container shrinks from 400x400px to 200x200 px, then the view box width and height parameters double.

I am confused about what you want. I don't know what you mean, in your question, when you talk about "view box width and height parameters double". Perhaps you are confused about how viewBox works.
As long as the SVG has a viewBox it should be responsive.
In the following example, I've put the SVG in a div container and animated the container size to simulate the page size changing.
#container {
width: 200px;
background: linen;
animation: scale 1s alternate infinite;
}
#keyframes scale {
from { width: 200px; }
to { width: 100px; }
}
<div id="container">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="gold"/>
<rect x="10" y="50" width="20" height="50" fill="gold"/>
<rect x="70" y="50" width="20" height="50" fill="gold"/>
<circle cx="35" cy="45" r="5"/>
<circle cx="65" cy="45" r="5"/>
</svg>
</div>
But it could just as easily be a container that is the width of the page. Try running the following snippet. Then click "Fullpage" and resize the browser window.:
#container {
width: 100%;
background: linen;
}
<div id="container">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="gold"/>
<rect x="10" y="50" width="20" height="50" fill="gold"/>
<rect x="70" y="50" width="20" height="50" fill="gold"/>
<circle cx="35" cy="45" r="5"/>
<circle cx="65" cy="45" r="5"/>
</svg>
</div>

If you don't need to change svg with css or modify it, I suggest you to use it as an img element. You create an img and put the path to your svg in the src. Here's what I mean: https://codepen.io/tlemaitre/pen/PBKGOo

I've recently had a similar problem, I have a container that needs to be preserveAspectRatio="none" to keep the shape fluid on different window sizes but the image inside gets gets distorted. I've not seen any solution to this problem with or without JS, so I don't think it's possible.

Related

Click only through holes in svg mask

I have svg mask which determines holes in rectangular. Behind svg mask I have some clickable elements and I would like to pass events to them, but only through holes. I've experimented with pointer-events values, but I can only make either whole mask to pass events or whole mask to capture them. For one hole it can be simply done using clip-path, just determining outer part of the hole, but several holes make things more difficult. Is there any possibility to avoid using clip-path? I also tried pointer-events: visiblePainted and pointer-events: painted, but had no success.
.background {
width: 400px;
height: 400px;
background: red;
cursor: pointer;
}
.svg {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
<button class="background">
</button>
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" class="svg">
<defs>
<mask id="mask">
<rect
x="0"
y="0"
width="400"
height="400"
fill="white"
/>
<rect
x="20"
y="20"
width="40"
height="40"
fill="black"
/>
<rect
x="290"
y="290"
width="40"
height="40"
fill="black"
/>
</mask>
</defs>
<rect
x="0"
y="0"
width="400"
height="400"
fill="black"
opacity="0.5"
mask="url(#mask)"
pointer-events="auto"
/>
</svg>
There are several aspects to this problem. First, you are right the behavior of masks and clip-paths is different in relation to hit-testing.
A clip path is a geometric boundary, and a given point is clearly either inside or outside that boundary; thus, pointer events must be captured normally over the rendered areas of a clipped element, but must not be captured over the clipped areas... By contrast, a mask is not a binary transition, but a pixel operation, and different behavior for fully transparent and almost-but-not-fully-transparent may be confusingly arbitrary; as a consequence, for elements with a mask applied, pointer events must still be captured even in areas where the mask goes to zero opacity.
Second, a clip-path is a geometric shape, but just like all paths, it might contain holes. Instead of three <rect>s, you can use one <path> with three subpaths, as long as the clip-rule makes sure the subpaths inside get cut out of the surrounding shape.
Third, if the pointer-events property is applied to an <svg> element in a HTML context, its behavior becomes...strange. Any other value than pointer-events: none on the <svg> element lead to the whole bounding box receiving events - a behavior proposed for HTML elements, but currently not part of any spec.
The solution here is to set pointer-events: none on the <svg> element, and then to reverse that with pointer-events: painted on the child <rect> element.
button, svg {
position:absolute;
width:400px;
height:400px
}
button {
background: #0000ff;
cursor: pointer;
}
button:hover {
background: #008800;
}
svg {
pointer-events: none;
}
.over {
fill: #000;
clip-path: url(#clip);
pointer-events: painted;
}
<button></button>
<svg xmlns="http://www.w3.org/2000/svg" height="400" width="400">
<defs>
<clipPath id="clip" clip-rule="evenodd">
<path d="M 20 20 h 360 v 360 h -360 z
M 40 40 v 40 h 40 v -40 z
M 200 290 v 40 h 40 v -40 z" />
</clipPath>
</defs>
<rect y="0" x="0" height="400" width="400" class="over" />
</svg>
Clip masks are useful for cropping parts out of complicated objects, but if you're just working with blocks of solid colour then maybe it would be just as easy to create shapes that already have holes in them.
I've added an example below. Does this help?
<svg width="400" heoght="200" viewBox="0 0 400 200">
<text x="100" y="100" text-anchor="middle"
alignment-baseline="middle" onclick="alert('Hello!')"
style="cursor:pointer">Click me</text>
<text x="300" y="100" text-anchor="middle"
alignment-baseline="middle" onclick="alert('Hello?')"
style="cursor:pointer">Not me</text>
<path d="M20 20 180 20 180 180 20 180ZM60 60 60 140 140
140 140 60Z" fill="#3a6" fill-opacity="0.7"
fill-rule="nonzero"/>
<path d="M220 20 380 20 380 180 220 180Z" fill="#f20"
fill-opacity="0.7"/>
</svg>

Hover events for a dashed line in an SVG

An SVG with a line (or path) which uses stroke-dasharray only seems to trigger CSS and JS hover events when the user hovers over the solid parts of the dashed line: https://codepen.io/anon/pen/YeXoZy
Is there a simple way to make both the JS and CSS events trigger when the solid or invisible parts of the line are hovered?
My current plan is to draw a second, invisible line following the same path and use it to detect mouse events. https://codepen.io/anon/pen/BYNgRR This seems heavy handed and I'm hoping there's a cleaner way I'm missing.
I'm not sure how to do it without the second "detector" line, but a less heavy handed way is at least possible without the JS.
Switch the order of the lines, then you can use the hover selector as usual for the dashed line, then use + in a selector for the detector line to change the properties of the line immediately following it:
https://codepen.io/RyanGoree/pen/LQVKBV
This can be solved somewhat by using a rect instead of line and using SVG transforms with patterns.
An example can be seen at this CodePen.
It essentially bubbles down to:
<svg height="210" width="500">
<defs>
<pattern id="pattern1"
width="10" height="10"
patternUnits="userSpaceOnUse"
patternTransform="rotate(0 60 60)">
<line stroke="green" stroke-width="12px" y2="10"/>
</pattern>
<pattern id="pattern2"
width="10" height="10"
patternUnits="userSpaceOnUse"
patternTransform="rotate(0 60 60)">
<line stroke="red" stroke-width="12px" y2="10" stroke="transparent"/>
</pattern>
</defs>
<g transform="rotate(45 60 60)">
<rect x="0" y="0" width="500" height="5"/>
</g>
</svg>
And the following CSS:
rect {
fill: url(#pattern1)
}
rect:hover {
fill: url(#pattern2)
}
This is an old topic I know. But found the answer on Css hover sometimes doesn't work on svg paths
If you want to trigger the event only on stroke/visible, use pointer-events: stroke; or pointer-events: visible; (only the stroke) and pointer-events: all; (on both)
Here is a code example:
<body>
<div id="donut-score" class="svg-item" style="display: block;">
<svg width="100%" height="100%" viewBox="0 0 40 40" class="donut">
<circle class="donut-hole" cx="20" cy="20" r="15.91549430918954" fill="#fff"></circle>
<circle class="donut-ring" cx="20" cy="20" r="15.91549430918954" fill="transparent" stroke-width="3.5"></circle>
<circle id="donut-score-part-1" class="donut-segment donut-segment-1" onmousemove="this.style.stroke ='orange';" onmouseout="this.style.stroke = '#ff8197';" cx="20" cy="20" r="15.91549430918954" fill="transparent" stroke-width="3.5" stroke-dasharray="20 80" stroke-dashoffset="25"></circle>
<g class="donut-text-item donut-text-item">
<text y="50%" transform="translate(0, 2)">
<tspan id="donut-score-text" x="50%" text-anchor="middle" class="donut-text">Score </tspan>
<tspan id="donut-score-aantal" x="50%" Y="65%" text-anchor="middle" class="donut-text">0 </tspan>
</text>
</g>
</svg>
</div>
</body>
ccs:
.donut-segment {
stroke: #ff8197;
}
.svg-item {
width:200px;
font-size: 16px;
margin: 0 auto;
}
.donut-text {
font-size: 0.35em;
line-height: 1;
transform: translateY(0.5em);
font-weight: bold;
}
If you add pointer-events: stroke; to the class .donut-segment then it only works on the stroke. If you use non or pointer-events: all it works on both. I tested it in codepen.

ScrollMagic, reverse z-index order

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;
}

Change opacity of an area based on mouse position

Is it possible to change the opacity of background but only underneath the cursor area (for example a white small circle)? I am thinking of it a bit like a basic heatmap but the heat doesn't stay - it just follows the cursor.
At the moment I have the following
HTML:
html {
background-color: #000;
width: 100%;
height: 100%;
}
JS:
$(document).mousemove(function(event){
var i = event.pageX.toPrecision(1) / 1000;
$("html").css('opacity', i)
});
Sorry this is probably a very basic starting point. Would I need to use canvas?
You can do that using svg
What i did :-
I placed two same images with same co ordinates,height and width and gave a circular clip-path to the one on top (which has full opacity) when mouse moves the position of the circle also changes
$('svg').on('mousemove',function(e){
$('.a').attr('cx',e.pageX).attr('cy',e.pageY)
})
.one{
opacity:.5;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg width="500" height="500">
<clippath id="clip" >
<circle cx="50" cy="50" r="50" class="a"/>
</clippath>
<image xlink:href="https://images.unsplash.com/photo-1474575981580-1ec7944df3b2?dpr=1&auto=format&fit=crop&w=1500&h=934&q=80&cs=tinysrgb&crop=&bg=" width="500" height="500" x="0" y="0" class="one"/>
<image xlink:href="https://images.unsplash.com/photo-1474575981580-1ec7944df3b2?dpr=1&auto=format&fit=crop&w=1500&h=934&q=80&cs=tinysrgb&crop=&bg=" width="500" height="500" x="0" y="0" clip-path="url(#clip)"/>
</svg>

Preserve aspect ratio for SVG Text

This is an edited copy of https://stackoverflow.com/questions/29105120/preserve-aspect-ratio-for-svg-text-and-react-to-javascript-touch-events which I will remove, because it asked 2 related but technically different questions.
as I already explained in my last question, I'm trying to make a navigation-div with 4 buttons, one to go left, one to go right, another one to go down and yet another one to go up. Plus there needs to be an OK-button in the middle.
That worked really well with the explanation given here: Using CSS and HTML5 to create navigation buttons using trapezoids
I created the SVG like:
<div class="function height3x svg-container" style="height: 112px; width: 200px;">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="mySVG" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none" style="background-color: whitesmoke">
<g class="function" id="keyboard_btn_24">
<polygon id="ok" points="25,25 75,25 75,75 25,75"></polygon>
<text id="ok_text" x="39" y="55">OK</text>
</g>
<g class="function" id="keyboard_btn_25">
<polygon id="up" stroke="black" stroke-width="0.1" points="0,0 100,0 65,35 35,35"></polygon>
<text x="42" y="20"></text>
</g>
<g class="function" id="keyboard_btn_26">
<polygon id="right" stroke="black" stroke-width="0.1" points="100,0 100,100 65,65 65,35"></polygon>
<text x="81" y="53"></text>
</g>
<g class="function" id="keyboard_btn_27">
<polygon id="down" stroke="black" stroke-width="0.1" points="0,100 35,65 65,65 100,100"></polygon>
<text x="42" y="91"></text>
</g>
<g class="function" id="keyboard_btn_28">
<polygon id="left" stroke="black" stroke-width="0.1" points="0,0 35,35 35,65 0,100"></polygon>
<text x="5" y="53"></text>
</g>
</svg>
</div>
But I have two problems that I seem not to be able to figure out.
First of all: While I want the SVG to be responsive, I don't want to scale the text without keeping the aspect-ratio given by the font.
I already tried (unsuccessfully) preserveAspectRatio, which does not seem to do much to the text.
Questions: How can you make the tag keep it's aspect ratio, while changing the aspect ratio of the svg?
You can view and edit the minimal example: jsFiddle
Paulie_D - Commented on my old question:
As for the Aspect Ratio, you should remove the width & height 100%
values. They aren't really needed. The SVG will scale to the required
size based on the div size. - jsfiddle.net/2qqrL7ng/1 –
This is not an option, because the SVG Element needs to respond to size changes that can not keep the aspect ratio. Just the text needs to keep the ratio, everything else should be as responsive as possible.
EDIT
Switching the svg argument of perserveAspectRatio from "none" to "xMidYMid" keeps the aspect ratio of the SVG, but the desired effect is, that the SVG itself does not keep it's aspect ratio, but the -tags do. Which means the following would NOT be a solution:
<div class="function height3x svg-container" style="height: 112px; width: 200px;">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="mySVG" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="background-color: whitesmoke">
<g class="function" id="keyboard_btn_24">
<polygon id="ok" points="25,25 75,25 75,75 25,75"></polygon>
<text id="ok_text" x="39" y="55">OK</text>
</g>
<g class="function" id="keyboard_btn_25" preserveAspectRatio="xMidYMid">
<polygon id="up" stroke="black" stroke-width="0.1" points="0,0 100,0 65,35 35,35"></polygon>
<text x="42" y="20"></text>
</g>
<g class="function" id="keyboard_btn_26">
<polygon id="right" stroke="black" stroke-width="0.1" points="100,0 100,100 65,65 65,35"></polygon>
<text x="81" y="53"></text>
</g>
<g class="function" id="keyboard_btn_27">
<polygon id="down" stroke="black" stroke-width="0.1" points="0,100 35,65 65,65 100,100"></polygon>
<text x="42" y="91"></text>
</g>
<g class="function" id="keyboard_btn_28">
<polygon id="left" stroke="black" stroke-width="0.1" points="0,0 35,35 35,65 0,100"></polygon>
<text x="5" y="53"></text>
</g>
</svg>
</div>
/EDIT
Thanks in advance.
I know this is an old question but I did find a way to do what is asked in pure SVG, no JS, and I've used it in prod without issues!
The trick is to define a parent <svg> element without a viewBox so that it takes the container's dimensions, and then define children <svg> elements for the different kind of preserveAspectRatio values that you want!
In the following snippet, I just took the SVG used in the question and put all the <polygon> apart in a <svg viewBox="0 0 100 100" preserveAspectRatio="none">.
div {
height: 112px;
width: 200px;
}
div > svg {
height: 100%;
width: 100%;
background-color: whitesmoke;
}
polygon {
fill: none;
stroke: black;
stroke-width: 0.1;
}
<div>
<!-- SVG wrapper without viewBox to take parent's dimensions -->
<svg>
<!-- sub SVG that will be resized -->
<svg viewBox="0 0 100 100" preserveAspectRatio="none">
<g>
<polygon points="0,0 100,0 65,35 35,35"></polygon>
<text x="42" y="20"></text>
</g>
<g>
<polygon points="100,0 100,100 65,65 65,35"></polygon>
<text x="81" y="53"></text>
</g>
<g>
<polygon points="0,100 35,65 65,65 100,100"></polygon>
<text x="42" y="91"></text>
</g>
<g>
<polygon points="0,0 35,35 35,65 0,100"></polygon>
<text x="5" y="53"></text>
</g>
</svg>
<!-- regular SVG elements that won't be resized -->
<text id="ok_text"
x="50%" y="50%"
text-anchor="middle"
alignment-baseline="middle">
OK
</text>
</svg>
</div>
PS: the end result can even be used as the src of a regular image <img src="./my-weird-responsive-svg.svg" /> which makes this trick really robust!
Questions: How can you make the tag keep it's aspect ratio, while changing the aspect ratio of the svg?
You cannot. If the text is part of the SVG it gets scaled with the SVG. There is no way to make a part of the SVG exempt from the scaling.
Possible solutions:
(1) Remove the text from the SVG and position it on top. For example use a positioned <div> in your HTML or something.
(2) Use JS to calculate the aspect ratio of the SVG and apply an inverse scaling transform to the <text> element.
If you have javascript control on the current dimensions (like going fullscreen, for instance, or simply by checking what viewport size the user is using), you can add a simple class in a parent div outside the SVG.
Then, style the SVG text's font-size for both cases: when the parent div has such class (it tells you that your svg has scaled), and when the parent div hasn't such a class (i.e., the SVG is non-scaled). If this is your case, this simple solution works.
.svg_text {font-size: 12px;} /* normal non-scaled SVG */
.case_for_big_screen_thus_SVG_scaled .svg_text {font-size: 6px;} /* this parent class only is added when viewport dimensions make your SVG to be scaled */

Categories