CSS display属性的转换

1932

我目前正在设计一个CSS“超级下拉”菜单-基本上是一个常规的仅使用CSS的下拉菜单,但其中包含不同类型的内容。

目前看来, CSS 3转换不适用于“display”属性,也就是说,您无法从“display: none”到“display: block”(或任何组合)进行任何过渡。

是否有一种方法可以使上面示例中的二级菜单在某人悬停在顶级菜单项上时“淡入”?

我知道您可以在“visibility:”属性上使用过渡,但我无法想出有效使用它的方法。

我还尝试使用高度,但那样只会失败。

我知道使用JavaScript轻松实现这一点,但我想挑战自己仅使用CSS,并且我认为我做得不够好。


32
position: absolute; visibility: hidden;与display: none;相同。 - Jawad
13
只有当你添加类似于 z-index:0 的内容时才行。 - DanMan
31
建议永远不要使用 visibility: hidden,除非你希望屏幕阅读器读取它(而典型的浏览器不会)。它仅定义元素的可见性(就像说 opacity: 0 一样),它仍然是可选、可点击和以前的任何状态;它只是不可见。 - Forest Katsch
2
IE 8、9、10不支持pointer-events,因此并不总是可行。 - Steven Pribilinskiy
5
你需要使用“display: none”,否则你可能会意外地显示隐藏的对象,而这些对象在触发器外面,我只是提醒一下 :) - Samuel Ramzan
显示剩余7条评论
40个回答

6

不需要 JavaScript,也不需要过高的 max-height。相反,将您的max-height设置在文本元素上,并使用字体相关单位,如remem。这样,您可以设置比容器更大的最大高度,同时避免菜单关闭时出现延迟或“弹跳”:

HTML

<nav>
  <input type="checkbox" />
  <ul>
    <li>Link 1</li>
    <li>Link 1</li>
    <li>Link 1</li>
    <li>Link 1</li>
  </ul>
</nav>

CSS

nav input + ul li { // Notice I set max-height on li, not ul
   max-height: 0;
}

nav input:checked + ul li {
   max-height: 3rem; // A little bigger to allow for text-wrapping - but not outrageous
}

在这里可以看到一个例子:http://codepen.io/mindfullsilence/pen/DtzjE


6

我最终找到了适合我的解决方案,通过将 opacityposition absolute 结合使用(不占用隐藏时的空间)。

.toggle {
  opacity: 0;
  position: absolute;
  transition: opacity 0.8s;
}

.parent:hover .toggle {
  opacity: 1;
  position: static;
}

5
你可以按照自然的方式来使用display,但是你需要限制浏览器来使它工作,使用JavaScript或者像其他人建议的在一个标签内嵌套另一个标签的花式技巧。我不喜欢内部标签,因为它进一步复杂了CSS和尺寸,所以这里提供JavaScript解决方案:

https://jsfiddle.net/b9chris/hweyecu4/17/

以一个类似于盒子的形状开始:
<div id="box" class="hidden">Lorem</div>

一个隐藏的框。
div.hidden {
    display: none;
}
#box {
    transition: opacity 1s;
}
    

我们将使用在相关问答中发现的技巧,检查offsetHeight以立即限制浏览器:

https://dev59.com/hWgu5IYBdhLWcg3wt5Lt#16575811

首先,一个正式化上述技巧的库:

$.fn.noTrnsn = function () {
    return this.each(function (i, tag) {
        tag.style.transition = 'none';
    });
};
$.fn.resumeTrnsn = function () {
    return this.each(function (i, tag) {
        tag.offsetHeight;    
        tag.style.transition = null;
    });
};

接下来,我们将使用它来显示一个框,并淡入:
$('#button').on('click', function() {
    var tag = $('#box');
    if (tag.hasClass('hidden'))
        tag.noTrnsn().removeClass('hidden')
        .css({ opacity: 0 })
        .resumeTrnsn().css({ opacity: 1 });
    else
        tag.css({ opacity: 0 });
});

