CSS Shape-outside with two images - html

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>

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>

Stacking masked divs' contents with SVG

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>

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>

How to accomplish this shape with angled cuts at the bottom and an image background in CSS?

I have read up on various methods and played with the Clippy tool, the problem is the browser support just isn't there yet. What would be the best method for accomplishing the look of the image below with CSS? I am trying to add a shape as bottom-border as you can see in the image below right after the blue background image. Is there a way I can do this that most recent major browsers support through CSS?
What I've tried (doesn't seem to work in Chrome and others):
.element {
-webkit-clip-path: polygon(50% 0%, 100% 0, 100% 86%, 75% 100%, 0 85%, 0 0);
clip-path: polygon(50% 0%, 100% 0, 100% 86%, 75% 100%, 0 85%, 0 0);
}
The desired result would look something like:
Both dippas' answer and the demo in misterManSam's comment are good but they would work only if the page background is a solid color (which can then be used as border's color or within the gradient). They would run into problems when the page's background is either an image (or) a gradient and they should show through the cutout portion of the shape.
For such cases I would recommend using SVG instead of CSS because it is so complex to create it with CSS that it is not actually worth the effort. Though you've asked for CSS, I will detail these SVG methods here just in case you want to use them (or atleast some future readers might find it helpful).
With SVG:
With SVG we can either create a path and fill it with the image (or) use a SVG mask for creating the shape. (Note: CSS clip-path using SVG is still a no-go due to lack of support in IE.)
Below snippet uses SVG path element to create the shape and then fill it with the image.
svg {
height: 200px;
width: 100%;
}
path {
fill: url(#image);
}
/* Just for demo */
path:hover{
cursor: pointer;
}
body {
min-height: 100vh;
background-image: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
<svg viewBox='0 0 1024 200' preserveAspectRatio='none'>
<defs>
<pattern id='image' height='200' width='1024' patternUnits='userSpaceOnUse'>
<image xlink:href='http://lorempixel.com/1024/200/nature/3' height='200' width='1024' />
</pattern>
</defs>
<path d='M0,0 1024,0 1024,150 716.8,200 0,150z' />
</svg>
The following snippet uses SVG mask. The difference between using a path with an image fill and a mask is the hover area. With a path the hover effects are restricted to the shape boundary whereas with a mask, the image is still a rectangle (or square) and so hover effects are triggered even outside.
svg {
height: 200px;
width: 100%;
}
image {
mask: url(#masker);
}
/* Just for demo */
image:hover{
cursor: pointer;
}
body {
min-height: 100vh;
background-image: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
<svg viewBox='0 0 1024 200' preserveAspectRatio='none'>
<defs>
<mask id='masker' x='0' y='0' width='1024' height='200'>
<polygon points='0,0 1024,0 1024,200 0,200z' fill="#fff" />
<path d='M0,150 716.8,200 1024,150 1024,200 0,200z' fill="#000" />
</mask>
</defs>
<image xlink:href='http://lorempixel.com/1024/200/nature/3' height='200' width='1024' />
</svg>
With CSS:
The below option is our best bet with pure CSS but unfortunately it has poor browser support. It uses CSS linear-gradient as mask images to hide the portions that are not required. This method works only in Webkit powered browsers for now and so is a no-go.
.shape {
height: 200px;
width: 100%;
background-image: url(http://lorempixel.com/1200/200/nature/3);
-webkit-mask-image: linear-gradient(to top right, transparent 49.5%, white 50.5%), linear-gradient(to top left, transparent 49.5%, white 50.5%), linear-gradient(white, white);
mask-image: linear-gradient(to top right, transparent 49.5%, white 50.5%), linear-gradient(to top left, transparent 49.5%, white 50.5%), linear-gradient(white, white);
-webkit-mask-size: 70.5% 30%, 30% 30%, 100% 70%;
-webkit-mask-position: bottom left, bottom right, top left;
-webkit-mask-repeat: no-repeat;
}
body {
min-height: 100vh;
background-image: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
<div class='shape'></div>
Other attempts to produce a transparent cut run into problems if the shape has to be responsive. For example, the below snippet uses very complex transformations, positioning etc to achieve this shape but it is not responsive (view in full page mode). I wouldn't have recommended this method even if the shape was responsive (due to complexities involved) but the lack of responsiveness means this is a no-go.
.shape {
position: relative;
height: 200px;
width: 100%;
overflow: hidden;
}
.shape-left,
.shape-right,
.shape img {
position: absolute;
height: 100%;
}
.shape-left {
width: 75%;
transform: skewY(5deg);
overflow: hidden;
}
.shape-left img {
top: -7%;
bottom: 0px;
width: 133.3%;
transform: skewY(-5deg);
}
.shape-left,
.shape-left img {
transform-origin: bottom right;
backface-visibility: hidden;
}
.shape-right {
right: 0%;
width: 25%;
transform: skewY(-10deg);
overflow: hidden;
}
.shape-right img {
top: -13.5%;
left: -300%;
width: 400%;
transform: skewY(10deg);
}
.shape-right {
transform-origin: bottom left;
backface-visibility: hidden;
}
/* just for demo */
.reference {
height: 200px;
width: 100%;
}
.reference img {
height: 100%;
width: 100%;
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
background-image:radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
<div class='shape'>
<div class='shape-left'>
<img src='http://lorempixel.com/800/200/nature/3' />
</div>
<div class='shape-right'>
<img src='http://lorempixel.com/800/200/nature/3' />
</div>
</div>
<hr>
<div class='reference'>
<img src='http://lorempixel.com/800/200/nature/3' />
</div>
Note: This may have been the item that misterManSam was referring to in comments but I feel the needs are a bit different here even though both involve creating unusual borders.
you can use a background-image on a div and two shapes using it pseudo-selectors :before/:after
Something like this:
.bg {
background: url(http://lorempixel.com/1600/900) no-repeat center top;
min-height: 100px;
min-width: 200px;
position: relative
}
.bg:before {
content: "";
border-bottom: 65px solid white;
border-right: 575px solid rgba(0, 0, 0, 0);
position: absolute;
left: 0;
bottom: 0;
}
.bg:after {
content: "";
border-bottom: 65px solid white;
border-left: 200px solid rgba(0, 0, 0, 0);
position: absolute;
right: 0;
bottom: 0;
}
<div class="bg"></div>
I think the easiest way to do it is with pseudo elements on the parent div element. This is basic CSS knowledge and can be implemented very easily. The parent div needs to have the position: relative; property set and the rest is done by the ::before and ::after elements.
.background::before {
transform: rotate(10deg);
position: absolute;
}
Example
Hope this helps.