Stacking masked divs' contents with SVG - html

I'm currently stuck with trying to create stacked <div> elements where the top-level <div> has its content masked, in React (although this is likely unimportant other than for SVG element notation).
Given two responsively-styled <div>s — A and B — both containing an <img> each, where B is absolutely positioned above A, I would like to apply an inline, arbitrary SVG mask to <div> B so that it partially exposes the content of <div> A below it.
Visually, I am trying to achieve something like this...
Two <div>s, absolutely positioned so that they stack on top of one another:
An inline <svg>'s content:
...Is used as a mask against <div> B so that it produces this effect:
I want to avoid using clip-paths as they have limited browser support, and am wondering if there is a way of achieving this effect with a less complex approach. It is also important that I'm able to position the <img> elements within the <div>s, and add more elements if needed — rather than just setting a background mask-image on the <svg>.
Is there a way of doing this that does not rely on SVG <mask> or <clipPath> elements? And if not, is there a way to embed more than just an <img> (via SVG <image>) in the SVG mask?

In this example I'm using images, since this is what you want. I would have used background images. Also I'm using clip-path to clip the second div. In the first example I'm using an svg path for this.
#a,
#b {
outline: 1px solid;
width: 300px;
height: 300px;
position: absolute;
top: 0;
left: 0;
}
#b{
-webkit-clip-path: url(#clip);
clip-path: url(#clip);
}
#wrap {
position: relative;
}
<div id="wrap">
<div id="a">
<img width="300" height="300" src ="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/darwin300.jpg" />
</div>
<div id="b">
<img width="300" height="300" src ="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/puppyBeagle300.jpg" />
</div>
</div>
<svg height="0" width="0" class="svg-clip" style="position:absolute">
<defs>
<clipPath id="clip" clipPathUnits="objectBoundingBox">
<path d="M0,0.9 L0.3,.6 .4,.75 .6,.3 .75,.5 1,.1 1,1 0,1z" />
</clipPath>
</defs>
</svg>
Alternatively you can use the polygon function.
#a,
#b {
outline: 1px solid;
width: 300px;
height: 300px;
position: absolute;
top: 0;
left: 0;
}
#b{
-webkit-clip-path: polygon(0 90%, 30% 60%, 40% 75%, 60% 30%, 75% 50%, 100% 10%, 100% 100%, 0 100%);
clip-path: polygon(0 90%, 30% 60%, 40% 75%, 60% 30%, 75% 50%, 100% 10%, 100% 100%, 0 100%);
}
#wrap {
position: relative;
}
<div id="wrap">
<div id="a">
<img width="300" height="300" src ="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/darwin300.jpg" />
</div>
<div id="b">
<img width="300" height="300" src ="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/puppyBeagle300.jpg" />
</div>
</div>

Related

Border-radius failed to round clipped div corners [duplicate]