这段代码实现了盒子的淡入淡出效果。因此,.noTrnsn()关闭了过渡效果,然后我们移除了hidden类,将displaynone翻转到其默认状态,即block。然后,我们将不透明度设置为0,以准备淡入。现在,我们已经做好了准备工作,使用.resumeTrnsn()重新开启过渡效果。最后,通过将不透明度设置为1来启动过渡效果。
如果没有库文件,更改显示和不透明度都会导致我们得到不良结果。如果我们简单地删除库调用,就根本没有过渡效果。
请注意,上述代码在淡出动画结束时并未再次将显示设置为none。我们可以使用更高级的方法。让我们尝试一种从0淡入并增加高度的方式。

华丽!

https://jsfiddle.net/b9chris/hweyecu4/22/

#box {
    transition: height 1s, opacity 1s;
}

我们现在正在过渡高度和不透明度。请注意,我们没有设置高度,这意味着它是默认值auto。传统上,这不能过渡-从自动到像0这样的像素值移动将不会有任何过渡效果。我们将使用库和另一个库方法来解决这个问题。
$.fn.wait = function (time, fn) {
    if (time)
        this.delay(time);
    if (!fn)
        return this;

    var _this = this;
    return this.queue(function (n) {
        fn.call(_this);
        n();
    });
};

这是一个方便的方法,让我们参与jQuery现有的fx/animation队列,而不需要任何在jQuery 3.x中现已排除的动画框架。我不会解释jQuery的工作原理,但可以说,jQuery提供的.queue().stop()管道帮助我们防止动画相互干扰。让我们动画化下滑效果。
$('#button').on('click', function() {
    var tag = $('#box');
    if (tag.hasClass('hidden')) {
        // Open it
        // Measure it
        tag.stop().noTrnsn().removeClass('hidden').css({
            opacity: 0, height: 'auto'
        });
        var h = tag.height();
        tag.css({ height: 0 }).resumeTrnsn()
        // Animate it
        .css({ opacity: 1, height: h })
        .wait(1000, function() {
            tag.css({ height: 'auto' });
        });
    } else {
        // Close it
        // Measure it
        var h = tag.noTrnsn().height();
        tag.stop().css({ height: h })
        .resumeTrnsn()
        // Animate it
        .css({ opacity: 0, height: 0 })
        .wait(1000, function() {
            tag.addClass('hidden');
        });
    }
});

这段代码首先通过检查其类别来确定#box是否处于隐藏状态。但是,它使用wait()库调用实现了更多功能,在滑动/淡出动画的末尾添加了hidden类,如果实际上处于隐藏状态,则会找到它 - 这是上面更简单的示例无法做到的。这也使得元素的显示/隐藏可以反复进行,这在以前的示例中是一个错误,因为隐藏类从未被恢复。
您还可以看到在.noTrnsn()之后调用CSS和类更改,以通常设置动画阶段,包括测量,例如测量#box的最终高度而不向用户显示它,然后调用.resumeTrnsn(),并从完全设置的阶段将其动画化到其目标CSS值。

旧答案

https://jsfiddle.net/b9chris/hweyecu4/1/

您可以使用以下代码使其在单击时进行转换:

function toggleTransition() {
  var el = $("div.box1");

  if (el.length) {
    el[0].className = "box";
    el.stop().css({maxWidth: 10000}).animate({maxWidth: 10001}, 2000, function() {
        el[0].className = "box hidden";
    });
  } else {
    el = $("div.box");
    el[0].className = "box";
    el.stop().css({maxWidth: 10001}).animate({maxWidth: 10000}, 50, function() {
        el[0].className = "box box1";
    });
  }

  return el;
}

someTag.click(toggleTransition);

CSS就是你猜到的那样:

.hidden {
    display: none;
}
.box {
    width: 100px;
    height: 100px;
    background-color: blue;
    color: yellow;
    font-size: 18px;
    left: 20px;
    top: 20px;
    position: absolute;
    -webkit-transform-origin: 0 50%;
    transform-origin: 0 50%;
    -webkit-transform: scale(.2);
    transform: scale(.2);
    -webkit-transition: transform 2s;
    transition: transform 2s;
}
.box1{
    -webkit-transform: scale(1);
    transform: scale(1);
}

关键在于限制显示属性。通过移除隐藏类,然后等待50毫秒,然后通过添加的类开始过渡,我们使其出现并像我们想要的那样扩展,而不是它只是在屏幕上闪烁而没有任何动画。反向操作类似,只是在应用隐藏之前等待动画结束。

