Home > Blockchain >  Changing an element in position absolute
Changing an element in position absolute

Time:12-14

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:

  1. Regarding your <span>:
    Currently, it overflows .meal on small width values. This lets your card look poorly or amateurishly done.
  2. Remove dead code: Some declarations (or even whole rule sets, e.g. .price's) are unused, we can remove them.
  3. 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:

  1. Title and description.
  2. Price.
  3. 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>

  • Related