实现分栏宽度拉伸调整的方法

最近在进行开发时,遇到需要动态拉升左侧区域,从而改变左右两栏尺寸的布局需求。这是一个很常见的场景,通过 DOM 操作来实现是第一个想到的方法。仔细查找资料,在大神张鑫旭的博客上发现还有一个性能更好,利用纯 CSS 就能实现分栏宽度拉伸调整的效果。下面的代码和部分内容引用自该博客:

首先要实现的效果,拖拽分隔线,动态改变左右两栏尺寸。
Alt text

一、HTML 代码:
<!--HTML  代码-->
<div class = "column">
  <div class = "column-left">
    <div class="resize-bar">  </div>
     <div class="resize-line">  </div>
     <div class="resize-content"> 左侧的内容,左侧的内容,左侧的内容,左侧的内容 </div>
  </div>
    <div class = "column-right">
        右侧的内容,右侧的内容,右侧的内容,右侧的内容,右侧的内容,右侧的内容
     </div>
</div>
二、CSS 代码
/* CSS 代码*/
.column {
  overflow: hidden;
}
.column-left {
  height: 400px; /*此处高度不能设置100%,否则无法拖动,原因是100%无 scrollbar*/
  background-color: #fff;
  position: relative;
  float: left;
}
.column-right {
  height: 400px;
  padding: 16px;
  background-color: #eee;
  box-sizing: border-box;
  overflow: hidden;
 }
.resize-content {
  position: absolute;
  top: 0; right:5px; bottom: 0 ; left: 0;
  padding: 16px;
  overflow-x:  hidden;
}
.resize-bar {
  width: 200px; height: inherit;
  resize: horizontal;
  cursor: ew-resize;
  cursor: col-resize;
  opacity: 0;
  overflow: scroll;
}
/*拖拽线*/
.resize-line {
  position: absolute;
  right: 0; top: 0; bottom: 0;
  border-right: 2px solid #eee;
  border-left: 1px solid #bbb;
  pointer-events: none;
}
.resize-bar:hover ~ .resize-line, .resize-bar:active ~ .resize-line {
  border-left: 1px dashed skyblue;
}
.resize-bar::-webkit-scrollbar {
  width: 200px; height: inherit;
}
/*Firefox 只有下面一小块区域可以拉伸*/
@supports (-moz-user-select: none) {
  .resize-bar:hover ~ .resize-line, .resize-bar:active ~ .resize-line {
    border-left: 1px solid #bbb;
  }
  .resize-bar:hover ~ .resize-line::after, .resize-bar:active ~ .resize-line::after {
    content: ' ';
    position: absolute;
    width: 16px; height: 16px;
    bottom: 0; right: -8px;
    background: none;
    background-size: 100% 100%;
  }
}

~ 表示兄弟组合器,类似于相邻的兄弟组合器。区别在于,第二个选择器不必紧跟第一个选择器,这意味着它将选择第一个选择器后面的所有元素。

div ~ p { background-color: blue;}
<div id = "container">
  <p>First  </p>  <!--nothing-->
  <div><p>Child Paragraph</p> </div><!--nothing-->
  <p> Second </p> <!--background-color: blue-->
   <p> Third </p> <!--background-color: blue-->
</div>
三、原理

利用浏览器非 overflow: auto 的属性,设置元素 resize 可以拉伸的特性,从而实现纯 CSS 的分栏宽度控制。

webkit 浏览器下滚动条可以自定义,其中 resize 区域大小就是 scrollbar 的大小,于是,我们就可以将整个拉伸区域变成和容器一样高。

四、属性 resize 参数

resize 属性主要是用来改变元素尺寸大小的,其主要目的是增强用户体验。

resize: none | both | horizontal | vertical | inherit

在 CSS3 中 resize 属性指定的值分为以下几种。

  • none: 用户不能拖动元素修改尺寸大小。
  • both: 用户可以拖动元素,同时修改元素的高度和宽度。
  • horizontal: 用户可以拖动元素,仅可以改变元素的宽度,但不能修改元素的高度;
  • vertical: 用户可以拖动元素,仅可以改变元素的高度,但不能修改元素的宽度;
  • inherit: 继承父元素的 resize 属性值。