注意:我在这里滥用了.animate(maxWidth),以避免 setTimeout 竞争条件。当您或其他人不知道它的代码时,setTimeout很容易引入隐藏的错误。 .animate()可以轻松使用.stop()终止。我只是用它来在标准fx队列上放置50毫秒或2000毫秒的延迟,其他构建在其上的编码人员可以轻松找到/解决它。


5
我怀疑任何刚开始使用CSS过渡的人都会很快发现,如果您同时修改显示属性(块/无),它们将不起作用。一个尚未提到的解决方法是,您可以继续使用display:block/none来隐藏/显示元素,但将其不透明度设置为0,这样即使它是display:block,它仍然是不可见的。
然后,要淡入它,请添加另一个CSS类,例如“on”,该类将不透明度设置为1,并定义不透明度的过渡效果。正如您可能想象的那样,您必须使用JavaScript将“on”类添加到元素中,但至少您仍在使用CSS进行实际的转换。
附言:如果您发现自己需要同时执行display:block和添加“on”类,则使用setTimeout延迟后者。否则,浏览器会将两个操作视为同时发生并禁用过渡。

3

我认为SalmanPK的答案最接近。它使用以下CSS动画淡入或淡出一个项目。然而,display属性不能平滑地进行动画处理,只有opacity可以。

@-webkit-keyframes fadeIn {
    from { opacity: 0; }
      to { opacity: 1; }
}

@-webkit-keyframes fadeOut {
    from { opacity: 1; }
      to { opacity: 0; }
}

如果您想要将元素从“display block”动画到“display none”,目前我无法看到仅使用CSS实现的可能性。您需要获取高度并使用CSS动画来减小高度。这在CSS中是可能的,如下面的示例所示,但是要知道需要为元素动画的确切高度值可能有些棘手。

jsFiddle 示例

CSS

@-webkit-keyframes pushDown {
  0% {
    height: 10em;
  }
  25% {
    height: 7.5em;
  }
  50% {
    height: 5em;
  }
  75% {
    height: 2.5em;
  }
  100% {
    height: 0em;
  }
}

.push-down {
    -webkit-animation: pushDown 2s forwards linear;
}

JavaScript

var element = document.getElementById("element");

// Push item down
element.className = element.className + " push-down";

3
这个解决方案具有出色的兼容性,我还没有看到过这样的解决方案。
.hidden-element {
  position: absolute;
  z-index: -1;
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
  transition: visibility 0s, opacity .5s ease-out;
}

.hidden-element.visible {
  position: static;
  z-index: auto;
  pointer-events: auto;
  visibility: visible;
  opacity: 1;
}

解释:它使用了visibility: hidden技巧(与“一步显示和动画”兼容),但是它结合了position: absolute; z-index: -1; pointer-events: none;来确保隐藏的容器不占用空间,并且不响应用户交互


3

解决这个问题最简单的通用方法是:在你的 CSS 中自由地指定 display:none,但你需要使用 JavaScript 将其更改为block(或其他任何值),然后你还需要在涉及到的元素上添加一个实际执行转换的类 使用 setTimeout() 定时器。就是这样。

例如:

<style>
    #el {
        display: none;
        opacity: 0;
    }
    #el.auto-fade-in {
        opacity: 1;
        transition: all 1s ease-out; /* Future, future, please come sooner! */
        -webkit-transition: all 1s ease-out;
        -moz-transition: all 1s ease-out;
        -o-transition: all 1s ease-out;
    }
</style>

<div id=el>Well, well, well</div>

<script>
    var el = document.getElementById('el');
    el.style.display = 'block';
    setTimeout(function () { el.className = 'auto-fade-in' }, 0);
</script>

这在最新的现代化浏览器中已经测试过。很显然,它不会在Internet Explorer 9或更早的版本中工作。


这个运行得非常好,谢谢! - Ardee Aram

3
您可以使用过渡事件来实现此操作,因此您需要构建两个 CSS 类来进行转换,一个包含动画,另一个包含 display none 状态。在动画结束后切换它们即可。在我的情况下,如果我按下按钮,可以再次显示 div,并删除这两个类。请尝试下面的代码片段...