I want to be able to round out the 3 leftmost corners on this shape that I have created, any idea how that can be done?
div {
position: absolute;
z-index: 1;
width: 423px;
height: 90px;
background-color: #b0102d;
color: white;
right: 0;
margin-top: 10vw;
-webkit-clip-path: polygon(100% 0%, 100% 50%, 100% 100%, 25% 100%, 0% 50%, 25% 0%);
clip-path: polygon(100% 0%, 100% 50%, 100% 100%, 25% 100%, 0% 50%, 25% 0%);
}
<div></div>
use inset with round property :
inset(0% 45% 0% 45% round 10px)
An SVG filter can round any kind of clip-path. You simply need to apply it to a parent element. Adjust the stdDeviation to control the radius:
.box {
width: 423px;
height: 90px;
background-color: #b0102d;
color: white;
clip-path: polygon(100% 0%, 100% 50%, 100% 100%, 25% 100%, 0% 50%, 25% 0%);
}
.parent {
filter: url('#goo');
overflow:hidden;
position: fixed;
right:-50px;
z-index: 1;
margin-top: 10vw;
}
<div class="parent">
<div class="box"></div>
</div>
<svg style="visibility: hidden; position: absolute;" width="0" height="0" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter id="goo"><feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur" />
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9" result="goo" />
<feComposite in="SourceGraphic" in2="goo" operator="atop"/>
</filter>
</defs>
</svg>
Related: https://stackoverflow.com/a/65485455/8620333
I've recently found success experimenting with approaches like this...
SVG
<svg width="0" height="0">
<defs>
<clipPath id="clipped">
<circle cx="var(--myRad)" cy="var(--myRad)" r="var(--myRad)"></circle>
<circle cx="var(--myRad)" cy="calc(var(--myHeight) - var(--myRad))" r="var(--myRad)"></circle>
<circle cx="calc(var(--myWidth) - var(--myRad))" cy="calc(var(--myHeight) - var(--myRad))" r="var(--myRad)"></circle>
<circle cx="calc(var(--myWidth) - var(--myRad))" cy="var(--myRad)" r="var(--myRad)"></circle>
<rect y="var(--myRad)" width="var(--myWidth)" height="calc(var(--myHeight) - (2 * var(--myRad)))"></rect>
<rect x="var(--myRad)" width="calc(var(--myWidth) - (2 * var(--myRad)))" height="var(--myHeight)"></rect>
</clipPath>
</defs>
</svg>
CSS
.clipped {
--myWidth: 100vw;
--myHeight: 10rem;
--myRad: 2rem;
clip-path: url(#clipped);
}
I found this useful as compared to using border-radius with overflow set to hidden, because this approach doesn't create a BFC or break things like sticky position and css perspective effects. Also, this allows you to "inset" the position of the svg paths to clip inside the element with a "corner-radius" if you want.
You can also mess around with the circle to get some different effects.
-webkit-clip-path: circle(60.0% at 50% 10%);
clip-path: circle(50.0% at 50% 50%);
Codepen
Too bad you can't combine the polygon and circle... or maybe you can and I haven't played around with it enough to figure it out. HTH
clip-path: inset(45% 0% 33% 10% round 10px)
I don't have a comment option yes, so I'm writing it as an answer..
you need to write as many points as possible to round the corner. Nothig else...
for, example a few more points to make lower part bit rounder:
-webkit-clip-path: polygon(100% 0%, 100% 100%, 100% 100%, 25% 100%, 5% 70%,1% 60%, 0% 50%, 25% 0%);
oh, yes, or SVG as comment people here.. :)
You could use a child element and do a nested clip-path on that and the child's pseudo element. The parent will do a polygon clip on the shape first, then the pseudo will have an ellipse to round the borders. The clips will have a combined effect.
.parent, .parent div, .parent div:before {
width: 423px;
height: 90px;
position: absolute;
}
.parent {
right: 0;
background-image: linear-gradient(to right, transparent 210px, #b0102d 210px);
margin-top: 15vh;
}
.parent div {
clip-path: polygon(100% 0%, 100% 100%, 25% 100%, 0 50%, 25% 0);
}
.parent div:before {
content: "";
background-color: #b0102d;
clip-path: ellipse(200px 45px at 210px);
}
<div class="parent">
<div></div>
</div>
Here is the demo with some adaptations to illustrate what's going on:
.parent, .parent div, .parent div:before {
width: 423px;
height: 90px;
position: absolute;
}
.parent {
right: 0;
background-image: linear-gradient(to right, transparent 210px, yellow 210px);
margin-top: 15vh;
}
.parent div {
background-color: blue;
clip-path: polygon(90% 0%, 90% 100%, 25% 100%, 0 50%, 25% 0);
}
.parent div:before {
content: "";
background-color: #b0102d;
clip-path: ellipse(200px 45px at 210px);
}
<div class="parent">
<div></div>
</div>
The horizontal size and position of the ellipse can be used to get a different effect on the edges. Note that the background starting postion of the parent needs to be adjusted to the same value as the placement of the ellipse (last value in the clip-path) because it fills up whatever gets clipped off on the right side. This can be visualised by removing background-color: blue from .parent div in the second demo.
Here is an additional Codepen to to try it out.

A part of the abolute image on the clip path div is cut off [duplicate]

Is there any way to prevent clip-path from clipping its children? For example, consider the following code:
.el {
width: 300px;
height: 300px;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
background-color: orangered;
}
h1 {
position: relative;
z-index: 100;
}
<div class="el">
<h1>Work Hard, Play Hard</h1>
</div>
Codepen
Consider pseudo element:
.el {
width: 300px;
height: 300px;
position:relative;
z-index:0;
}
.el::before {
content:"";
position:absolute;
z-index:-1;
top:0;
left:0;
right:0;
bottom:0;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
background-color: orangered;
}
<div class="el">
<h1>Work Hard, Play Hard</h1>
</div>
Essentially what
Nick A said:
The clip path essentially chops off parts of the div, because the
header is inside the div it will inherently be clipped, it may be
easier to draw a hexagon inside the div using svg instead
Having something be the child of something that is disappearing... but you want that to appear, doesn't make too much sense.
Instead, place the thing you want to be shown outside the thing that is disappearing... that way it doesn't disappear/get clipped.
This is like ram vinoth said.
The problem is that you clipped a rectangular div into an hexagon, which hid the child elements. Try using SVG:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="300" xmlns:xlink="http://www.w3.org/1999/xlink">
<polygon class="hex" points="300,150 225,280 75,280 0,150 75,20 225,20" fill="orangered" transform="translate(10)"></polygon>
<text x="160" y="160"
font-size="30"
text-anchor="middle"
>
Any Text Here
</text>
</svg>

Prevent CSS clip-path from clipping children?

Is there any way to prevent clip-path from clipping its children? For example, consider the following code:
.el {
width: 300px;
height: 300px;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
background-color: orangered;
}
h1 {
position: relative;
z-index: 100;
}
<div class="el">
<h1>Work Hard, Play Hard</h1>
</div>
Codepen
Consider pseudo element:
.el {
width: 300px;
height: 300px;
position:relative;
z-index:0;
}
.el::before {
content:"";
position:absolute;
z-index:-1;
top:0;
left:0;
right:0;
bottom:0;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
background-color: orangered;
}
<div class="el">
<h1>Work Hard, Play Hard</h1>
</div>
Essentially what
Nick A said:
The clip path essentially chops off parts of the div, because the
header is inside the div it will inherently be clipped, it may be
easier to draw a hexagon inside the div using svg instead
Having something be the child of something that is disappearing... but you want that to appear, doesn't make too much sense.
Instead, place the thing you want to be shown outside the thing that is disappearing... that way it doesn't disappear/get clipped.
This is like ram vinoth said.
The problem is that you clipped a rectangular div into an hexagon, which hid the child elements. Try using SVG:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="300" xmlns:xlink="http://www.w3.org/1999/xlink">
<polygon class="hex" points="300,150 225,280 75,280 0,150 75,20 225,20" fill="orangered" transform="translate(10)"></polygon>
<text x="160" y="160"
font-size="30"
text-anchor="middle"
>
Any Text Here
</text>
</svg>

CSS Shape-outside with two images

I'm trying to get two images float next to each other with the shape-outside property. To be specific, I have two triangle-shaped .png images, which would make up a rectangle if put next to each other. Img1 should be on the left and Img2 on the right, and they're cut so that the diagonal goes from top right to bottom left.
With shape-outside I managed to get the text "hug" the diagonal border of the images, so something is working alright. The darn images just won't pop next to each other.
<style>
.myclass {
height: auto;
width: 100%;
}
.myclass img:first-child {
-webkit-shape-outside: polygon(0 0, 0 100%, 100% 0);
float: left;
width: 80%;
background-color: lightgray;
-webkit-clip-path: polygon(0 0, 0 100%, 100% 0);
}
.myclass img:nth-child(2) {
-webkit-shape-outside: polygon(100% 0, 100% 100%, 0 100%);
float: right;
width: 80%;
background-color: lightgray;
-webkit-clip-path: polygon(100% 0, 100% 100%, 0 100%);
}
</style>
<div class='myclass'>
<img src='img/img1.png'>
<img src='img/img2.png'>
</div>
<div class='myclass'>
<img src='img/img1.png'>
<img src='img/img2.png'>
</div>
I realize that I could achieve this by doing the image in Photoshop but for linking functionality I'd prefer them to stay as separate elements. Also, absolute positioning could work but that would require quite a lot of media queries as I want the site to be responsive and the amount of this kind of blocks varies and can be quite a few.
Reason:
The reason why they images don't pop up next to each other is because they both have width: 80%.
The clip-path applied to the element will clip into the required shape and the shape-outside setting will make inline text wrap around based on the shape but neither of them will change the shape of the bounding box of the img elements (which will remain square/rectangular).
In the below screenshot, the darker overlay (on top of the image) is the shape created through shape outside setting whereas the lighter overlay (it is there above the image also but is invisible due to the darker overlay's presence) represents the bounding box of the element and it is still rectangular.
This means that both the images can't be placed on the same line and so the second will automatically get pushed below. They can never be set on the same line as long as their combined width is > 100%. When the width of the two img elements is set to 50%, we can see that both of them appear on same line and that inline text wraps around in accordance with the shape on either side. So, there is nothing actually wrong with the shape-outside or the clip-path.
.myclass {
clear: both;
height: auto;
width: 100%;
}
.myclass img:first-child {
float: left;
width: 50%;
background-color: lightgray;
-webkit-shape-outside: polygon(0% 0%, 0% 100%, 100% 0%);
-webkit-clip-path: polygon(0% 0%, 0% 100%, 100% 0%);
}
.myclass img:nth-child(2) {
float: right;
width: 50%;
background-color: lightgray;
-webkit-shape-outside: polygon(100% 0%, 100% 100%, 0% 100%);
-webkit-clip-path: polygon(100% 0%, 100% 100%, 0% 100%);
}
<div class='myclass'>
<img src='http://lorempixel.com/800/200/nature/1'>
<img src='http://lorempixel.com/800/200/nature/2'>
Lorem ipsum dolor sit amet, some very lengthy inline content which is added to demonstrate how the content wraps around in accordance with the shapes.
</div>
Solution 1: Absolute Positioning
One solution to your problem is to use absolute positioning (I did see your statement in question but it is still an option). But then there is no need for shape-outside property itself because there is nothing to wrap around.
.myclass {
position: relative;
height: auto;
width: 100%;
}
.myclass img:first-child {
position: absolute;
width: 100%;
background-color: lightgray;
-webkit-clip-path: polygon(0% 0%, 0% 100%, 100% 0%);
}
.myclass img:nth-child(2) {
position: absolute;
right: 0px;
width: 100%;
background-color: lightgray;
-webkit-clip-path: polygon(100% 0%, 100% 100%, 0% 100%);
}
<div class='myclass'>
<img src='http://lorempixel.com/800/200/nature/1'>
<img src='http://lorempixel.com/800/200/nature/2'>
</div>
Solution 2: SVG recommended
As Paulie_D had mentioned in comments, your best bet would be to use SVG if you don't want to use absolute positioning. While we would still need to give (x,y) coordinates for the image tags as though we are doing absolute positioning, SVGs are by default responsive (they auto-adapt) and so the need for responsiveness is handled implicitly.
svg image:nth-of-type(1) {
-webkit-clip-path: url(#clipper-left);
clip-path: url(#clipper-left);
}
svg image:nth-of-type(2) {
-webkit-clip-path: url(#clipper-right);
clip-path: url(#clipper-right);
}
svg {
width: 100vw;
height: 40vh;
}
body {
margin: 0;
padding: 0;
}
<svg viewBox='0 0 900 400' preserveAspectRatio="none">
<defs>
<clipPath clipPathUnits="objectBoundingBox" id="clipper-left">
<path d="M0,0 1,0 0,1z" />
</clipPath>
<clipPath clipPathUnits="objectBoundingBox" id="clipper-right">
<path d="M1,0 0,1 1,1z" />
</clipPath>
</defs>
<image xlink:href="http://lorempixel.com/900/400/nature/1" x="0" y="0" height="400px" width="900px" />
<image xlink:href="http://lorempixel.com/900/400/nature/2" x="0" y="0" height="400px" width="900px" />
</svg>

SVG scaling with parent container

I'm trying to adjust a triangle that will cover 50% of the parent container from corner to corner, no matter what the ratios of the box are the triangle can be skewered.
<div class="container">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="xMinYMin meet" viewBox="0,0,100,100">
<polygon points="100,100 100,0 0,100"/>
</svg>
</div>
.container {
height:160px;
background-color:#ccc;
margin-top:10px;
}
I've setup a fiddle with the code, I'm trying to replicate the same behaviour that I was able to achieve with css, the reason why I want to go the route of svg is to stop the line from getting pixelation this is the previous code of css.
How to achieve the same result in css
<div class="parent">
<div class="arrow-right"></div>
</div>
.parent {
position:relative;
width:230px;
height:150px;
background-color:red;
}
.arrow-right {
position: absolute;
width: 100%;
height: 100%;
left: 0px;
bottom: 0px;
background: linear-gradient(to left top, #333 50%, transparent 50%);
opacity: 0.5;
}
How do I change the viewBox to allow for the polygon shape to not stay in proportion?
You need to add preserveAspectRatio="none" and stretch svg svg {width:100%; height:100%}
fiddle