五、利用组件 react-resizable

Alt text

1、安装 react-resizable
npm install --save react-resizable
2、使用 react-resizable, demo 如下
import React from 'react';
const Resizable = require('react-resizable').Resizable; // or,
const ResizableBox = require('react-resizable').ResizableBox;

// ES6
import { Resizable, ResizableBox } from 'react-resizable';

export default class TestLayout extends React.Component {
  state = {width: 200, height: 200};

  onClick = () => {
    this.setState({width: 200, height: 200});
  };

  onResize = (event, {element, size}) => {
    this.setState({width: size.width, height: size.height});
  };

  render() {
    return (
      <div>
        <button onClick={this.onClick} style={{'marginBottom': '10px'}}>Reset first element's width/height</button>
        <div className="layoutRoot">
          <Resizable className="box" height={this.state.height} width={this.state.width} onResize={this.onResize}>
            <div className="box" style={{width: this.state.width + 'px', height: this.state.height + 'px'}}>
              <span className="text">{"Raw use of <Resizable> element. 200x200, no constraints."}</span>
            </div>
          </Resizable>
          <ResizableBox className="box" width={200} height={200}>
            <span className="text">{"<ResizableBox>, same as above."}</span>
          </ResizableBox>
          <ResizableBox className="box" width={200} height={200} draggableOpts={{grid: [25, 25]}}>
            <span className="text">Resizable box that snaps to even intervals of 25px.</span>
          </ResizableBox>
          <ResizableBox className="box" width={200} height={200} minConstraints={[150, 150]} maxConstraints={[500, 300]}>
            <span className="text">Resizable box, starting at 200x200. Min size is 150x150, max is 500x300.</span>
          </ResizableBox>
          <ResizableBox className="box box3" width={200} height={200} minConstraints={[150, 150]} maxConstraints={[500, 300]}>
            <span className="text">Resizable box with a handle that only appears on hover.</span>
          </ResizableBox>
          <ResizableBox className="box" width={200} height={200} lockAspectRatio={true}>
            <span className="text">Resizable square with a locked aspect ratio.</span>
          </ResizableBox>
          <ResizableBox className="box" width={200} height={120} lockAspectRatio={true}>
            <span className="text">Resizable rectangle with a locked aspect ratio.</span>
          </ResizableBox>
          <ResizableBox className="box" width={200} height={200} axis="x">
            <span className="text">Only resizable by "x" axis.</span>
          </ResizableBox>
          <ResizableBox className="box" width={200} height={200} axis="y">
            <span className="text">Only resizable by "y" axis.</span>
          </ResizableBox>
          <ResizableBox className="box" width={200} height={200} axis="both">
            <span className="text">Resizable ("both" axis).</span>
          </ResizableBox>
          <ResizableBox className="box" width={200} height={200} axis="none">
            <span className="text">Not resizable ("none" axis).</span>
          </ResizableBox>
        </div>
      </div>
    );
  }
}
/*styles.css*/
.react-resizable {
  position: relative;
  height: 100%;
  float: left;
}
.react-resizable-handle {
  position: absolute;
  width: 1px;
  height: 100%;
  bottom: 0;
  right: 0;
  background: #c0c7d0;
  padding: 0;
  background-repeat: repeat;
  background-origin: content-box;
  box-sizing: border-box;
  cursor: col-resize;
  top: 0 !important;
}
3、<Resizable><ResizableBox>的参数
{
  children: React.Element<any>,
  width: number,
  height: number,
  // If you change this, be sure to update your css
  handleSize: [number, number] = [10, 10],
  lockAspectRatio: boolean = false,
  axis: 'both' | 'x' | 'y' | 'none' = 'both',
  minConstraints: [number, number] = [10, 10],
  maxConstraints: [number, number] = [Infinity, Infinity],
  onResizeStop?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any,
  onResizeStart?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any,
  onResize?: ?(e: SyntheticEvent, data: ResizeCallbackData) => any,
  draggableOpts?: ?Object
};