纯css实现多栏拖动大小
本文的代码和实现思路参考 公众号 - iCSS 前端趣闻:CSS 实现可拉伸调整尺寸的分栏布局 (opens new window) 下文为记录和整理学习的过程中自身的思考。如有侵权请联系我删除
# 纯 css 实现多栏拖动大小
# 实现原理
核心是使用 css 的 resize (opens new window) 属性。当节点拖动的时候,resize 属性会自动帮我们修改 dom 节点的行内样式(width/height)。免去了我们自己用 js 实现拖动
配合 flex 布局,实现一个容器设置宽/高,剩余的容器占 flex:1
实现自适应的布局
/* 横向拖动的时候关键的css */
.resize {
width: 100%;
height: 16px;
transform: scaleY(100);
transform-origin: left;
overflow: scroll;
resize: horizontal;
opacity: 0;
}
/* 垂直拖动的时候关键的css */
.resize {
width: 16px;
height: 16px;
transform: scaleX(100);
transform-origin: left;
overflow: scroll;
resize: vertical;
opacity: 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
第二个核心原理:宽高 16px
和 scaleX
、 scaleY
- 为什么是 16px?
因为把一个节点设置为 resize 后,他的可以拖动变化宽高的边框是 16px~17px。chrome 是 17,火狐只有缩小到 16px 的时候上下箭头才会被隐藏,所以就把火狐预估为 16px。所以还是稳妥起见取小不取大,以免多余元素没被隐藏到
scaleX
、scaleY
的作用
就算设置了 resize 属性,也并不是整个 div 的边框都能拖动,能用于拖动的只有下图中红色框圈中的区域,其余的边框是不能触发 resize 效果的。
而根据上面的可以得知,红色框框的大小可以理解为 16*16px。如果我们想让整个容器的边框都可以拖动,只需要把这个 16* 16 往对应的方向拉大即可。如下图:
往垂直方向,放大 10 倍,红框 1 中整条范围都可以触发 resize 了
换为横向同理:
所以 scaleX
、 scaleY
是为了对应横向/竖向的拖动的方块放大足够大的距离。而这个放大的倍数目前给的是 100 (尽可能大的倍数,能覆盖父 div 对应的边框长度即可)
比如一个需要缩放的 div 的大小为 160*160px。 那么 .resize 其实只需要 scaleX(10),那么 resize 用于拖动的边框长度也是 160,就可以完全覆盖这个 div。为什么不能采用 scaleX(10) 呢?
假设我们现在设置缩放的位置是根据 transform-origin: left top;
来设置的;就会看到如下的效果:
如果改为 transform-origin: left;
或者直接去掉 transform-origin: left;
属性,效果如下图:
而放大到 100(甚至更大的时候),情况如下图:
# 实现原理的最小实现 demo
拖动右边边框实现缩放
<style>
.box {
background-color: #99cccc;
display: inline-block;
height: 160px;
overflow: hidden;
}
.resize {
width: 160px;
height: 16px;
/* 可以尝试修改这个缩放数值查看效果 */
transform: scaleY(100);
transform-origin: left;
overflow: scroll;
resize: horizontal;
opacity: 0.1;
}
</style>
<div class="box">
<div class="resize"></div>
<div>resize</div>
</div>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 关于左右拖动方向的问题
如果你有打开我的 demo,可以看到 demo-2 左右布局升级版 的示例,找到右边节点的 css 把 direction 这个属性关掉,然后在试下拖动右边的容器
.right.aside .resize {
direction: rtl;
}
2
3
这时候你会发现红色边框之前光标是拖动的箭头,现在变成了普通的箭头了。真正可以拖动的跑到了最右边。
可是这时候往右拖动,div 往左扩充。往左拖动,div 反而开始收缩。这明显很反人类。
所以就用到 css 的 direction (opens new window) 属性。这个属性也就 2 个值,ltr
和 rtl
。具体的演示可以看 MDN 的文档的效果。
可以这么理解:
正常我们阅读顺序是 从左往右
,这时候 .resize 是在右下角,那如果我们把这个 右边的 .resize 的阅读方向改为 从右往左
。这时候 .resize 就能被换到左下角去,这时候拖动就符合我们人类直觉了。
而且我们显示的内容并不在 .resize 容器中,.resize 只是为了设置宽度,撑开父容器的宽度,所以.resize 的阅读方向并不影响网页上的显示
# 关于上下拖动方向的问题
可以看到我的 demo-3 上下左右拖动布局
这里我的方法和大佬的并不一样~
贴上大佬的 demo 地址 demo (opens new window)
我选择的方法是让 .resize 贴近我们要用于拖动的那条边。
可以看下面的示例图,我利用的是 div 的顺序,让 .resize 贴近边(.resize 不能设置定位,否则就不能撑开父节点了)
大佬的实现方式是用到了
transform: scale(100, -1);
第二个参数 -1 其实就是说在Y轴使用反方向,是的 .resize 的定点换到左上角去(我的demo没这么用,可以自行探索一下)
# 关于极限拖动不松开的问题
如果按标题所说,纯 css 实现拖动大小,大体木有问题!可是细节会出问题
比如我的 demo-1 里面就提出的:当左右拉动到极限时松开鼠标 ,就无法重新拉回去。就是拖动 div 到 div 都不能在扩张,鼠标已经超出了拖动的线的时候
比如下图:一直拖动,已经无法在扩张了,然后鼠标在红色箭头的位置松开
为什么会出现这样的情况?看下图的解释:
因为 .resize 在拖动过程中,浏览器一直在拿鼠标计算偏移距离,鼠标没松开的时候偏移都一直还在计算。可以看到 body 的宽度才 1457px。而 .resize 的宽度已经达到了 1517px。那就更加超过 .slide 的容器了。
这时候拖动的边界其实是在 1517px 的最右边,显然 .slide 容器已经看不到他的最右边的边界了
这种情况也很好解决,其实无非就是当前 .resize 的宽度已经超出了父容器的宽度了,父容器已经找不到 .resize 的边界了。
那么我们检测到这种情况的时候,把 .resize 的宽度重置为父容器的宽度
除了宽度问题,高度的拖动也会存在同样的问题,所以 demo-4 借助了 js 的能力。搞来如下的代码:
var observe = new MutationObserver(function (mutationsList, observer) {
var target = mutationsList[0] ? mutationsList[0].target : null
if (!target) {
return false
}
var parent = target.parentNode
var classList = target.classList
var isHorizontal = classList.value.indexOf('horizontal') !== -1
if (isHorizontal) {
var parentWidth = parent.clientWidth
var diffWidth = target.clientWidth - parentWidth
if (diffWidth > -18 || parentWidth * -1 === diffWidth) {
target.style.width = parentWidth + 'px'
}
} else {
var offsetTop = target.offsetTop + 16
var parentHeight = parent.clientHeight
var maxHeight = parentHeight - offsetTop
var diffHeight = target.clientHeight - maxHeight
if (diffHeight > 2) {
target.style.height = maxHeight + 'px'
}
}
})
var resizeDom = document.querySelectorAll('.js-demo .resize')
resizeDom.forEach(item => {
observe.observe(item, { attributes: true })
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
简单说一下代码的意思
- 找到所有 .resize 的节点(
document.querySelectorAll('.js-demo .resize')
) - 利用 MutationObserver (opens new window) 能力,监听 div 的变化
new MutationObserver()
- 每次 observe 只能监听一个节点,所以来了个循环
- 监听到该 div 变化的时候,根据 class 类名判断一下是往哪个方向移动的(左右的话就设置宽度,上下设置高度)
- 根据一些简单的数学公式,得出 div 什么时候被拖动到边界后就重置一下宽度或者高度
- 优化的点在于这个拖动的时候触发监听太频繁了,可以加个防抖(懒,就没加了)
具体的效果看 demo-4 吧! 不然我就白写了啊
# 最后
以上就是跟着大佬学习容器拖动修改大小的全部学习笔记
加入一些简单的 js 实现一个通用的拖动,毕竟纯靠 css 能实现固然最好,可是 css 做不了那么复杂的计算
包括 resize 属性虽然是 css,可是也是浏览器底层帮我们改变了节点的样式(可以理解为把这部分的逻辑给了浏览器帮我们做了)