CSS rotate SVG repeated <use> element around object center - html
I'm trying to rotate an single object which contains SVG through CSS. I'm having trouble to get the rotation around the center of the element, though 'tranform-origin' is set.
HTML:
<svg xmlns="http://www.w3.org/2000/svg" width="8.94" height="31.23" viewBox="0 0 8.94 31.23">
<defs>
<g id="el">
<rect x="1.86" y="0.2" width="5.22" height="30.82" transform="translate(-1.87 0.66) rotate(-7)"></rect>
</g>
</defs>
</svg>
<svg class="canvas">
<use x="49%" y="76%" href="#el"></use>
<use x="80%" y="63%" href="#el"></use>
<use x="5%" y="32%" href="#el"></use>
<use x="80%" y="12%" href="#el"></use>
</svg>
CSS:
.canvas {
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.canvas use:nth-child(4){
fill: red;
transform-origin: 50% 50%;
transform: rotate(20deg);
}
I made a JSfiddle to make things clear. What I'm trying to do is rotating the red element 20deg around it's own center. But it keeps rotating around the top left corner.
For SVG elements, unlike HTML, the coordinate system is not defined by its local bounding box:
For SVG elements without an associated CSS layout box the horizontal and vertical offset represent an offset from the point of origin of the element’s local coordinate space.
In your case, the coordinate system is that of the .canvas, and the <use> element rotates around its center.
You have to calculate the center of your element as "position + center of the bounding box", so for your element at x=80%, y=12% and a bounding box of 8.95px×31.25px:
.canvas use:nth-child(4){
transform-origin: calc(80% + 4.475px) calc(12% + 15.625px);
transform: rotate(20deg);
}
I did want to post this as a comment because I feel like it's not fully solved, but it's too long to fit in a comment so here it is as an answer:
When you use the <use> tag, the transform seems to be based on if the path was in the top left. Therefore what you can do is instead of using % widths for the transform-origin use vh and vw (which only works because you have set the .canvas to be the size of the display) and set it equal to the offset of the path. This brings the center of rotation to where the path is.
So in your case, this would be:
.canvas use:nth-child(4){
fill: red;
transform-origin: 80vw 12vh;
transform: rotate(20deg);
}
This works but it doesn't really solve the root problem.
.canvas {
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.canvas use:nth-child(4){
fill: red;
transform-origin: 80vw 12vh;
transform: rotate(20deg);
}
<svg xmlns="http://www.w3.org/2000/svg" width="8.94" height="31.23" viewBox="0 0 8.94 31.23">
<defs>
<g id="el">
<rect x="1.86" y="0.2" width="5.22" height="30.82" transform="translate(-1.87 0.66) rotate(-7)"></rect>
</g>
</defs>
</svg>
<svg class="canvas">
<use x="49%" y="76%" href="#el"></use>
<use x="80%" y="63%" href="#el"></use>
<use x="5%" y="32%" href="#el"></use>
<use x="80%" y="12%" href="#el"></use>
</svg>
Related
Is there a way to use pseudo element :before :after in svg path
I'm trying to add a new element after an svg path, that will have the same shape and path as his parent using :after or :before I created a g tag wrapping the path i want to target calling it by the class eyes <div id="svg-container"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 641.27 1107.33"> <defs> <style> .cls-30,.cls-32{stroke:#000;}.cls-30,.cls-32{stroke-miterlimit:10;}.cls-32{stroke-width:2px;}.cls-32{fill:#fff79a;} </style> </defs> <g class="eyes" id="GLOBE_GAUCHE" data-name="GLOBE GAUCHE"> <path class="cls-32" d="M298.37,210.19a39.52,39.52,0,0,0-18.46,7.23c-13.18,9.52-37,30.1-31.86,51.4,2.06,8.63,4.47,28,28.41,36.54a56.76,56.76,0,0,0,52.73-8.23,58.12,58.12,0,0,0,23.45-38.34,39.45,39.45,0,0,0-.5-15.94C348.79,229.24,337.62,205.53,298.37,210.19Z" transform="translate(-63.9 148.72)"> </path> </g> <g class="pupilles" id="PUPILLE_GAUCHE" data-name="PUPILLE GAUCHE"> <path class="cls-30" d="M281.69,266.67s-1.81-13.91,3.65-2.52c3.85-1.57,13.76-7.9,7.61.2,1.37,1.6,8.89,2.93-.84,4.93.48,2.24,6,10-3.41,4.87-1.9-.39-4,9.57-5.66-.92C282.63,271.22,272.29,270.37,281.69,266.67Z" transform="translate(-63.9 148.72)"> </path> </g> <g class="eyes" id="GLOBE_DROIT" data-name="GLOBE DROIT"> <path class="cls-32" d="M434.11,207.73A39.52,39.52,0,0,0,415.65,215c-13.18,9.52-37,30.1-31.86,51.4,2.06,8.63,4.47,28,28.41,36.54a56.76,56.76,0,0,0,52.73-8.23,58.12,58.12,0,0,0,23.45-38.34,39.63,39.63,0,0,0-.5-15.94C484.53,226.78,473.35,203.07,434.11,207.73Z" transform="translate(-63.9 148.72)"> </path> </g> <g class="pupilles" id="PUPILLE_DROIT" data-name="PUPILLE DROIT"> <path class="cls-30" d="M450.29,260.72s8.59-12.71-4.63-4.78c-1.06-1.35-5-7.78-5.28-.32-1,2.91-15.65,1.23-1.35,7.42.61,1.7-5.82,5.89,2.28,4.74.91,1.9,2.19,6.85,5.92.87C450.32,265.93,457.76,268.9,450.29,260.72Z" transform="translate(-63.9 148.72)"> </path> </g> </svg> </div> Then in the css i'm tryin to insert a new element :after the path of the eyes class html, body { height: 100%; margin: 0; } #svg-container { background-color: #f1f1f1; height: 100%; } svg { display: block; margin: auto; height: 100%; } .eyes path::after { content: ''; position: absolute; top: 0; left: 0; height: 100%; width: 100%; background: #d9a12a; } I can see in the console that it create the :after element at the right position, however it doesn't seem to show and the number of pixels are not showing (not even 0) Is there a way to implement this ?
Change size of SVG clipping mask on mouseover
I want to show images using a mask. I tried the css clip-path but because the browser support is that poor I want to use an svg for clipping. My question is how can i change the size of the mask on mousover? Like here: I am using this code at the moment: <svg> <defs> <!--defines the shape to use for clipping--> <circle id="circle" cx="100" cy="100" r="100" /> </defs> <clipPath id="clip"> <!--creates the clipping mask from the shape--> <use xlink:href="#circle" overflow="visible"></use> </clipPath> <!--group containing the image with clipping applied--> <g clip-path="url(#clip)"> <image overflow="visible" xlink:href="model_detail.jpg"></image> </g> </svg
In your case, since you need to resize both the circle and the clipped image, you may scale the svg element on hover like so: svg { display: block; position: absolute; margin: auto; top: 0; bottom: 0; left: 0; right: 0; transform: scale(1); transition: transform 0.5s; } svg:hover { transform: scale(1.5); } <svg viewBox="0 0 200 200" width="200"> <defs> <!--defines the shape to use for clipping--> <circle id="circle" cx="100" cy="100" r="100" /> </defs> <clipPath id="clip"> <!--creates the clipping mask from the shape--> <use xlink:href="#circle" overflow="visible"></use> </clipPath> <!--group containing the image with clipping applied--> <g clip-path="url(#clip)"> <image overflow="visible" xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/darwin300.jpg"></image> </g> </svg> Please observe that I've added a viewBox and a width attribute to the svg. If you don't declare the viewBox and the width the svg element will have a size of 300px/150px and the part of the circle that falls outside the svg canvas will be cuted off. UPDATE The OP is commenting I don't want to scale the image, just the mask. Is that possible? This is how I would do it: In the next example I'm using transitions to scale the circle when you mouse over the svg element: #c{transform: scale(1); transition: transform 0.5s;} svg:hover #c { transform: scale(1.5); } Next comes a working example: svg { border: 1px solid; display: block; position: absolute; margin: auto; top: 0; bottom: 0; left: 0; right: 0; } #c{transform: scale(1); transition: transform 0.5s;} svg:hover #c { transform: scale(1.5); } <svg viewBox="-150 -150 300 300" width="200"> <defs> <clipPath id="clip"> <!--creates the clipping mask from the shape--> <circle id="c" r="100" /> </clipPath> </defs> <!--group containing the image with clipping applied--> <image clip-path="url(#clip)" id="img" xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/darwin300.jpg" x="-150" y="-150" width="300" height="300"></image> </svg>
You could do it easily with JavaScript, adding an eventlistener onMouseOver over a class. However, if you need CSS, you can an animation that will do it,like this. .zoom { padding: 50px; background-color: green; transition: transform .2s; /* Animation */ width: 200px; height: 200px; margin: 0 auto; } .zoom:hover { transform: scale(1.5); /* (150% zoom - Note: if the zoom is too large, it will go outside of the viewport) */ } </style> <div class="zoom"></div>
Make child text of rotated SVG element upright
I have an SVG element which gets applied a transform: rotate(0deg) value which moves a circle around something else. I'd like to add text to the center of the circle, but the problem I'm running into is that because the circle and text share the same rotate property for alignment the text is unreadable because it is often times upside down. Is there a way to make the text that sits within the circle always be upright without manually changing the circle and circle-text css properties? Is there some sort of way I can make the tspan element orient its self back to whatever the browser default is? svg { height: 100%; width: 100%; } circle { transform: rotate(50deg); } .circle-text { transform: rotate(50deg); } .container { max-width: 800px; height: 800px; margin: 0 auto; } <div class="container"> <svg width="200" height="100"> <circle cx="64.5" cy="0" r="10" fill="#472e12" /> <text class="circle-text" x="64.5" y="0" text-anchor="middle" fill="#fff"> <tspan>-30</tspan> </text> </svg> </div>
This is my solution: I'm putting both the circle and the text inside a group: .theg, and I'm rotating the group 50deg. Next I'm rotating the text -50deg and also I'm setting transform-origin:64.5px 0; i.e. the text is rotating around the center of the circle. Also I've added dominant-baseline="middle" to the circle. I hope this is what you need. svg { width: 100%; } .theg{ transform: rotate(50deg); } .circle-text { transform-origin:64.5px 0; transform: rotate(-50deg); } <div class="container"> <svg viewBox="0 0 200 100"> <g class="theg"> <circle cx="64.5" cy="0" r="10" fill="#472e12" /> <text class="circle-text" x="64.5" y="0" text-anchor="middle" dominant-baseline="middle" fill="#ff0"> <tspan>-30</tspan> </text> </g> </svg> </div>
CSS svg hover animation/transition
im using a svg clip-path as mask for an image. Now i want an inner border inside it on hover. So i made a second clip-path for hover but the transition doesnt affect it. I want the border comming from the sides (reducing zoom/negative scaling). Here you can see what i want without working animation/transition: body { background: #ccc; } .clip-svg { position: relative; display: block; height: 400px; width: 300px; background-position: center center; background-size: auto 100%; clip-path: url(#Emblem); transition: 0.4s all ease; } .clip-svg:hover { clip-path: url(#Emblem2); } <div class="clip-svg" style="background-image: url(https://images.pexels.com/photos/864994/pexels-photo-864994.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260)"></div> <svg width="0" height="0"> <defs> <clipPath id="Emblem"> <path d="M279,240c0,96-139.4,145-139.4,145S22,336,22,240c0-58,0-203,0-203s65-11,129-11c75,0,128,11,128,11S279,136,279,240z"/> </clipPath> <clipPath id="Emblem2"> <path d="M39,51.6V240c0,72.4,80.9,116.6,101.2,126.5c11-4.5,35.7-15.4,59.8-32.3c18.5-13,33.2-26.8,43.6-41.3 c12.2-17,18.4-34.8,18.4-53V51.3C240.8,48,200.9,43,151,43C106.3,43,59.9,48.7,39,51.6z"/> <path d="M151,26C87,26,22,37,22,37s0,145,0,203c0,96,117.6,145,117.6,145S279,336,279,240c0-104,0-203,0-203S226,26,151,26z M270,240c0,19.9-6.7,39.3-19.9,57.7c-10.9,15.1-26.2,29.7-45.5,43.1c-26.2,18.3-52.8,29.8-63.1,33.8l-1.6,0.6l-1.6-0.8 c-10.4-5-37.2-18.9-61.3-41.3c-30.5-28.4-46-59.7-46-93.2V44.7l3.4-0.5C53.5,41.4,103.1,35,151,35c53.2,0,95.3,5.6,115.6,8.9 l3.4,0.5V240z"/> </clipPath> </defs> </svg> <br/> Image: https://www.pexels.com/...d-tablet-864994/ Thanks in advance
I would consider two layers each one using a clip-path and I would control the opacity: body { background: #ccc; } .clip-svg { position: relative; display: inline-block; height: 400px; width: 300px; } .clip-svg::before, .clip-svg::after { content: ""; position: absolute; top: 0; right: 0; left: 0; bottom: 0; background-position: center center; background-size: auto 100%; background-image: var(--i); transition: 0.8s all ease; } .clip-svg::before { clip-path: url(#Emblem2); } .clip-svg::after { clip-path: url(#Emblem); } .clip-svg:hover::after { opacity:0; } <div class="clip-svg" style="--i: url(https://images.pexels.com/photos/864994/pexels-photo-864994.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260)"></div> <svg width="0" height="0"> <defs> <clipPath id="Emblem"> <path d="M279,240c0,96-139.4,145-139.4,145S22,336,22,240c0-58,0-203,0-203s65-11,129-11c75,0,128,11,128,11S279,136,279,240z"/> </clipPath> <clipPath id="Emblem2"> <path d="M39,51.6V240c0,72.4,80.9,116.6,101.2,126.5c11-4.5,35.7-15.4,59.8-32.3c18.5-13,33.2-26.8,43.6-41.3 c12.2-17,18.4-34.8,18.4-53V51.3C240.8,48,200.9,43,151,43C106.3,43,59.9,48.7,39,51.6z"/> <path d="M151,26C87,26,22,37,22,37s0,145,0,203c0,96,117.6,145,117.6,145S279,336,279,240c0-104,0-203,0-203S226,26,151,26z M270,240c0,19.9-6.7,39.3-19.9,57.7c-10.9,15.1-26.2,29.7-45.5,43.1c-26.2,18.3-52.8,29.8-63.1,33.8l-1.6,0.6l-1.6-0.8 c-10.4-5-37.2-18.9-61.3-41.3c-30.5-28.4-46-59.7-46-93.2V44.7l3.4-0.5C53.5,41.4,103.1,35,151,35c53.2,0,95.3,5.6,115.6,8.9 l3.4,0.5V240z"/> </clipPath> </defs> </svg>
I don't see a way to achieve what you want using clip paths on an HTML element. You can only replace one clip path with another in CSS. You can't interpolate between two of them. However it is fairly easy to do, if you are okay with moving the image into an SVG. Then you can do whatever you want with the inner border. Note however, with this solution, the inner border is not a clip path, so it doesn't make the image transparent. I don't know if that is important to you or not. It should be possible to do that if you really need it to. body { background: #ccc; } .clip-svg .emblem2-ref { transform-origin: 150px 200px; transform: scale(1.2, 1.2); transition: 0.4s all ease; } .clip-svg:hover .emblem2-ref { transform: scale(1, 1); } #Emblem2 { fill: #ccc; } <!-- Clip path and inner border definitions. Can be included once and used by multiple SVGs --> <svg width="0" height="0"> <defs> <clipPath id="Emblem"> <path d="M279,240c0,96-139.4,145-139.4,145S22,336,22,240c0-58,0-203,0-203s65-11,129-11c75,0,128,11,128,11S279,136,279,240z"/> </clipPath> <path id="Emblem2" d="M39,51.6V240c0,72.4,80.9,116.6,101.2,126.5c11-4.5,35.7-15.4,59.8-32.3c18.5-13,33.2-26.8,43.6-41.3 c12.2-17,18.4-34.8,18.4-53V51.3C240.8,48,200.9,43,151,43C106.3,43,59.9,48.7,39,51.6z M270,240c0,19.9-6.7,39.3-19.9,57.7c-10.9,15.1-26.2,29.7-45.5,43.1c-26.2,18.3-52.8,29.8-63.1,33.8l-1.6,0.6l-1.6-0.8 c-10.4-5-37.2-18.9-61.3-41.3c-30.5-28.4-46-59.7-46-93.2V44.7l3.4-0.5C53.5,41.4,103.1,35,151,35c53.2,0,95.3,5.6,115.6,8.9 l3.4,0.5V240z"/> </defs> </svg> <!-- Will need one of these SVGs for every image you want to display --> <svg width="300" height="400" class="clip-svg"> <g clip-path="url(#Emblem)"> <image width="300" height="400" preserveAspectRatio="xMidYMid slice" xlink:href="https://images.pexels.com/photos/864994/pexels-photo-864994.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260"/> <use class="emblem2-ref" xlink:href="#Emblem2"/> </g> </svg> <br/> Image: https://www.pexels.com/...d-tablet-864994/
I want to implement SVG clip-path for SVG element
I want to implement SVG clip-path for SVG element. I have a DIV element in which I want to put SVG element which will act as a clipping mask, and also I have the separate SVG element that has an image to which the clipping mask will be applied. The first problem I faced with is that clipping mask moves to the left top corner of the viewport but not located inside of the parent DIV element. The second problem is that I want to make an image on the full screen not depending on the screen size. Incorrect Mask Circle Correct Mask Circle (what I want to have) Do you have suggestions how to make it? Thanks in advance! html, body { margin:0; padding:0; overflow:hidden } svg { position:absolute; top:0; left:0;} .image-clip-src { width: 100%; height: 100%; } .svg-wrapper { width: 72px; height: 72px; padding: 2.5em; border: 1px solid #4D4F51; margin: 0 auto; border-radius: 50%; overflow: hidden; position: fixed; top: 55%; z-index: 9; left: 64%; transform: translateY(-50%); cursor: pointer; } .clipped-image image { clip-path: url(#clipping); } <svg class="clipped-image" width="100%" height="100%" viewBox="0 0 1440 960" preserveAspectRatio="xMinYMin meet"> <image class="image-clip-src" xlink:href="https://images.unsplash.com/photo-1526327227970-4bda49fa3489?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=3c4bce33d96df6b18af53fb2dae3363e&auto=format&fit=crop&w=1650&q=80" width="100%" height="100%" overflow="visible"/> </svg> <div class="svg-wrapper"> <svg class="svg-defs"> <defs> <clipPath id="clipping"> <circle r="72" stroke="black" stroke-width="3"/> </clipPath> </defs> </svg> </div>
That's not the way SVG works. When you tell something to use a clip path, all it sees is the clip path definition itself. It doesn't know or care about where on the page you have positioned it's parent <svg>. If you want the clip circle to be at a certain position on the water image, you need to specify its position using cx and cy. html, body { margin:0; padding:0; overflow:hidden } svg { position:absolute; top:0; left:0;} .image-clip-src { width: 100%; height: 100%; } .clipped-image image { clip-path: url(#clipping); } <svg class="clipped-image" width="100%" height="100%" viewBox="0 0 1440 960" preserveAspectRatio="xMinYMin meet"> <defs> <clipPath id="clipping"> <circle cx="64%" cy="55%" r="72" stroke="black" stroke-width="3"/> </clipPath> </defs> <image class="image-clip-src" xlink:href="https://images.unsplash.com/photo-1526327227970-4bda49fa3489?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=3c4bce33d96df6b18af53fb2dae3363e&auto=format&fit=crop&w=1650&q=80" width="100%" height="100%" overflow="visible"/> <circle cx="64%" cy="55%" r="72" fill="none" stroke="#4D4F51" stroke-width="1"/> </svg>