Related
I have a project where I need to insert speech bubbles / message boxes. The general shape I am trying to achieve is this one :
.bubble {
height: 100px;
width: 200px;
border: 3px solid gray;
background: lightgray;
position: relative;
cursor:pointer;
}
.triangle {
width: 0;
border-top: 20px solid black;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
cursor:pointer;
}
<div class="bubble">Speech bubble
</div>
<div class="triangle">
</div>
This currently does not pass a hit-test as the transparent border is also clickable.
Objectives
The hit box (clickable / hoverable areas) needs to stick to the shape's boundaries (the transparent borders here are also hoverable, invalidating this).
I need to display the shape over various content (images, gradents, text...),
Issues
The main issues I am having when manipulating this shape are:
Have the ability to move the triangle around the speech bubble according to the position of the element it refers to (top/left/right/bottom sides)
adding a border or box shadow around it when emphasis is needed
Is there anyway of addressing these issues?
In order to achieve this, you should consider altering your markup in order to make your html more efficient. This can be achieved using a pseudo element. I'll address each point individually, and put it all together at the end of my answer.
First of all,
Use pseudo elements to avoid extra elements
You could use a pseudo element to remove the extra .triangle div. This not only reduces your div numbers, but also helps with positioning as you can use the top: left: right: and bottom: css properties in order to position according to your main element. This can be seen below:
.oneAndOnlyDiv {
height: 100px;
width: 200px;
border: 3px solid gray;
background: lightgray;
position: relative;
}
.oneAndOnlyDiv:before {
content: "";
position: absolute;
top: 100%;
left: 20px;
width: 0;
border-top: 20px solid black;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
}
<div class="oneAndOnlyDiv">Main div</div>
Hit testing
In order to create your "hit test", you may wish to use a rotated element instead of a border hack.
Something like:
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
}
div:before {
content: "";
position: absolute;
top: 100%;
left: 20px;
height: 20px;
width: 20px;
background: black;
transform: rotate(45deg);
transform-origin:top right;
}
<div>Only element</div>
or use a skewed pseudo element:
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
}
div:before {
content: "";
position: absolute;
top: 90%;
left: 20px;
height: 30%;
width: 20px;
background: black;
transform: skewY(-45deg);
transform-origin:bottom left;
z-index:-1;
}
<div>Only element</div>
which will show the pointer only when the square or main element is hovered.
But hang on, that messes up the positioning? how can you deal with that?
There are a few solutions to that. One of which is to use the calc CSS property.
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
}
div:before {
content: "";
position: absolute;
top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
top: calc(100% - 10px); /*i.e. half the height*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
}
<div>Only element</div>
Adding a border
You can add a border quite easily now, simply by adding a border declaration to the main element, and setting the border-bottom and border-right of the pseudo element to inherit
Border
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
border:3px double black;
}
div:before {
content: "";
position: absolute;
top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
top: calc(100% - 10px); /*i.e. half the height*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
border-bottom:inherit;
border-right:inherit;
box-shadow:inherit;
}
<div>Only element</div>
Box Shadow:
In order to have a box shadow, I've used the :after pseudo element in order to hide the box shadow over the other pseudo, making the element seem as one single element.
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
box-shadow: 5px 5px 10px 2px black;
}
div:before,div:after {
content: "";
position: absolute;
top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
top: calc(100% - 10px); /*i.e. half the height*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
z-index:-1;
box-shadow:inherit;
}
div:after{
box-shadow:none;
z-index:8;
}
<div>Only element</div>
Putting it all together
You can also add a border radius to your message box or speech bubble by again, using the border-radius property:
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
border:3px double black;
border-radius:10px;
}
div:before {
content: "";
position: absolute;
top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
top: calc(100% - 10px); /*i.e. half the height*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
border-bottom:inherit;
border-right:inherit;
box-shadow:inherit;
}
<div>Only element</div>
This even allows you to create not only a triangle, but how about a circle instead?
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
border:3px double black;
border-radius:10px;
}
div:before {
content: "";
position: absolute;
top: -webkit-calc(100% - 13px); /*may require prefix for old browser support*/
top: calc(100% - 13px); /*i.e. half the height + border*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
border:3px double transparent;
border-bottom:inherit;
border-right:inherit;
box-shadow:inherit;
border-radius:50%;
}
<div>Only element</div>
If you are having issues with content overflowing and being 'hidden' behind this pseudo element, and you aren't fussed about having a border, you could use a negative z-index which will solve this issue.
Don't like using 'magic numbers'?
If you don't like the idea of using a calc value, in which the positioning in my answer is currently using (whilst working), you may wish to use transform:translate(50%)
This would be a much better approach, since:
You do not need to know the size of the border, nor half the width
You will be making your message box/ bubble a lot more dynamic in its positioning, and would support further sizings.
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor: pointer;
border: 3px double black;
border-radius: 10px;
}
div:before {
content: "";
position: absolute;
top: 100%;
left: 30px;
height: 20px;
width: 20px;
background: gray;
box-sizing:border-box;
transform: rotate(45deg) translate(-50%);
border-bottom: inherit;
border-right: inherit;
box-shadow: inherit;
}
<div>Only element</div>
Want to move it? You can!
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor: pointer;
border: 3px double black;
border-radius: 10px;
}
div:before {
content: "";
position: absolute;
top: 100%;
left: 10%;
height: 20px;
width: 20px;
background: gray;
box-sizing: border-box;
transform: rotate(45deg) translate(-50%);
border-bottom: inherit;
border-right: inherit;
box-shadow: inherit;
transition: all 0.8s;
}
div:hover:before {
left: 90%;
}
<div>Only element</div>
Want it one the right?
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor: pointer;
border: 3px double black;
border-radius: 10px;
}
div:before {
content: "";
position: absolute;
top: 15%;
left: 100%;
height: 20px;
width: 20px;
background: gray;
box-sizing:border-box;
transform: rotate(45deg) translate(-50%);
border-top: inherit;
border-right: inherit;
box-shadow: inherit;
transition:all 0.8s;
}
div:hover:before{
top:80%;
}
<div>Only Element</div>
Want it to be a different shape of triangle?
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor: pointer;
border-radius: 10px;
}
div:before {
content: "";
position: absolute;
top: 70%;
left: 100%;
height: 20px;
width: 20px;
background: gray;
box-sizing:border-box;
transform: translate(-50%) skewX(45deg);
box-shadow: inherit;
transition:all 0.8s;
z-index:-1;
}
div:hover:before{
transform: translate(-50%);
border-radius:50%;
top:20%;
}
<div>Only Element</div>
We can rely on clip-path and drop-shadow filter to easily achieve this:
.box {
margin: 50px;
width: 200px;
height: 100px;
border-radius: 15px;
background: red;
position: relative;
filter: /* the more shadow you add the thicker the border will be */
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green);
}
.box::before {
content: "";
position: absolute;
top: 100%;
left: 20%;
height: 30px;
width: 50px;
background: inherit;
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.box:hover {
background:blue;
}
body {
background:linear-gradient(to right, pink,grey);
}
<div class="box"></div>
We can extend this basic example to consider any kind of position and triangle shape:
.box {
margin: 30px;
width: 150px;
height: 80px;
display:inline-block;
border-radius: 15px;
background: red;
position: relative;
filter: /* the more shadow you add the thicker the border will be */
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green);
}
.box::before {
content: "";
position: absolute;
height: var(--h,20px);
width: var(--w,30px);
background: inherit;
transform:scale(var(--x,1),var(--y,1));
}
.box.p-bottom::before{
top: 100%;
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.box.p-bottom.alt::before{
clip-path: polygon(0 0, 100% 0, 100% 100%);
}
.box.p-top::before{
bottom: 100%;
clip-path: polygon(0 100%, 100% 100%, 50% 0);
}
.box.p-top.alt::before{
clip-path: polygon(0 100%, 100% 100%, 100% 0);
}
.box.p-left::before{
right: 100%;
clip-path: polygon(100% 0, 100% 100%,0% 50%);
}
.box.p-left.alt::before{
clip-path: polygon(100% 0, 100% 100%,0% 100%);
}
.box.p-right::before{
left: 100%;
clip-path: polygon(0% 0, 0% 100%,100% 50%);
}
.box.p-right.alt::before{
clip-path: polygon(0% 0, 0% 100%,100% 100%);
}
.box.right::before{
right:var(--p,20px);
}
.box.left::before {
left:var(--p,20px);
}
.box.top::before{
top:var(--p,20px);
}
.box.bottom::before {
bottom:var(--p,20px);
}
.box:hover {
background:blue;
}
body {
background:linear-gradient(to right, pink,grey);
}
<div class="box p-bottom right"></div>
<div class="box p-bottom right alt"></div>
<div class="box p-bottom right alt" style="--x:-1"></div>
<div class="box p-top left"></div>
<div class="box p-top right" style="--p:40%"></div>
<div class="box p-top right alt" style="--p:40%"></div>
<div class="box p-left top"></div>
<div class="box p-left top alt"></div>
<div class="box p-right bottom" style="--w:20px;"></div>
<div class="box p-right bottom" style="--p:30px;--w:20px;--h:30px"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px;--y:-1"></div>
We can also consider any kind of background for the whole shape. The trick work for a fixed width/height. The idea is to create a background having the same size for both the main and pseudo element then we simply adjust the position of the one inside the pseudo element to match the one of the parent (to have a perfect overlap)
.box {
--h:20px;
--w:30px;
--p:20px;
margin: 30px;
width: 150px;
height: 80px;
display:inline-block;
border-radius: 15px;
background:
var(--back,linear-gradient(45deg,red,purple))
center/
calc(150px + 2*var(--w)) calc(80px + 2*var(--h));
position: relative;
filter: /* the more shadow you add the thicker the border will be */
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green);
}
.box::before {
content: "";
position: absolute;
height: var(--h);
width: var(--w);
background: inherit;
transform:scale(var(--x,1),var(--y,1));
background-position:var(--b1) 0 var(--b2);
}
.box.p-bottom::before{
top: 100%;
clip-path: polygon(0 0, 100% 0, 50% 100%);
--b1:bottom;
}
.box.p-bottom.alt::before{
clip-path: polygon(0 0, 100% 0, 100% 100%);
}
.box.p-top::before{
bottom: 100%;
clip-path: polygon(0 100%, 100% 100%, 50% 0);
--b1:top;
}
.box.p-top.alt::before{
clip-path: polygon(0 100%, 100% 100%, 100% 0);
}
.box.p-left::before{
right: 100%;
clip-path: polygon(100% 0, 100% 100%,0% 50%);
--b1:left;
}
.box.p-left.alt::before{
clip-path: polygon(100% 0, 100% 100%,0% 100%);
}
.box.p-right::before{
left: 100%;
clip-path: polygon(0% 0, 0% 100%,100% 50%);
--b1:right;
}
.box.p-right.alt::before{
clip-path: polygon(0% 0, 0% 100%,100% 100%);
}
.box.right::before{
right:var(--p);
--b2:right calc(-1*var(--p) - var(--w));
}
.box.left::before {
left:var(--p);
--b2:left calc(-1*var(--p) - var(--w));
}
.box.top::before{
top:var(--p);
--b2:top calc(-1*var(--p) - var(--h));
}
.box.bottom::before {
bottom:var(--p);
--b2:bottom calc(-1*var(--p) - var(--h));
}
body {
background:linear-gradient(to right, pink,grey);
}
<div class="box p-bottom right"></div>
<div class="box p-bottom right alt" style="--back:url(https://picsum.photos/id/15/400/300)"></div>
<div class="box p-bottom right alt" style="--x:-1;--back:red"></div>
<div class="box p-top left" style="--back:url(https://picsum.photos/id/18/400/300)"></div>
<div class="box p-top right" style="--p:40px;--back:url(https://picsum.photos/id/1018/400/300)"></div>
<div class="box p-top right alt" style="--p:60px;--back:radial-gradient(red,pink,yellow)"></div>
<div class="box p-left top" style="--back:black"></div>
<div class="box p-left top alt" style="--back:repeating-linear-gradient(45deg,#fff 0 10px,orange 0 20px)"></div>
<div class="box p-right bottom" style="--w:20px;--back:linear-gradient(red,pink,yellow)"></div>
<div class="box p-right bottom" style="--p:30px;--w:20px;--h:30px;--back:repeating-radial-gradient(#fff 0 10px,orange 0 20px)"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px;--back:conic-gradient(red,pink,yellow,red)"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px;--y:-1;"></div>
SVG
This does not pass a hit-test as the transparent border is also clickable
This can be done using the pointer-events in svg.
pointer-events:visibleFill; Will only select the part where there is paint.
This example uses filter_box-shadow and is not supported by IE.
Also uses two shapes.
html,
body {
margin: 0;
padding: 0;
}
.bubble {
width: 150px;
height: 150px;
-webkit-filter: drop-shadow(5px 5px 0px #aaa);
filter: drop-shadow(5px 5px 0px #aaa);
}
.bubble-shape {
fill: #1e1;
}
.shape-text {
color: black;
}
<svg class="bubble" viewBox="0 0 110 110" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
<g class="bubble-shape" style="cursor:pointer; pointer-events:visibleFill;">
<rect x="10" y="10" width="90" height="90" rx="15" ry="15" />
<polygon points="20,94 40,94 30,105" />
</g>
</svg>
This example uses one path
Should be fully supported by IE.
html,
body {
margin: 0;
padding: 0;
}
.bubble {
width: 150px;
height: 150px;
}
.bubble-shape {
stroke-width: 15;
stroke: #ddd;
fill: #1e1;
}
.shape-text {
color: black;
}
<svg class="bubble" viewBox="-70 -10 390 370" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
<g style="cursor:pointer; pointer-events:visible;">
<path class="bubble-shape" d="m 0,0 250,0 c 25,0 50,20 50,50 l 0,225 c 0,25 -25,50 -50,50 l -175,0 -25,20 -20,-20 -40,0 c -25,0 -50,-25 -50,-50 l 0,-225 C -50,25 -50,0 0,0 Z" />
</g>
</svg>
How can I use the blue circle as a value of the progress bar with only using CSS?
The progress bar itself is only 2 pixel height, and the circle that represents the value of the progress bar is much bigger.
.circle {
width: 20px;
height: 20px;
border: 2px solid blue;
border-radius: 50%;
}
progress {
background-color: #9da29b;
border: 0;
height: 2px;
width: 100%;
margin-top: 5em;
}
progress::-webkit-progress-bar {
background-color: #9da29b;
}
progress::-webkit-progress-value {
background-color: #f01010;
}
progress::-moz-progress-bar {
background-color: #9da29b;
}
#this-is-not-that-i-want {
background-color: #9da29b;
border: 0;
height: 30px;
width: 100%;
margin-top: 5em;
}
#this-is-not-that-i-want::-webkit-progress-value {
background-color: #f01010;
background-image: radial-gradient(circle at calc(100% - 30px) center, black 15px, lightgreen 15px);
}
#this-is-not-that-i-want::progress-value {
background-image: radial-gradient(circle at calc(100% - 30px) center, black 15px, lightgreen 15px);
}
}
<div class="circle"></div>
<progress id="it-is-smaller-than-the-circle" value="60" max="100"></progress>
<progress id="this-is-not-that-i-want" value="60" max="100"></progress>
https://jsfiddle.net/mg5Lvx5z/1/
If I understand you correctly, you can use :after to create a psuedo element inside the progress bar, then position it to the progress level.
#it-is-smaller-than-the-circle::-webkit-progress-value {position: relative;}
#it-is-smaller-than-the-circle::-webkit-progress-value:after {
content:'';
display:block;
width:20px;
height:20px;
border:2px solid blue;
border-radius: 50%;
position:absolute;
left:100%;
margin:-10px 0 0 -10px;
}
working fiddle https://jsfiddle.net/mg5Lvx5z/2/
I have a project where I need to insert speech bubbles / message boxes. The general shape I am trying to achieve is this one :
.bubble {
height: 100px;
width: 200px;
border: 3px solid gray;
background: lightgray;
position: relative;
cursor:pointer;
}
.triangle {
width: 0;
border-top: 20px solid black;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
cursor:pointer;
}
<div class="bubble">Speech bubble
</div>
<div class="triangle">
</div>
This currently does not pass a hit-test as the transparent border is also clickable.
Objectives
The hit box (clickable / hoverable areas) needs to stick to the shape's boundaries (the transparent borders here are also hoverable, invalidating this).
I need to display the shape over various content (images, gradents, text...),
Issues
The main issues I am having when manipulating this shape are:
Have the ability to move the triangle around the speech bubble according to the position of the element it refers to (top/left/right/bottom sides)
adding a border or box shadow around it when emphasis is needed
Is there anyway of addressing these issues?
In order to achieve this, you should consider altering your markup in order to make your html more efficient. This can be achieved using a pseudo element. I'll address each point individually, and put it all together at the end of my answer.
First of all,
Use pseudo elements to avoid extra elements
You could use a pseudo element to remove the extra .triangle div. This not only reduces your div numbers, but also helps with positioning as you can use the top: left: right: and bottom: css properties in order to position according to your main element. This can be seen below:
.oneAndOnlyDiv {
height: 100px;
width: 200px;
border: 3px solid gray;
background: lightgray;
position: relative;
}
.oneAndOnlyDiv:before {
content: "";
position: absolute;
top: 100%;
left: 20px;
width: 0;
border-top: 20px solid black;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
}
<div class="oneAndOnlyDiv">Main div</div>
Hit testing
In order to create your "hit test", you may wish to use a rotated element instead of a border hack.
Something like:
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
}
div:before {
content: "";
position: absolute;
top: 100%;
left: 20px;
height: 20px;
width: 20px;
background: black;
transform: rotate(45deg);
transform-origin:top right;
}
<div>Only element</div>
or use a skewed pseudo element:
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
}
div:before {
content: "";
position: absolute;
top: 90%;
left: 20px;
height: 30%;
width: 20px;
background: black;
transform: skewY(-45deg);
transform-origin:bottom left;
z-index:-1;
}
<div>Only element</div>
which will show the pointer only when the square or main element is hovered.
But hang on, that messes up the positioning? how can you deal with that?
There are a few solutions to that. One of which is to use the calc CSS property.
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
}
div:before {
content: "";
position: absolute;
top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
top: calc(100% - 10px); /*i.e. half the height*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
}
<div>Only element</div>
Adding a border
You can add a border quite easily now, simply by adding a border declaration to the main element, and setting the border-bottom and border-right of the pseudo element to inherit
Border
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
border:3px double black;
}
div:before {
content: "";
position: absolute;
top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
top: calc(100% - 10px); /*i.e. half the height*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
border-bottom:inherit;
border-right:inherit;
box-shadow:inherit;
}
<div>Only element</div>
Box Shadow:
In order to have a box shadow, I've used the :after pseudo element in order to hide the box shadow over the other pseudo, making the element seem as one single element.
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
box-shadow: 5px 5px 10px 2px black;
}
div:before,div:after {
content: "";
position: absolute;
top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
top: calc(100% - 10px); /*i.e. half the height*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
z-index:-1;
box-shadow:inherit;
}
div:after{
box-shadow:none;
z-index:8;
}
<div>Only element</div>
Putting it all together
You can also add a border radius to your message box or speech bubble by again, using the border-radius property:
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
border:3px double black;
border-radius:10px;
}
div:before {
content: "";
position: absolute;
top: -webkit-calc(100% - 10px); /*may require prefix for old browser support*/
top: calc(100% - 10px); /*i.e. half the height*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
border-bottom:inherit;
border-right:inherit;
box-shadow:inherit;
}
<div>Only element</div>
This even allows you to create not only a triangle, but how about a circle instead?
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor:pointer;
border:3px double black;
border-radius:10px;
}
div:before {
content: "";
position: absolute;
top: -webkit-calc(100% - 13px); /*may require prefix for old browser support*/
top: calc(100% - 13px); /*i.e. half the height + border*/
left: 20px;
height: 20px;
width: 20px;
background: gray;
transform: rotate(45deg);
border:3px double transparent;
border-bottom:inherit;
border-right:inherit;
box-shadow:inherit;
border-radius:50%;
}
<div>Only element</div>
If you are having issues with content overflowing and being 'hidden' behind this pseudo element, and you aren't fussed about having a border, you could use a negative z-index which will solve this issue.
Don't like using 'magic numbers'?
If you don't like the idea of using a calc value, in which the positioning in my answer is currently using (whilst working), you may wish to use transform:translate(50%)
This would be a much better approach, since:
You do not need to know the size of the border, nor half the width
You will be making your message box/ bubble a lot more dynamic in its positioning, and would support further sizings.
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor: pointer;
border: 3px double black;
border-radius: 10px;
}
div:before {
content: "";
position: absolute;
top: 100%;
left: 30px;
height: 20px;
width: 20px;
background: gray;
box-sizing:border-box;
transform: rotate(45deg) translate(-50%);
border-bottom: inherit;
border-right: inherit;
box-shadow: inherit;
}
<div>Only element</div>
Want to move it? You can!
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor: pointer;
border: 3px double black;
border-radius: 10px;
}
div:before {
content: "";
position: absolute;
top: 100%;
left: 10%;
height: 20px;
width: 20px;
background: gray;
box-sizing: border-box;
transform: rotate(45deg) translate(-50%);
border-bottom: inherit;
border-right: inherit;
box-shadow: inherit;
transition: all 0.8s;
}
div:hover:before {
left: 90%;
}
<div>Only element</div>
Want it one the right?
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor: pointer;
border: 3px double black;
border-radius: 10px;
}
div:before {
content: "";
position: absolute;
top: 15%;
left: 100%;
height: 20px;
width: 20px;
background: gray;
box-sizing:border-box;
transform: rotate(45deg) translate(-50%);
border-top: inherit;
border-right: inherit;
box-shadow: inherit;
transition:all 0.8s;
}
div:hover:before{
top:80%;
}
<div>Only Element</div>
Want it to be a different shape of triangle?
div {
height: 100px;
width: 200px;
background: gray;
position: relative;
cursor: pointer;
border-radius: 10px;
}
div:before {
content: "";
position: absolute;
top: 70%;
left: 100%;
height: 20px;
width: 20px;
background: gray;
box-sizing:border-box;
transform: translate(-50%) skewX(45deg);
box-shadow: inherit;
transition:all 0.8s;
z-index:-1;
}
div:hover:before{
transform: translate(-50%);
border-radius:50%;
top:20%;
}
<div>Only Element</div>
We can rely on clip-path and drop-shadow filter to easily achieve this:
.box {
margin: 50px;
width: 200px;
height: 100px;
border-radius: 15px;
background: red;
position: relative;
filter: /* the more shadow you add the thicker the border will be */
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green);
}
.box::before {
content: "";
position: absolute;
top: 100%;
left: 20%;
height: 30px;
width: 50px;
background: inherit;
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.box:hover {
background:blue;
}
body {
background:linear-gradient(to right, pink,grey);
}
<div class="box"></div>
We can extend this basic example to consider any kind of position and triangle shape:
.box {
margin: 30px;
width: 150px;
height: 80px;
display:inline-block;
border-radius: 15px;
background: red;
position: relative;
filter: /* the more shadow you add the thicker the border will be */
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green);
}
.box::before {
content: "";
position: absolute;
height: var(--h,20px);
width: var(--w,30px);
background: inherit;
transform:scale(var(--x,1),var(--y,1));
}
.box.p-bottom::before{
top: 100%;
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.box.p-bottom.alt::before{
clip-path: polygon(0 0, 100% 0, 100% 100%);
}
.box.p-top::before{
bottom: 100%;
clip-path: polygon(0 100%, 100% 100%, 50% 0);
}
.box.p-top.alt::before{
clip-path: polygon(0 100%, 100% 100%, 100% 0);
}
.box.p-left::before{
right: 100%;
clip-path: polygon(100% 0, 100% 100%,0% 50%);
}
.box.p-left.alt::before{
clip-path: polygon(100% 0, 100% 100%,0% 100%);
}
.box.p-right::before{
left: 100%;
clip-path: polygon(0% 0, 0% 100%,100% 50%);
}
.box.p-right.alt::before{
clip-path: polygon(0% 0, 0% 100%,100% 100%);
}
.box.right::before{
right:var(--p,20px);
}
.box.left::before {
left:var(--p,20px);
}
.box.top::before{
top:var(--p,20px);
}
.box.bottom::before {
bottom:var(--p,20px);
}
.box:hover {
background:blue;
}
body {
background:linear-gradient(to right, pink,grey);
}
<div class="box p-bottom right"></div>
<div class="box p-bottom right alt"></div>
<div class="box p-bottom right alt" style="--x:-1"></div>
<div class="box p-top left"></div>
<div class="box p-top right" style="--p:40%"></div>
<div class="box p-top right alt" style="--p:40%"></div>
<div class="box p-left top"></div>
<div class="box p-left top alt"></div>
<div class="box p-right bottom" style="--w:20px;"></div>
<div class="box p-right bottom" style="--p:30px;--w:20px;--h:30px"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px;--y:-1"></div>
We can also consider any kind of background for the whole shape. The trick work for a fixed width/height. The idea is to create a background having the same size for both the main and pseudo element then we simply adjust the position of the one inside the pseudo element to match the one of the parent (to have a perfect overlap)
.box {
--h:20px;
--w:30px;
--p:20px;
margin: 30px;
width: 150px;
height: 80px;
display:inline-block;
border-radius: 15px;
background:
var(--back,linear-gradient(45deg,red,purple))
center/
calc(150px + 2*var(--w)) calc(80px + 2*var(--h));
position: relative;
filter: /* the more shadow you add the thicker the border will be */
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green)
drop-shadow(0px 0px 1px green);
}
.box::before {
content: "";
position: absolute;
height: var(--h);
width: var(--w);
background: inherit;
transform:scale(var(--x,1),var(--y,1));
background-position:var(--b1) 0 var(--b2);
}
.box.p-bottom::before{
top: 100%;
clip-path: polygon(0 0, 100% 0, 50% 100%);
--b1:bottom;
}
.box.p-bottom.alt::before{
clip-path: polygon(0 0, 100% 0, 100% 100%);
}
.box.p-top::before{
bottom: 100%;
clip-path: polygon(0 100%, 100% 100%, 50% 0);
--b1:top;
}
.box.p-top.alt::before{
clip-path: polygon(0 100%, 100% 100%, 100% 0);
}
.box.p-left::before{
right: 100%;
clip-path: polygon(100% 0, 100% 100%,0% 50%);
--b1:left;
}
.box.p-left.alt::before{
clip-path: polygon(100% 0, 100% 100%,0% 100%);
}
.box.p-right::before{
left: 100%;
clip-path: polygon(0% 0, 0% 100%,100% 50%);
--b1:right;
}
.box.p-right.alt::before{
clip-path: polygon(0% 0, 0% 100%,100% 100%);
}
.box.right::before{
right:var(--p);
--b2:right calc(-1*var(--p) - var(--w));
}
.box.left::before {
left:var(--p);
--b2:left calc(-1*var(--p) - var(--w));
}
.box.top::before{
top:var(--p);
--b2:top calc(-1*var(--p) - var(--h));
}
.box.bottom::before {
bottom:var(--p);
--b2:bottom calc(-1*var(--p) - var(--h));
}
body {
background:linear-gradient(to right, pink,grey);
}
<div class="box p-bottom right"></div>
<div class="box p-bottom right alt" style="--back:url(https://picsum.photos/id/15/400/300)"></div>
<div class="box p-bottom right alt" style="--x:-1;--back:red"></div>
<div class="box p-top left" style="--back:url(https://picsum.photos/id/18/400/300)"></div>
<div class="box p-top right" style="--p:40px;--back:url(https://picsum.photos/id/1018/400/300)"></div>
<div class="box p-top right alt" style="--p:60px;--back:radial-gradient(red,pink,yellow)"></div>
<div class="box p-left top" style="--back:black"></div>
<div class="box p-left top alt" style="--back:repeating-linear-gradient(45deg,#fff 0 10px,orange 0 20px)"></div>
<div class="box p-right bottom" style="--w:20px;--back:linear-gradient(red,pink,yellow)"></div>
<div class="box p-right bottom" style="--p:30px;--w:20px;--h:30px;--back:repeating-radial-gradient(#fff 0 10px,orange 0 20px)"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px;--back:conic-gradient(red,pink,yellow,red)"></div>
<div class="box p-right bottom alt" style="--p:30px;--w:20px;--h:30px;--y:-1;"></div>
SVG
This does not pass a hit-test as the transparent border is also clickable
This can be done using the pointer-events in svg.
pointer-events:visibleFill; Will only select the part where there is paint.
This example uses filter_box-shadow and is not supported by IE.
Also uses two shapes.
html,
body {
margin: 0;
padding: 0;
}
.bubble {
width: 150px;
height: 150px;
-webkit-filter: drop-shadow(5px 5px 0px #aaa);
filter: drop-shadow(5px 5px 0px #aaa);
}
.bubble-shape {
fill: #1e1;
}
.shape-text {
color: black;
}
<svg class="bubble" viewBox="0 0 110 110" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
<g class="bubble-shape" style="cursor:pointer; pointer-events:visibleFill;">
<rect x="10" y="10" width="90" height="90" rx="15" ry="15" />
<polygon points="20,94 40,94 30,105" />
</g>
</svg>
This example uses one path
Should be fully supported by IE.
html,
body {
margin: 0;
padding: 0;
}
.bubble {
width: 150px;
height: 150px;
}
.bubble-shape {
stroke-width: 15;
stroke: #ddd;
fill: #1e1;
}
.shape-text {
color: black;
}
<svg class="bubble" viewBox="-70 -10 390 370" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
<g style="cursor:pointer; pointer-events:visible;">
<path class="bubble-shape" d="m 0,0 250,0 c 25,0 50,20 50,50 l 0,225 c 0,25 -25,50 -50,50 l -175,0 -25,20 -20,-20 -40,0 c -25,0 -50,-25 -50,-50 l 0,-225 C -50,25 -50,0 0,0 Z" />
</g>
</svg>
I'm developing a responsive web application, and need to create 2 separate content areas as follows,
So far, I tried,
border-right: 30px solid transparent;
border-bottom: 30px solid #4c4c4c;
height: 100%;
width: 100%;
position: fixed;
But, unfortunately couldn't create a triangle.
Is there any other way to create a triangle using CSS with the possibility to wrap content entirely within the div's besides using the border property?
Any help would be greatly appreciated.
I believe you are looking for triangles with borders and a transparent cut in between (which none of the existing answers seem to address) and so here is an example. It's absolutely possible to achieve with but takes a lot of hacking around.
Using CSS Transforms:
The below snippet uses pseudo-elements and transforms to produce the triangles effect. The output is responsive but the usage of skew transforms mean that if the container's shape becomes a rectangle then the skew angles would need modification and more tweaking of the positioning attributes etc.
.container {
position: relative;
overflow: hidden;
height: 200px;
width: 200px;
}
.div-1,
.div-2 {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
overflow: hidden;
}
.div-1 {
top: calc(-100% - 5px);
transform: skewY(45deg);
transform-origin: left top;
border-bottom: 2px solid;
}
.div-1:after {
position: absolute;
content: '';
height: calc(100% - 2px);
width: calc(100% - 2px);
top: calc(100% + 7px);
left: 0px;
transform: skewY(-45deg);
transform-origin: left top;
border: 1px solid;
}
.div-2 {
top: 5px;
transform: skewY(45deg);
transform-origin: left bottom;
border-top: 1px solid;
}
.div-2:after {
position: absolute;
content: '';
height: calc(100% - 7px);
width: calc(100% - 7px);
top: 0px;
left: 0px;
transform: skewY(-45deg);
transform-origin: left bottom;
border: 1px solid;
}
* {
box-sizing: border-box;
}
/* just for demo */
.container{
transition: all 1s;
}
.container:hover{
width: 400px;
height: 400px;
}
body{
background: radial-gradient(circle at center, aliceblue, mediumslateblue);
min-height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class='container'>
<div class='div-1'></div>
<div class='div-2'></div>
</div>
Using Gradients:
Another approach would be to use a couple of linear gradients as background images like in the below snippet. But there are plenty of drawbacks here too. (1) Gradients have very poor browser support at present. (2) Angular gradients tend to produce jagged lines which would need smoothing. (3) You had specifically mentioned 2 div elements in the image in question and I presume you want content within them, in which case that would need extra work.
.container {
position: relative;
overflow: hidden;
height: 200px;
width: 300px;
background: linear-gradient(to top right, transparent calc(50% + 2px), black calc(50% + 2px), black calc(50% + 4px), transparent calc(50% + 4px)), linear-gradient(to bottom left, transparent calc(50% + 2px), black calc(50% + 2px), black calc(50% + 4px), transparent calc(50% + 4px)) ;
}
.container:before{
position: absolute;
content: '';
height: calc(100% - 6px);
width: calc(100% - 6px);
left: 4px;
border-top: 2px solid black;
border-right: 2px solid black;
}
.container:after{
position: absolute;
content: '';
height: calc(100% - 6px);
width: calc(100% - 6px);
top: 4px;
border-left: 2px solid black;
border-bottom: 2px solid black;
}
/* just for demo */
.container{
transition: all 1s;
}
.container:hover{
width: 700px;
height: 400px;
}
body{
background: radial-gradient(circle at center, aliceblue, mediumslateblue);
min-height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class='container'>
</div>
Using SVG:
All these lead me to my suggestion, which is, to use SVG for creating such shapes. They are easy to create using just path elements, easily maintainable and are responsive by nature.
.container {
position: relative;
height: 300px;
width: 200px;
}
svg {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
}
svg path {
fill: transparent;
stroke: black;
}
/* just for demo */
.container {
transition: all 1s;
}
.container:hover {
height: 400px;
width: 700px;
}
body {
background: radial-gradient(circle at center, aliceblue, mediumslateblue);
min-height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class='container'>
<svg viewBox='0 0 100 100' preserveAspectRatio='none'>
<path d='M4,2 L98,2 98,96 4,2z M2,4 L2,98 96,98 2,4z' vector-effect='non-scaling-stroke' />
</svg>
</div>
Note: With any of the approaches mentioned above (or those given in other answers), you cannot make the content to remain within the boundaries of those triangles. Text can be placed upon them but the text cannot be contained within those boundaries unless the CSS Shapes module's shape-inside property is fully implemented.
Just don't give width and height will make triangle.
.triangle1 {
border-right: 30px solid transparent;
border-bottom: 30px solid #4c4c4c;
position: fixed;
}
Working Fiddle
As per your image here i have created two reverse triangle.
.triangle1 {
border-right: 100px solid transparent;
border-bottom: 100px solid #4c4c4c;
position: fixed;
}
.triangle2 {
border-left: 100px solid transparent;
border-top: 100px solid #4c4c4c;
position: fixed;
margin-left: 3px;
}
<div class="triangle1">
</div>
<div class="triangle2">
</div>
Edit:
Here is one way you can add text inside triangle.
Add another text div inside triangle and set it's position.
Fiddle
.triangle1 {
width: 0;
height: 0;
border-left: 0px solid transparent;
border-right: 200px solid transparent;
border-bottom: 150px solid black;
}
For DIV Element 1. For the second just make a square and render the triangle on the top of the square.
Html code :
<div id="DIV-Element1"></div>
<div id="DIV-Element2"></div>
Css :
#DIV-Element1 { width: 0; height: 0; border-bottom: 100px solid black; border-right: 100px solid transparent; }
#DIV-Element2 { width: 0; height: 0; margin-left: 15px;border-top: 100px solid black; border-left: 100px solid transparent; }
you can adjust your triangle by changing border
I hope this will work
I am trying to make a shape similar to a parallelogram, but without the angle on the right side. So it will keep its straight up and down line on the right, but the left side will keep its angle. Here is a fiddle: http://jsfiddle.net/2hj88xts/
CSS:
#parallelogram {
width: 250px;
height: 100px;
transform: skew(-15deg);
background: red;
}
You could try using a border-left with transparent as the color and abandon the *-transform's altogether. This would require a CSS change but no additional HTML markup:
Current Angle:
#parallelogram {
width: 250px;
height: 0;
border-bottom: 100px solid red;
border-left: 30px solid transparent;
margin-left: 10px;
}
<div id="parallelogram"></div>
To adjust the left angle simply tweak the border-left pixel amount. The larger the pixel amount, the more shallow the angle. The smaller the pixel amount, the steeper the angle.
Shallow Angle:
#parallelogram {
width: 250px;
height: 0;
border-bottom: 100px solid red;
border-left: 100px solid transparent;
margin-left: 10px;
}
<div id="parallelogram"></div>
Steep Angle:
#parallelogram {
width: 250px;
height: 0;
border-bottom: 100px solid red;
border-left: 15px solid transparent;
margin-left: 10px;
}
<div id="parallelogram"></div>
Use a pseudo element :
#parallelogram {
width: 250px;
height: 100px;
background: red;
margin-left: 100px;
position:relative;
}
#parallelogram:before{
content: '';
position:absolute;
width: 0;
height: 0;
border-style: solid;
border-width: 0 0 100px 40px;
border-color: transparent transparent red transparent;
left: -40px;
}
<div id="parallelogram"></div>
JSFiddle
Update
If you love living on the edge, try clip-path:
#parallelogram{
width: 250px;
height: 100px;
background: red;
-webkit-clip-path: polygon(29% 0, 100% 0, 100% 100%, 0% 100%);
clip-path: polygon(29% 0, 100% 0, 100% 100%, 0% 100%);
}
<div id="parallelogram"></div>
JSFiddle
Ok, So combine two divs together and remove the effect from the right div and pull it left to overlap the edge of the left div. Do something like this:
#parallelogram {
width: 250px;
height: 100px;
-webkit-transform: skew(-15deg );
-moz-transform: skew(-15deg);
-o-transform: skew(-15deg);
background: red;
margin-left:10px;
display:inline-block;
}
#end {
width: 250px;
height: 100px;
background: red;
display:inline-block;
margin-left:-20px;
}
<div>
<div id="parallelogram">
</div>
<div id="end">
</div>
</div>
Here is a svg solution without any CSS.
<svg>
<path d="M60,0 L250,0 L250,100 20,100z" fill="red" />
</svg>