Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In pure CSS, is it possible to set margin equal to height of current element?

I'm have a vertical stack of items to which the user can append one by clicking a button, roughly like this.

<ol>
  <li><textarea></textarea></li>
  <li><textarea></textarea></li>
</ol>
<a data-action="additem">Add another</a>

I'm trying to write a CSS animation so that when the new li is inserted, the "Add another" smoothly slides down to its new resting place. Fixed height on the li tags is not an option, and I'm trying to avoid using the max-height animation hack because it can have weird layout effects.

I figured out that I could animate margin-bottom from something to 0 and have the desired effect, but I can't figure out how in CSS to express that I want the current height of the element to which this rule is applied. Percentages are measured relative to the width of the element, which isn't what I need here, and I can't think of a clever trick using calc or the like to express what I want to the browser.

Suggestions?

EDIT

I'm using a template with a repeat binding to add the items to the list. The JS only pushes another object into an observable array, and the framework handles the actual DOM insertion. The li tag has on it the following CSS to get it to enter smoothly:

animation: append forwards .5s;

And append is defined as:

@keyframes append {
    from {
        transform: translateX(10%);
        opacity: 0;
        margin-bottom: _____;
    }

    to {
        transform: none;
        opacity: 1;
        margin-bottom: 0;
    }
}
like image 919
ehdv Avatar asked Jan 08 '14 19:01

ehdv


People also ask

How do you set equal margins in CSS?

You can set the margin property to auto to horizontally center the element within its container. The element will then take up the specified width, and the remaining space will be split equally between the left and right margins.

How do you add height margin in CSS?

Set the CSS box-sizing property to border-box to make width and height include the content, border, and padding. This allows you to set width: 100% and still add padding or border . In other words box-sizing: border-box makes width include everything between the inner edges of the margin.

How do I make my Div 100% height?

With the advent of the CSS flex model, solving the 100% height problem becomes very, very easy: use height: 100%; display: flex on the parent, and flex: 1 on the child elements. They'll automatically take up all the available space in their container.


1 Answers

Not currently...

I've come up against this frustrating issue a number of times, always trying to either animate a non-numeric value, access a specific property of the current element as an animation value, or animate an unspecified value to a specified one. Generally I always have to fall back to either some form of not-quite-perfect max-height animation (like you've already mentioned) or use a mixture of CSS and JavaScript/jQuery.

For your issue there are a few options, but none are exactly what you're after.


css only version (using duplicated markup and another animation)

  • http://jsfiddle.net/7m8F9/2/
  • http://jsfiddle.net/7m8F9/3/ <-- improved version using bottom and position:relative
  • http://jsfiddle.net/7m8F9/5/ <-- even better version, going back to translateY

One trick often used with CSS-only hacks, is to duplicate markup — in this instance, the link iteself — and place it within parent wrappers that will be turned on or off by different means. The downsides to this method are that you get a rather ugly markup, and in this particular instance a bullet-number that appears jarringly (because of having to move the opacity animation from the li to the textarea).

The benefits of this method however are that by moving the link inside the li you can use -100% on the y-axis with a translate, or another offset method. Oddly though I can't work out what translateY(-100%) is calculating based upon... it doesn't seem to be the parent height, perhaps it is the height of itself. For this reason I've updated the fiddle to use bottom and relative positioning instead, although in Firefox (on mac) this glitches briefly.

It does seem to be that translateY is calculating percentage based on it's own height, so in order to get around this problem I've had to make use of position absolute and force the the link layer to assume the same dimensions as the li... annoying, as it involves z-indexing the textarea above the link, and an internal span to offset the link text, but at least it does work.

The following code works in the latest Firefox, and would work in other modern browsers if all the different browser-prefixes were correctly used to define the animation keyframes, I don't have time to set them all up right now however.

markup:

<ol class="list">
  <li><textarea></textarea><a class="add" href="#"><span>Add another</span></a></li>
  <li><textarea></textarea><a class="add" href="#"><span>Add another</span></a></li>
</ol>

css:

ol li {
  position: relative;
}
ol li .add {
  display: none;
}
ol li:last-child .add {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
  width: 100%;
  height: 100%;
  display: block;
  animation-duration: 1s;
  animation-name: slide;
}

ol li:last-child .add span {
    position: absolute;
    bottom: -20px;
}

.list li textarea {
  position: relative;
  animation-duration: 1s;
  animation-name: append;
  z-index: 1;
}

@keyframes append {
  from {
    transform: translateX(10%);
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}

@keyframes slide {
  from {
    transform: translateY(-100%);
  }
  to {
    transform: none;
  }
}


javascript version (code triggered translations)

http://jsfiddle.net/7m8F9/1/

The following obviously doesn't take into account the fact that you are using a template engine to power your DOM manipulations, but all the code needs to work properly is a before and after height of the list (to calculate the difference in height), and an event to trigger at the point where the new list item is added.

Sadly it is not yet possible to do this all in pure CSS, at least not as far as I have seen, perhaps once calc has leveled up...? Or perhaps if some way is introduced to reference the current elements dimensions, not just it's offset parent.

It should be noted I didn't have Internet Explorer around to test this with, but all other modern browsers seem happy.

markup:

<ol class="list">
  <li><textarea></textarea></li>
  <li><textarea></textarea></li>
</ol>
<div class="add">
  <a href="#">Add another</a>
</div>

javascript (with jQuery):

function prefix(){
  for ( var a = ['Webkit','Moz','O','ms'], i=0, l = a.length; i<l; i++ ) {
    if ( document.body.style[a[i]+'AnimationName'] !== undefined ) {
      return { js: a[i], css: '-' + a[i].toLowerCase() + '-' };
    }
  }
  return { css:'', js:'' };
}

$(function(){
  $('.add a').click(function(e){
    e.preventDefault();
    var pref = prefix(),
      link = $(this).parent(), 
      list = $('.list'),
      lihi = list.height(),
      liad = $('<li><textarea></textarea></li>').appendTo(list),
      lihd = lihi - list.height();
      link.css(pref.css + 'transform', 'translateY(' + lihd + 'px)');
    setTimeout(function(){link.addClass('translate-zero transition-all');},0);
    setTimeout(function(){
      link.css(pref.css + 'transform', '');
      link.removeClass('translate-zero transition-all');
    },500);
  });
});

css:

.transition-all {
  -webkit-transition: all 0.5s;
  -moz-transition: all 0.5s;
  -ms-transition: all 0.5s;
  -o-transition: all 0.5s;
  transition: all 0.5s;
}

.translate-zero {
  -webkit-transform: translateY(0) !important;
  -moz-transform: translateY(0) !important;
  -ms-transform: translateY(0) !important;
  -o-transform: translateY(0) !important;
  transform: translateY(0) !important;
}

.list li {
  animation-duration: 1s;
  animation-name: append;
}

@keyframes append {
  from {
    transform: translateX(10%);
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}


redesign version

A number of times I have hit a similar issue, only to find a redesign helps do away with the problem and can often actually improve usability. In your case it may be best to place the "add link" above the list (or top right), or integrate the button as a floating icon somewhere... where-ever you put it, it is best to try and keep it in a static location, moving interaction points can be annoying for users, especially if they wish to add more than one item in quick succession.

example of top right link

like image 100
Pebbl Avatar answered Oct 19 '22 23:10

Pebbl