先简单介绍下 overflow,在MDN描述是其设置了元素溢出时所需的行为——即当元素的内容太大而无法适应它的块级格式化上下文时,在设置了 hidden 或者 scroll,所有超出内容将被裁减。但有时候有些元素不被裁剪,比较常见的情况是当点击一个父元素然后展示一个子元素时,类似于右键操作,希望这个选项能完整展示,我遇到的问题有些不同,是希望元素平移出去实现一些动画效果。这个问题目前没有完美的处理方法,只能通过一些额外手段去适应,但目前在w3c 提议中有被提及,也许不久会有 css 原生的支持方法
hidden 处理
如果是希望 hidden 属性下的子元素展示,处理上会简单很多
使用 clip 属性
overflow 中 clip 属性值与 hidden 类似,都是将内容将以元素的边距进行裁剪,主要区别是 hidden 会创建一个新的格式化上下文,具体区别可以查看MDN,这里不多介绍。
提到 clip 是因为在overflow-x
和overflow-y
作为值,clip 和 hidden 作为属性值效果有所不同。如果你想实现平行方向超出隐藏,垂直方向超出展示的话,可能会想到设置overflow-x
为 hidden,同时设置overflow-y
为 visible,但这是行不通的,因为当overflow-x
值 hidden 时,即使overflow-y
设置为 visible 或者 clip,overflow-y
也会表现成 hidden,MDN 介绍里也有说明,更加准确的描述是:
当
overflow-x
或overflow-y
设置了非visible
/clip
属性值时,如果overflow-y
或overflow-x
设置了visible
/clip
,会自动转成auto
/hidden
。
如果需要实现平行方向超出隐藏,垂直方向超出展示的效果,可以考虑使用 clip 属性值
clip-path
clip-path 是 css3 新增的属性,可以通过设置路径来裁剪元素,具体使用可以查看MDN。clip-path 效果与 overflow:clip 效果类似,但区别是 clip-path 可以设置路径,可以实现更多的裁剪效果,这个网站可以在线生成 clip-path 的路径,可以尝试一下。
只需要动态修改路径,把需要展示的部分裁剪出来,就可以实现类似 overflow:visible 的效果,例如下面的代码,在右侧展示了一个矩形。
.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);
}
其他
可以实现裁剪 css 属性有contain, 其中contain: paint
与overflow: 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:translateY
和clip-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
属性,可以精确的控制子孙的展示,这样就不需要额外处理事件,但维护两层元素可能会增加额外的工作量。