描述

先简单介绍下 overflow,在MDN描述是其设置了元素溢出时所需的行为——即当元素的内容太大而无法适应它的块级格式化上下文时,在设置了 hidden 或者 scroll,所有超出内容将被裁减。但有时候有些元素不被裁剪,比较常见的情况是当点击一个父元素然后展示一个子元素时,类似于右键操作,希望这个选项能完整展示,我遇到的问题有些不同,是希望元素平移出去实现一些动画效果。这个问题目前没有完美的处理方法,只能通过一些额外手段去适应,但目前在w3c 提议中有被提及,也许不久会有 css 原生的支持方法

hidden 处理

如果是希望 hidden 属性下的子元素展示,处理上会简单很多

使用 clip 属性

overflow 中 clip 属性值与 hidden 类似,都是将内容将以元素的边距进行裁剪,主要区别是 hidden 会创建一个新的格式化上下文,具体区别可以查看MDN,这里不多介绍。

提到 clip 是因为在overflow-xoverflow-y作为值,clip 和 hidden 作为属性值效果有所不同。如果你想实现平行方向超出隐藏,垂直方向超出展示的话,可能会想到设置overflow-x为 hidden,同时设置overflow-y为 visible,但这是行不通的,因为当overflow-x值 hidden 时,即使overflow-y设置为 visible 或者 clip,overflow-y也会表现成 hidden,MDN 介绍里也有说明,更加准确的描述是:

overflow-xoverflow-y设置了非 visible/clip属性值时,如果overflow-yoverflow-x设置了 visible/clip,会自动转成auto/hidden

如果需要实现平行方向超出隐藏,垂直方向超出展示的效果,可以考虑使用 clip 属性值

clip-path

clip-path 是 css3 新增的属性,可以通过设置路径来裁剪元素,具体使用可以查看MDN。clip-path 效果与 overflow:clip 效果类似,但区别是 clip-path 可以设置路径,可以实现更多的裁剪效果,这个网站可以在线生成 clip-path 的路径,可以尝试一下。

只需要动态修改路径,把需要展示的部分裁剪出来,就可以实现类似 overflow:visible 的效果,例如下面的代码,在右侧展示了一个矩形。

css
.wrap {
  height: 400px;
  width: 400px;
  background: gray;
  clip-path: polygon(0px 0px, 280px 0px, 280px 150px, 380px 150px, 380px 250px, 280px 250px, 280px 400px, 0px 400px);
}

clip-path

其他

可以实现裁剪 css 属性有contain, 其中contain: paintoverflow: hidden类似。另外也可以通过mask或者通过元素覆盖实现类似裁剪的效果,但这些方法可能存在覆盖其他元素,导致页面出现滚动的问题,这里就不多介绍了

scroll 处理

scroll 的处理要麻烦一些,但处理方法也能套用在 hidden 上

使用 absolute 或者 fixed

通过设置 position,使用 absolute 或者 fixed 会被移出正常文档流,可以让元素离开 overflow 的上下文,也是比较处理这类问题很常用的方法。但使用 absolute 或者 fixed 时,overflow 包含的父元素不能有 relative 属性,并且不受文档流控制也会让处理起来比较困难。因为已经脱离正常文档流了,所以元素是否是子元素没多少隐藏,完全可以新建元素挂载在最近的 relative 元素或者 body 元素下,也有一样的处理流程。

一个简单的例子,点击后子元素展示,并且跟随滚动

<head>
  <link rel="stylesheet" href="/styles.css" />
</head>
<body>
  <div class="scroll">
    <div class="content"></div>
    <div class="absolute_child"></div>
  </div>
</body>
<script src="/scripts.js"></script>

覆盖一个透明的 scroll 元素

使用 clip-path 能非常灵活的裁剪元素,但问题是 clip-path 没办法直接进行滚动,对于要滚动的要取,可以在外层挂载一个透明的滚动元素,监听元素滚动,动态设置底层元素的transform:translateYclip-path,可以达到普通滚动的效果,也可以灵活裁剪元素。这个最大的问题底层的方法没办法直接触发事件,需要提供其他手段触发时间,例如动态设置外层pointer-events: none属性,在鼠标按下时设置为 none,抬起时恢复为 auto,这样被覆盖的元素可以直接触发鼠标抬起事件。或者点击时外层元素设置display: none,使用document.elementFromPoint获取被覆盖的元素,增加创建 Event 事件给对应元素。

题外话,在测试动态设置pointer-events: none,发现在 Chromium 内核,如果手动将 pointer-events 从 auto 调整为 none,外层元素依然是可以滚动,但无法触发点击,而在 firefox 或者 Safari 都是无法触发的。

下面的代码,点击后在右侧展示了一个矩形,并且可以跟随元素滚动

<head>
  <link rel="stylesheet" href="/styles.css" />
</head>
<body>
  <div class="wrap">
    <div class="content"></div>
    <div class="scroll">
      <div class="scroll-content"></div>
    </div>
  </div>
</body>
<script src="/scripts.js"></script>

这个方法也可以稍微进行扩展下,例如覆盖的元素不透明,进行展示并滚动,而被覆盖的元素进行隐藏,在需要的时候提高优先级并展示,例如使用visibility属性,可以精确的控制子孙的展示,这样就不需要额外处理事件,但维护两层元素可能会增加额外的工作量。