可拖拽拉宽改变元素宽度布局-ResizeLayout

最近有一个需求,要实现一个通过拖拽来改变区域的宽度。

PS: 文尾有 GitHub 地址,喜欢的小伙伴可以点个赞哦😉

详细描述

在两个区域中间,有一个拖拽的区域,当鼠标按下,左右拖动时出现一条线,当鼠标松开时,改变区域的宽度。

效果图如下:
效果图.gif

Demo

在线实例地址:https://georgeleoo.github.io/resize-layout/

思考

看到这个需求,感觉有点复杂,但是实际上很简单。我们只需把这个需求进行拆分即可简单化。

通过观察,我们可以将这个需求拆分成3个部分:

  1. 布局
  2. 拖动拖拽线
  3. 松开鼠标改变区域的宽度

需求1: 布局

对于这个布局来说很简单,有两个div,这两个div中间有一个拖拽区域和一根拖拽线。

图中红色为拖拽线,蓝色区域是拖拽区域,只有线是可拖拽的

如果我们就直接写布局代码,很简单,随随便便的就能写好。但是这样做的话,通用性不高,我希望的是对任意的布局都有拖拽改变区域宽度的功能。

我们要把这个功能做成一个通用组件,对任意的布局都可以进行拖拽,该如何去编辑组件呢?

我们先从使用方法开始去逆推组件的写法,我们可以把这个功能抽成组件。左右区域是一个组件,每个组件有两个区域,即内容区域和拖拽区域,然后将这两个组件放到一个大布局组件中进行子组件的限定。使用方法大概如下:

<ResizeLayout>
  <ResizeCol>左侧</ResizeCol>
  <ResizeCol>右侧</ResizeCol>
</ResizeLayout>

由此,我们可以知道,ResizeLayout 是一个区域布局,在这个布局中,有两个子组件 ResizeCol,分别表示是左侧内容和右侧内容。

有时候我们需要给左侧设置一个默认的宽度,右侧自动分配剩下的内容,那么就需要给 ResizeCol添加一个 width属性即可。

<ResizeLayout>
  <ResizeCol width="220px">左侧</ResizeCol>
  <ResizeCol>右侧</ResizeCol>
</ResizeLayout>

如何去自动平分剩下的宽度?

首先我们得遍历子组件ResizeCol,然后读取其自身上的 width 属性,并将其放入一个 list 中,如果某一组件未设置宽度,也要将数据保存到 list 中,只是 width 属性为空。这样才能计算出有多少个未设置宽度的子组件的数量。ResizeCol的数量:我们可以通过this.$slots.default来获取,然后再查找this.$slots.defaultcomponentOptions.tagResizeCol类型组件。

平均宽度的计算会使用如下简单的数学公示:
AW=\frac{W - HW}{NWC}
其中

  • AW:剩下未设置宽度的ResizeCol的平均宽度
  • W:ResizeLayout 的总宽度
  • HW:已设置宽度的ResizeCol的总宽度
  • NWC:剩下未设置宽度的ResizeCol的数量

需求2: 拖动拖拽线

我们先思考一下,要实现拖动这个功能需要哪些步骤?

要拖动拖拽线,我们必须把鼠标移动到可拖动的区域,而拖动的区域的拖拽线是定位在这个区域的右侧;接着按下鼠标左键,这个时候记录一下鼠标按下的位置startClientX,然后移动鼠标,记录moveClientX的值;通过观察,要改变这根拖拽线的位置,我们只需改变它的right值即可,这个right值其实就是startClientX - moveClientX,当right > 0时,表示向左拖动,当right < 0 时,表示向右侧拖动。

