I am working on a project and I have an element that is set in "position : absolute", but it's only text and an animation. I know it's better not to use that kind of position in that case. How can i change it to look the same but without the position absolute ? Can I use a display flex with space between ? If I use space between, do I need to add another div in HTML ?
.meal div {
background-color: white;
border-radius: 1rem;
box-shadow: 0 0 10px #0000002e;
margin-bottom: 2rem;
height: 4rem;
position: relative;
padding-left: 1rem;
padding-top: 1rem;
animation: fadeIn 0.7s ease-in both, top 0.5s linear;
cursor: pointer;
}
.meal__price {
position: absolute;
top: 2rem;
font-weight: bold;
left: 85%;
}
.price {
position: absolute;
right: 2rem;
top: 1rem;
font-weight: bold;
}
.meal h3, .meal__description {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.meal h3 {
position: absolute;
top: 0;
width: 70%;
}
.meal__description {
position: absolute;
top: 2rem;
font-size: 16px;
width: 90%;
}
.meal span {
position: absolute;
background-color: #99E2D0;
height: 100%;
display: flex;
align-items: center;
bottom: 0rem;
width: 0%;
justify-content: center;
border-top-right-radius: 0.9rem;
border-bottom-right-radius: 0.9rem;
left: 100%;
color: white;
font-size: 1.375rem;
visibility: hidden;
transition: all 0.5s;
}
.fa-check-circle {
visibility: hidden;
}
.meal div:hover span {
left: 80%;
visibility: visible;
width: 20%;
}
.meal div:hover .meal__price {
left: 65%;
}
.meal div:hover .meal__description {
width: 50%;
}
.meal div:hover i {
animation: rotation 0.4s linear 0.15s forwards;
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
visibility: visible;
transform: rotate(360deg);
}
}
.meal div:hover .meal__price {
animation: move-animation 0.2s linear forwards;
}
@keyframes move-animation {
from {
left: 85%;
}
to {
left: 70%;
}
}
<div >
<div>
<h3>Pigeonneau d’Ille-et-Vilaine</h3>
<p >Sur son lit de gnocchis aux légumes</p>
<p >44€</p>
<span>
<i ></i>
</span>
</div>
CodePudding user response:
Using position: absolute
on elements that only consist of text or images is absolutely fine depending on the context.
But I would agree, in this case you could have achieved the same layout without resorting to position: absolute
, which takes elements out of the flow.
Also, not using position: absolute
means that we won't have to rely on magic numbers! Currently you are setting .meal div
to a fixed height, regardless of its content. This feels wrong, but won't be necessary if its children are still part of its flow.
And trying to use CSS Flex here is a great idea! In fact, I will use Flex in combination with Grid for re-styling your meal card.
(Sidenote: There are obviously very many ways to achieve what you asked for, as you have obviously noticed. This is just the way I did it, and I highly encourage you to explore alternatives for this—and for other things, too!—yourself before reading this. Once you know a way, you're less likely to develop a unique solution yourself.)
Here are the things in your CSS I would like to change:
- Regarding your
<span>
:
Currently, it overflows.meal
on small width values. This lets your card look poorly or amateurishly done. - Remove dead code: Some declarations (or even whole rule sets, e.g.
.price
's) are unused, we can remove them. position: absolute
: This is what your question is actually about.
Fixing 2. is a simple matter of deleting. I don't think that requires any further explanation.
To fix 1. we can add overflow: hidden
to the rule for .meal div
. With this, we won't need the border on the right sides of span
, since those will be hidden by the property we just set.
Here is an extreme example showing the differences:
/* Don't set border-radius when using overflow:hidden! */
.outer:not(.overflow)>.inner {border-radius: 9999px;}
.overflow {overflow: hidden}
.inner {
width: 100%;
height: 0;
background-color: crimson;
transition: height .5s;
}
.outer {
aspect-ratio: 1/1;
width: 10rem;
border-radius: 9999px;
background-color: lightblue;
}
.outer:hover>.inner {height: 100%}
/* Ignore; CSS to make the example look nicer */
body {display:flex;gap:1rem}
header {text-align:center}
<section>
<header>With <code>overflow: hidden</code></header>
<div >
<div ></div>
</div>
</section>
<section>
<header>Previously</header>
<div >
<div ></div>
</div>
</section>
Re-style for Flex (and Grid!)
We don't want to use position: absolute
anymore, so we remove those declarations as well as any related. This means, we also remove:
position: relative
: We don't need a non-static predecessor.top
,left
,right
,bottom
: We cannot use these anymore.
Distributing the space
What I take from the looks of it is, that we basically want 3 columns:
- Title and description.
- Price.
- The turquoise
<span>
that expands on hover (and as per the code, has a FontAwesome icon on top of it?).
Currently, these are all siblings. But styling will be easier if we wrap all except the <span>
. In doing so, we can style the title, description and price tag without affecting the left out <span>
.
An example of how we can wrap them:
<div >
<div>
<div>
<h3></h3>
<p ></p>
<p ></p>
</div>
<span>
<i ></i>
</span>
</div>
</div>
But one of your selectors is .meal div
, which will select the inner <div>
as well. We can simply fix this by using a child combinator (>
) instead of a descendant combinator (
, space): .meal>div
.
The CSS
The first instance where Flex seems fit, is for placing the new wrapper <div>
and the <span>
side-by-side. Also, we want the wrapper to take up the remaining space:
.meal>div {display:flex}
.meal>div>div {flex-grow:1}
/* Take up remaining space. The (non-zero) value is relevant
if other flex children have this property declared as well.
Then, the flex parent will distribute the remaining space
according to the *weight* (e.g. this value) of the children. */
The content of the wrapper however should be organized in 2 columns: 1 for the title and description, and 1 for the price. We can either wrap title and description in yet another wrapper, or we can use Grid to make our lives easier:
.meal>div>div {
display:grid;
/* 1fr is very similar to 'flex-grow: 1', just for Grid instead of Flex. */
grid-template-columns: 1fr auto;
grid-template-rows: repeat(2, auto); /* Alternatively: auto auto */
}
.meal__price {
grid-column: 2/2;
grid-row: 1/-1;
/* "Take up all rows between
the first and the last, inclusively" */
}
After some small changes to make it look more similar to the original version, this is what we end up with:
.fa-check-circle {visibility: hidden}
.meal>div {
margin-bottom: 2rem;
border-radius: 1rem;
display: flex;
animation: fadeIn 0.7s ease-in both, top 0.5s linear;
background-color: white;
box-shadow: 0 0 10px #0000002e;
cursor: pointer;
overflow: hidden;
}
.meal>div>div {
flex-grow: 1;
padding: 1rem;
display:grid;
grid-template-columns: 1fr auto;
grid-template-rows: repeat(2, auto);
}
.meal>div>div * {margin:0}
.meal>div:hover i {animation: rotation 0.4s linear 0.15s forwards}
.meal>div:hover span {
visibility: visible;
width: 6rem;
}
.meal h3, .meal__description {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.meal__price {
place-self: flex-end;
grid-column: 2/2;
grid-row: 1/-1;
font-weight: bold;
}
.meal span {
width: 0;
display: grid;
place-items: center;
visibility: hidden;
transition: all 0.5s;
background-color: #99E2D0;
font-size: 1.375rem;
color: white;
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
visibility: visible;
transform: rotate(360deg);
}
}
<div >
<div>
<div>
<h3>Pigeonneau d’Ille-et-Vilaine</h3>
<p >Sur son lit de gnocchis aux légumes</p>
<p >44€</p>
</div>
<span>
<i ></i>
</span>
</div>
</div>
Just to practice some CSS again, I tried recreating your code as closely as possible, purely from what I can see. Since I think reading others' CSS is helpful, I provided it here as well:
.meal {
border-radius: 1rem;
display: flex;
background-color: white;
box-shadow: .2rem .15rem .6rem rgba(0, 0, 0, .35);
overflow: hidden;
font-family: sans-serif;
}
.meal::after {
content: "";
width: 0;
background-color: #99E2D0;
transition: width .6s;
}
.meal:hover::after {width: 6rem}
.meal>div {
flex-grow: 1;
padding-block: .8rem;
padding-inline: 1rem;
display:
}
.meal header {
font-size: large;
font-weight: bold;
}
.meal .price {
clear: right;
float: right;
}
p:last-child {margin-block-end:0}
<section >
<div>
<header>Pigeonneau d’Ille-et-Vilaine</header>
<p >
Sur son lit de gnocchis aux légumes
<span >Price: 44€</span>
</p>
</div>
</section>