$(document).ready(function() {
  // Assign transition event
  $("table").on("animationend webkitAnimationEnd", ".visibility_switch_off", function(event) {
    // We check if this is the same animation we want
    if (event.originalEvent.animationName == "col_hide_anim") {
      // After the animation we assign this new class that basically hides the elements.
      $(this).addClass("animation-helper-display-none");
    }

  });

  $("button").click(function(event) {

    $("table tr .hide-col").toggleClass(function() {
      // We switch the animation class in a toggle fashion...
      // and we know in that after the animation end, there
      // is will the animation-helper-display-none extra
      // class, that we nee to remove, when we want to
      // show the elements again, depending on the toggle
      // state, so we create a relation between them.
      if ($(this).is(".animation-helper-display-none")) {
        // I'm toggling and there is already the above class, then
        // what we want it to show the elements , so we remove
        // both classes...
        return "visibility_switch_off animation-helper-display-none";
      }
      else {
        // Here we just want to hide the elements, so we just
        // add the animation class, the other will be added
        // later be the animationend event...
        return "visibility_switch_off";
      }
    });
  });
});
table th {
  background-color: grey;
}

table td {
  background-color: white;
  padding: 5px;
}

.animation-helper-display-none {
  display: none;
}

table tr .visibility_switch_off {
  animation-fill-mode: forwards;
  animation-name: col_hide_anim;
  animation-duration: 1s;
}

@-webkit-keyframes col_hide_anim {
  0% {opacity: 1;}
  100% {opacity: 0;}
}

@-moz-keyframes col_hide_anim {
  0% {opacity: 1;}
  100% {opacity: 0;}
}

@-o-keyframes col_hide_anim {
  0% {opacity: 1;}
  100% {opacity: 0;}
}

@keyframes col_hide_anim {
  0%   {opacity: 1;}
  100% {opacity: 0;}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
  <theader>
    <tr>
      <th>Name</th>
      <th class='hide-col'>Age</th>
      <th>Country</th>
    </tr>
  </theader>
  <tbody>
    <tr>
      <td>Name</td>
      <td class='hide-col'>Age</td>
      <td>Country</td>
    </tr>
  </tbody>
</table>

<button>Switch - Hide Age column with fadeout animation and display none after</button>


3
我曾遇到过类似的问题,但无法找到答案。经过几次谷歌搜索,我来到了这里。考虑到我没有找到所期望的简单答案,我偶然找到了一个既优雅又有效的解决方案。
原来,CSS属性visibility有一个值collapse,通常用于表格项。但是,如果应用于任何其他元素,它将有效地使它们呈现为隐藏,与display: hidden基本相同,只是该元素不占用任何空间,并且仍然可以对该元素进行动画处理。
以下是一个简单的示例。

function toggleVisibility() {
  let exampleElement = document.querySelector('span');
  if (exampleElement.classList.contains('visible')) {
    return;
  }
  exampleElement.innerHTML = 'I will not take up space!';
  exampleElement.classList.toggle('hidden');
  exampleElement.classList.toggle('visible');
  setTimeout(() => {
    exampleElement.classList.toggle('visible');
    exampleElement.classList.toggle('hidden');
  }, 3000);
}
#main {
  display: flex;
  flex-direction: column;
  width: 300px;
  text-align: center;
}

.hidden {
  visibility: collapse;
  opacity: 0;
  transition: visibility 2s, opacity 2s linear;
}

.visible {
  visibility: visible;
  opacity: 1;
  transition: visibility 0.5s, opacity 0.5s linear;
}
<div id="main">
  <button onclick="toggleVisibility()">Click Me!</button>
  <span class="hidden"></span>
  <span>I will get pushed back up...</span>
</div>


2
我开始了一个名为[Toggle Display Animate][1]的开源骨架项目。
这个骨架助手可以让您轻松地模仿jQuery show/hide,但使用in/out CSS 3转换动画。
它使用类切换,所以您可以在元素上使用除display:none|block|table|inline等之外的任何CSS方法,以及其他可以想到的替代用途。
它的主要设计目的是用于元素切换状态,并支持还原状态,其中隐藏对象允许您倒播关键帧或播放隐藏元素的替代动画。
我正在进行的概念大部分标记都是CSS,实际上很少使用JavaScript。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接