既然要操作鼠标,那么我们就需要监听鼠标事件,分别是mousedownmousemove事件来分别监听鼠标按下和移动事件。

    <div class="resize-col-line" @mousedown="handlerMouseDown" ></div>

    /**
     * @description 鼠标按下时
     */
    handlerMouseDown (e) {
      this.isMouseDown = true
      this.startClientX = e.clientX
      this.addEventListener()
    },
    /**
     * @description 设置监听事件
     */
    addEventListener () {
      document.addEventListener('mousemove', this.handlerMouseMove)
      // document.addEventListener('mouseup', this.handlerMouseUp)
    },
    /**
     * @description 鼠标移动时
     */
    handlerMouseMove (e) {
      this.moveClientX = e.clientX

      this.setResizeBarLineStyle()
    },
    /**
     * @description 设置 ResizeBarLine 的样式
     */
    setResizeBarLineStyle () {
      // 计算偏移量:鼠标按下的点到鼠标移动的点的偏移量
      let offset = this.startClientX - this.moveClientX
      // 最小宽度
      let minWidth = parseInt(this.minWidth)
      if (minWidth < 0) {
        console.warn('minWidth 不得小于0')
        minWidth = 0
      }
      // 当 offset 大于 0 时,表示向左拖动,小于 0 表示向右拖动
      if (offset >= 0) {
        // 当前元素的宽度
        const currentWidth = parseInt(
          this.ResizeLayout.colWidthList[this.index].width
        )
        // 若 当前元素的宽度 - 偏移量 < 最小宽度 时,则最终的偏移量就是 当前元素的宽度 - 最小宽度
        if (currentWidth - offset < minWidth) {
          offset = currentWidth - minWidth
        }
      } else {
        // 与当前元素相邻的元素的宽度
        const nextWidth = parseInt(
          this.ResizeLayout.colWidthList[this.index + 1].width
        )
        // 若 当前元素的宽度 - 偏移量 < 最小宽度 时,则最终的偏移量就是 当前元素的宽度 - 最小宽度
        if (nextWidth - Math.abs(offset) < minWidth) {
          offset = -(nextWidth - minWidth)
        }
      }
      this.resizeBarLineStyle = {
        right: offset + Math.ceil(this._resizeBarThick / 2) + 'px',
        borderLeftColor: this.borderLeftColor
      }
    },

需求3: 松开鼠标改变区域的宽度

要实现拖动这个功能又需要哪些步骤?

假设我们从图中的起始位置移动到绿色区域的位置,绿色区域的右边框的左侧区域就是鼠标松开后的最终的左侧区域宽度,绿色区域的右边框的右侧区域就是鼠标松开后的最终的右侧区域宽度。

松开鼠标后,由图可知:

最终的左侧区域宽度 = 原左侧ResizeCol的宽度 - right + 绿色区域的一半。
最终的右侧区域宽度 = 原右侧ResizeCol的宽度 - right - 绿色区域的一半。

绿色区域一半的原因是拖拽线始终在拖拽区域的中间

如果右侧有多列的话,只会改变与拖拽线相邻的那列。


向左拖拽图示
    setWidth() {
      // 当前元素的宽度
      const currentWidth = this.ResizeLayout.colWidthList[this.index].width;
      // 与当前元素相邻的元素的宽度
      const nextWidth = this.ResizeLayout.colWidthList[this.index + 1].width;

      // 当前元素最终的宽度
      let finalCurrentWidth =
        parseInt(currentWidth) -
        parseInt(this.resizeBarLineStyle.right) +
        Math.ceil(this.resizeBarWidth / 2);
      // 与当前元素相邻的元素最终的宽度
      let finalNextWidth =
        parseInt(nextWidth) +
        parseInt(this.resizeBarLineStyle.right) -
        Math.ceil(this.resizeBarWidth / 2);

      this.$set(
        this.ResizeLayout.colWidthList[this.index],
        "width",
        finalCurrentWidth + "px"
      );
      this.$set(
        this.ResizeLayout.colWidthList[this.index + 1],
        "width",
        finalNextWidth + "px"
      );
    },

这样我们就有个大概的思路了,完整代码可参考github,基本每一步都有注释,需要的小伙伴可以star一下。

最后在看一个利用ResizeLayout仿 VSCODE UI的效果图

Github地址:https://github.com/GeorgeLeoo/resize-layout.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335