纯css实现多栏拖动大小

2 篇文章 0 订阅

本文的代码和实现思路参考
公众号 - iCSS 前端趣闻:CSS 实现可拉伸调整尺寸的分栏布局
下文为记录和整理学习的过程中自身的思考。如有侵权请联系我删除

纯 css 实现多栏拖动大小

查看效果演示

实现原理

核心是使用 css 的 resize 属性。当节点拖动的时候,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;
}

第二个核心原理:宽高 16pxscaleXscaleY

  • 为什么是 16px?

因为把一个节点设置为 resize 后,他的可以拖动变化宽高的边框是 16px~17px。chrome 是 17,火狐只有缩小到 16px 的时候上下箭头才会被隐藏,所以就把火狐预估为 16px。所以还是稳妥起见取小不取大,以免多余元素没被隐藏到


  • scaleXscaleY 的作用

就算设置了 resize 属性,也并不是整个 div 的边框都能拖动,能用于拖动的只有下图中红色框圈中的区域,其余的边框是不能触发 resize 效果的。

而根据上面的可以得知,红色框框的大小可以理解为 16*16px。如果我们想让整个容器的边框都可以拖动,只需要把这个 16* 16 往对应的方向拉大即可。如下图:

往垂直方向,放大 10 倍,红框 1 中整条范围都可以触发 resize 了

换为横向同理:

所以 scaleXscaleY 是为了对应横向/竖向的拖动的方块放大足够大的距离。而这个放大的倍数目前给的是 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>

关于左右拖动方向的问题

如果你有打开我的 demo,可以看到 demo-2 左右布局升级版 的示例,找到右边节点的 css 把 direction 这个属性关掉,然后在试下拖动右边的容器

.right.aside .resize {
  direction: rtl;
}

这时候你会发现红色边框之前光标是拖动的箭头,现在变成了普通的箭头了。真正可以拖动的跑到了最右边。

可是这时候往右拖动,div 往左扩充。往左拖动,div 反而开始收缩。这明显很反人类。

所以就用到 css 的 direction 属性。这个属性也就 2 个值,ltrrtl。具体的演示可以看 MDN 的文档的效果。

可以这么理解:
正常我们阅读顺序是 从左往右,这时候 .resize 是在右下角,那如果我们把这个 右边的 .resize 的阅读方向改为 从右往左。这时候 .resize 就能被换到左下角去,这时候拖动就符合我们人类直觉了。

而且我们显示的内容并不在 .resize 容器中,.resize 只是为了设置宽度,撑开父容器的宽度,所以.resize 的阅读方向并不影响网页上的显示

关于上下拖动方向的问题

可以看到我的 demo-3 上下左右拖动布局

这里我的方法和大佬的并不一样~

贴上大佬的 demo 地址 demo

我选择的方法是让 .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 })
})

简单说一下代码的意思

  1. 找到所有 .resize 的节点(document.querySelectorAll('.js-demo .resize'))
  2. 利用 MutationObserver 能力,监听 div 的变化 new MutationObserver()
  3. 每次 observe 只能监听一个节点,所以来了个循环
  4. 监听到该 div 变化的时候,根据 class 类名判断一下是往哪个方向移动的(左右的话就设置宽度,上下设置高度)
  5. 根据一些简单的数学公式,得出 div 什么时候被拖动到边界后就重置一下宽度或者高度
  6. 优化的点在于这个拖动的时候触发监听太频繁了,可以加个防抖(懒,就没加了)

具体的效果看 demo-4 吧! 不然我就白写了啊

最后

以上就是跟着大佬学习容器拖动修改大小的全部学习笔记

加入一些简单的 js 实现一个通用的拖动,毕竟纯靠 css 能实现固然最好,可是 css 做不了那么复杂的计算

包括 resize 属性虽然是 css,可是也是浏览器底层帮我们改变了节点的样式(可以理解为把这部分的逻辑给了浏览器帮我们做了)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值