在CSS网格中创建一个拖动条以调整div大小

56

我有一个包含两个盒子和一条垂直分隔线的容器div(以下是代码和fiddle)。

我正在使用CSS网格来定位容器内的元素。

我想要实现的是,根据垂直线的位置,使用该垂直线来水平调整这两个盒子的大小。

如果问题很基础,我很抱歉,我是新手网页开发人员,之前只使用过Python,已经尝试过Google和StackOverflow搜索,但所有的解决方案似乎都过于复杂,并且通常需要额外的库,我正在寻找一些更简单的JS-only方案。

HTML:

<div class="wrapper">
  <div class="box a">A</div>
  <div class="handler"></div>
  <div class="box b">B</div>
</div>

CSS:

body {
  margin: 40px;
}

.wrapper {
  display: grid;
  grid-template-columns: 200px 8px 200px;
  grid-gap: 10px;
  background-color: #fff;
  color: #444;
}

.box {
  background-color: #444;
  color: #fff;
  border-radius: 5px;
  padding: 20px;
  font-size: 150%;
  resize: both;
}

.handler{
    width: 3px;
    height: 100%;
    padding: 0px 0;
    top: 0;
    background: red;
    draggable: true;
}

https://jsfiddle.net/gv8Lwckh/6/


我不太明白您要实现的行为:现在A和B两个框的大小是相同的,那么当我向左拖动手柄时会发生什么呢?我猜A框应该会缩小,但是B框呢?如果将手柄向右拖动,A框会变大吗?B框会发生什么? - Terry
是的,没错。我认为只需将resize: both属性赋予我的box div,并将draggable: true属性赋予处理程序,我就可以完美地实现这一点。 - Aquazi
{btsdaf} - Terry
我很乐意帮你做这件事,你能告诉我如何使用JavaScript动态更改CSS的方法吗? - Aquazi
4个回答

113
你想要做的事情可以使用CSS Flexbox实现,不需要使用CSS Grid。坏消息是,HTML + CSS并不聪明,声明resizedraggable无法使布局灵活且可由用户交互调整。为此,您需要使用JS。好消息是,这实际上并不太复杂。以下是代码输出的快速截屏:

然而,为了理解我将要发布的代码,您需要熟悉以下内容:

  • 使用.addEventListener进行事件绑定。在这种情况下,我们将使用mousedownmouseupmousemove的组合来确定用户是否正在拖动元素。
  • CSS flexbox布局

解决方案说明

CSS的初始布局

首先,您需要使用CSS flexbox来布置您的盒子。我们只需在父元素上声明display: flex,然后使用flex: 1 1 auto(这意味着“让元素增长,让元素缩小,并具有相等的宽度”)。此布局仅在页面最初渲染时有效:

.wrapper {
  /* Use flexbox */
  display: flex;
}

.box {
  /* Use box-sizing so that element's outerwidth will match width property */
  box-sizing: border-box;

  /* Allow box to grow and shrink, and ensure they are all equally sized */
  flex: 1 1 auto;
}

监听拖动交互

您需要监听可能来自.handler元素的鼠标事件,并且需要一个全局标志来记住用户是否正在拖动:

var handler = document.querySelector('.handler');
var isHandlerDragging = false;

然后,您可以使用以下逻辑来检查用户是否正在拖动:

document.addEventListener('mousedown', function(e) {
  // If mousedown event is fired from .handler, toggle flag to true
  if (e.target === handler) {
    isHandlerDragging = true;
  }
});

document.addEventListener('mousemove', function(e) {
  // Don't do anything if dragging flag is false
  if (!isHandlerDragging) {
    return false;
  }

  // Set boxA width properly
  // [...more logic here...]
});

document.addEventListener('mouseup', function(e) {
  // Turn off dragging flag when user mouse is up
  isHandlerDragging = false;
});

计算A盒子的宽度

现在你需要计算A盒子的宽度(插入到上述代码中的[...more logic here...]占位符中),以便与鼠标的移动匹配。Flexbox将确保B盒子填充剩余空间:

// Get offset
var containerOffsetLeft = wrapper.offsetLeft;

// Get x-coordinate of pointer relative to container
var pointerRelativeXpos = e.clientX - containerOffsetLeft;

// Resize box A
// * 8px is the left/right spacing between .handler and its inner pseudo-element
// * Set flex-grow to 0 to prevent it from growing
boxA.style.width = (pointerRelativeXpos - 8) + 'px';
boxA.style.flexGrow = 0;

工作示例

var handler = document.querySelector('.handler');
var wrapper = handler.closest('.wrapper');
var boxA = wrapper.querySelector('.box');
var isHandlerDragging = false;

document.addEventListener('mousedown', function(e) {
  // If mousedown event is fired from .handler, toggle flag to true
  if (e.target === handler) {
    isHandlerDragging = true;
  }
});

document.addEventListener('mousemove', function(e) {
  // Don't do anything if dragging flag is false
  if (!isHandlerDragging) {
    return false;
  }

  // Get offset
  var containerOffsetLeft = wrapper.offsetLeft;

  // Get x-coordinate of pointer relative to container
  var pointerRelativeXpos = e.clientX - containerOffsetLeft;
  
  // Arbitrary minimum width set on box A, otherwise its inner content will collapse to width of 0
  var boxAminWidth = 60;

  // Resize box A
  // * 8px is the left/right spacing between .handler and its inner pseudo-element
  // * Set flex-grow to 0 to prevent it from growing
  boxA.style.width = (Math.max(boxAminWidth, pointerRelativeXpos - 8)) + 'px';
  boxA.style.flexGrow = 0;
});

document.addEventListener('mouseup', function(e) {
  // Turn off dragging flag when user mouse is up
  isHandlerDragging = false;
});
body {
  margin: 40px;
}

.wrapper {
  background-color: #fff;
  color: #444;
  /* Use flexbox */
  display: flex;
}

.box {
  background-color: #444;
  color: #fff;
  border-radius: 5px;
  padding: 20px;
  font-size: 150%;
  
  /* Use box-sizing so that element's outerwidth will match width property */
  box-sizing: border-box;
  
  /* Allow box to grow and shrink, and ensure they are all equally sized */
  flex: 1 1 auto;
}

.handler {
  width: 20px;
  padding: 0;
  cursor: ew-resize;
  flex: 0 0 auto;
}

.handler::before {
  content: '';
  display: block;
  width: 4px;
  height: 100%;
  background: red;
  margin: 0 auto;
}
<div class="wrapper">
  <div class="box">A</div>
  <div class="handler"></div>
  <div class="box">B</div>
</div>


19
如果你坚持使用网格(grid)而不切换到弹性布局(flex),那么这会是一个完美的答案。原帖中提到使用了CSS网格(grid)。 - Jacques
1
@Cryptopat 谢谢,可能是意外添加的。现在已经移除。 - Terry
2
能否设置盒子A的初始宽度? - James Fremen
3
我更喜欢这个答案,因为它使用了flexbox而不是CSS网格(支持更好),尽管问题标题首先提到了CSS网格。 - Jorge Lazo
1
@Terry,我在使用wrapper.offsetLeft时遇到了一些问题,因为包装元素位于其他div内部,但是使用wrapper.getBoundingClientRect().left可以解决问题。 - Luca Faggianelli
显示剩余11条评论

31

这是一个处理拖动事件的示例,但使用了CSS Grids

诀窍在于将grid-template-columns(或rows)设置为网格容器而不是网格项的大小。

let isLeftDragging = false;
let isRightDragging = false;

function ResetColumnSizes() {
  // when page resizes return to default col sizes
  let page = document.getElementById("pageFrame");
  page.style.gridTemplateColumns = "2fr 6px 6fr 6px 2fr";
}

function SetCursor(cursor) {
  let page = document.getElementById("page");
  page.style.cursor = cursor;
}

function StartLeftDrag() {
  // console.log("mouse down");
  isLeftDragging = true;

  SetCursor("ew-resize");
}

function StartRightDrag() {
  // console.log("mouse down");
  isRightDragging = true;

  SetCursor("ew-resize");
}

function EndDrag() {
  // console.log("mouse up");
  isLeftDragging = false;
  isRightDragging = false;

  SetCursor("auto");
}

function OnDrag(event) {
  if (isLeftDragging || isRightDragging) {
    // console.log("Dragging");
    //console.log(event);

    let page = document.getElementById("page");
    let leftcol = document.getElementById("leftcol");
    let rightcol = document.getElementById("rightcol");

    let leftColWidth = isLeftDragging ? event.clientX : leftcol.clientWidth;
    let rightColWidth = isRightDragging ? page.clientWidth - event.clientX : rightcol.clientWidth;

    let dragbarWidth = 6;

    let cols = [
      leftColWidth,
      dragbarWidth,
      page.clientWidth - (2 * dragbarWidth) - leftColWidth - rightColWidth,
      dragbarWidth,
      rightColWidth
    ];

    let newColDefn = cols.map(c => c.toString() + "px").join(" ");

    // console.log(newColDefn);
    page.style.gridTemplateColumns = newColDefn;

    event.preventDefault()
  }
}
#page {
  height: 100%;
  background-color: pink;
  display: grid;
  grid-template-areas: 'header header header header header' 'leftcol leftdragbar tabs tabs tabs' 'leftcol leftdragbar tabpages rightdragbar rightcol' 'leftcol leftdragbar footer footer footer';
  grid-template-rows: min-content 1fr 9fr 1fr;
  grid-template-columns: 2fr 6px 6fr 6px 2fr;
}


/*****************************/

#header {
  background-color: lightblue;
  overflow: auto;
  grid-area: header;
}

#leftcol {
  background-color: #aaaaaa;
  overflow: auto;
  grid-area: leftcol;
}

#leftdragbar {
  background-color: black;
  grid-area: leftdragbar;
  cursor: ew-resize;
}

#tabs {
  background-color: #cccccc;
  overflow: auto;
  grid-area: tabs;
}

#tabpages {
  background-color: #888888;
  overflow: auto;
  grid-area: tabpages;
}

#rightdragbar {
  background-color: black;
  grid-area: rightdragbar;
  cursor: ew-resize;
}

#rightcol {
  background-color: #aaaaaa;
  overflow: auto;
  grid-area: rightcol;
}

#footer {
  background-color: lightblue;
  overflow: auto;
  grid-area: footer;
}
<body onresize="ResetColumnSizes()">
  <div id="page" onmouseup="EndDrag()" onmousemove="OnDrag(event)">
    <div id="header">
      Header
    </div>
    <div id="leftcol">
      Left Col
    </div>
    <div id="leftdragbar" onmousedown="StartLeftDrag()"></div>
    <div id="tabs">
      Tabs
    </div>
    <div id="tabpages">
      Tab Pages
    </div>
    <div id="rightdragbar" onmousedown="StartRightDrag()"></div>
    <div id="rightcol">
      Rightcol
    </div>
    <div id="footer">
      Footer
    </div>
  </div>
</body>

https://codepen.io/lukerazor/pen/GVBMZK


2
干得好!我拿了你的代码并扩展了它,使其适用于垂直分割器和水平分割器。我通过动态添加和删除网格容器的onmouseup和onmousemove事件处理程序来实现这一点:https://jsfiddle.net/zabh8jev/ - Jonathan Elkins

4
实际上匹配问题!制作一个拖动条来调整CSS网格内的div大小。
这里提供了一种可能的方法,保留原始的 OP 布局和 CSS,使用 Grids。目标是捕捉 网格模板列 的原始状态,并将其转换为浮点数。浏览器总是以像素为单位计算,这些列加上间隙的总和表示容器元素的总宽度。这个总和必须始终相同,否则元素会跳动!请注意,使用网格和 screenX 这种方式可以避免鼠标按下时常见的跳动错误。添加了注释,这将允许在任意数量的列或行中应用逻辑,祝好运。通过使用 指针事件,它也可以从触摸设备上工作。

let target = document.querySelector("div") // Target container element
let md = false;     // Will be true at mouse down
let xorigin;        // Click origin X position
let gtcorigin = []; // Origin Grid Template Columns in pixels

const pointerdown = (e) => {
  if (e.target.classList[0] === "handler"){ // Filter to target the wanted element
    md = true;                              // Set mouse down
    xorigin = e.screenX;                    // Store the origin X position
    // Grid Template Columns, array of pixels as float
    gtcorigin = window.getComputedStyle(target)["grid-template-columns"].split(" ").map((a) => +(a.slice(0, -2)));
    document.body.style.cursor = "col-resize" // This makes things nice
    document.body.style.userSelect = "none"   // This makes things nice
  }
}
const pointerup = (e) => {
  md = false; // Reset bool at mouse up
  document.body.style.cursor = "pointer"
  document.body.style.userSelect = "unset"
}

const resizer = (e) => {
  if (md){ // Mouse is down hover the handler element
    let gtc = window.getComputedStyle(target)["grid-template-columns"].split(" ").map((a) => +(a.slice(0, -2)));       // Grid Template Columns, array of pixels as float
    let xdragdif = xorigin - e.screenX; // Move in pixels since the click
    gtc[0] = gtcorigin[0] - xdragdif    // First column, if negative, it will grow
    gtc[2] = gtcorigin[2] + xdragdif    // Third column
    gtc = gtc.map((a) => a+"px")        // Set back the values in string with "px"
    document.querySelector("console").textContent = gtc.join(" ") // !!! This is only for the demo
    target.style.gridTemplateColumns = gtc.join(" ") // Apply the new Grid Template Column as inline style. 
  }
}

// Attach all events on the largest container element. Here the body is used. 
document.body.addEventListener("pointerdown", pointerdown, false)
document.body.addEventListener("pointerup", pointerup, false)
document.body.addEventListener("pointermove", resizer, false)
body {
  margin: 40px;
  overflow-x: hidden
}

.wrapper {
  display: grid;
  grid-template-columns: 200px 8px 200px;
  grid-gap: 10px;
  background-color: #fff;
  color: #444;
}

.box {
  background-color: #444;
  color: #fff;
  border-radius: 5px;
  padding: 20px;
  font-size: 150%;
}

.handler{
  width: 3px;
  height: 100%;
  padding: 0px 0;
  top: 0;
  background: red;
  cursor: col-resize
}
<div class="wrapper">
  <div class="box">A</div>
  <div class="handler"></div>
  <div class="box">B</div>
</div>


<console></console>

这里没有限制,可以仅使用CSS进行增强,使用min-width和其他类似的规则,浮动值可以被检索以创建范围滑块等等。

4

我进行了更改,因此您可以添加更多的水平和垂直滑块。

test1.html:
<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="test1.css">
        <script src=  "test1.js" > </script>
    </head>
    <body>
        <div id="page" onmouseup="EndDrag()" onmousemove="OnDrag(event)">
            <div id="header">
                Header asdlkj flkdfj sdflkksdjf sd;flsdjf sd;flkjsd;fljsd;flsdj;fjsd f;sdlfj;sdlfj
            </div>
            <div id="leftcol">
                Left Col
            </div>
            <div id="leftdragbar" onmousedown="StartHDrag(1)"></div>
            <div id="tabs">
                Tabs
            </div>
            <div id="topdragbar" onmousedown="StartVDrag(2)"></div>
            <div id="tabpages">
                Tab Pages
            </div>
            <div id="rightdragbar" onmousedown="StartHDrag(3)"></div>
            <div id="rightcol">
                Rightcol
            </div>
            <div id="botdragbar" onmousedown="StartVDrag(4)"></div>
            <div id="footer">
                Footer
            </div>
        </div>
        <div id= 'status'></div>
    </body>
</html>

test1.css

body {
}

#page {
    height: 100vh;
    background-color: pink;
    display: grid;
    grid-template-areas:
        'header header header header header'
        'leftcol leftdragbar tabs tabs tabs'
        'leftcol leftdragbar topdragbar topdragbar topdragbar'
        'leftcol leftdragbar tabpages rightdragbar rightcol'
        'botdragbar botdragbar botdragbar botdragbar botdragbar'
        'footer footer footer footer footer';
    grid-template-rows: min-content 1fr 6px 9fr 6px 1fr;
    grid-template-columns: 2fr 6px 6fr 6px 2fr; 
}

/*****************************/
#header {
    background-color: lightblue;
    overflow: auto;
    grid-area: header;
}

#leftcol {
    background-color: #aaaaaa;
    overflow: auto;
    grid-area: leftcol;
}

#leftdragbar {
    background-color: black;
    grid-area: leftdragbar;
    cursor: ew-resize;
}

#topdragbar {
    background-color: black;
    grid-area: topdragbar;
    cursor: ns-resize;
}

#botdragbar {
    background-color: black;
    grid-area: botdragbar;
    cursor: ns-resize;
}

#tabs {
    background-color: #cccccc;
    overflow: auto;
    grid-area: tabs;
}

#tabpages {
    background-color: #888888;
    overflow: auto;
    grid-area: tabpages;
}

#rightdragbar {
    background-color: black;
    grid-area: rightdragbar;
    cursor: ew-resize;
}


#rightcol {
    background-color: #aaaaaa;
    overflow: auto;
    grid-area: rightcol;
}

#footer {
    background-color: lightblue;
    overflow: auto;
    grid-area: footer;
}

test1.js

let isHDragging = false;
let isVDragging = false;

let cols = ['2fr','6px','6fr','6px','2fr'];  //grid-template-columns: 2fr 6px 6fr 6px 2fr;
let colns = ['leftcol','','tabpages','','rightcol'];
let Tcols = [];

let rows = ['min-content','1fr','6px','9fr','6px','1fr'];  //grid-template-rows: min-content 1fr 6px 9fr 1fr
let rowns = ['header','tabs','','tabpages','','footer'];
let Trows = []
let CLfactor ;
let CRfactor ;
let gWcol = -1;
let gWrow = -1;

function StartHDrag(pWcol) {
    isHDragging = true;
    SetCursor("ew-resize");
    CLfactor = parseFloat(cols[pWcol-1]) / document.getElementById(colns[pWcol-1]).clientWidth;
    CRfactor = parseFloat(cols[pWcol+1]) / document.getElementById(colns[pWcol+1]).clientWidth;
    Tcols = cols.map(parseFloat);
    gWcol = pWcol;
}

function StartVDrag(pRow) {
    isVDragging = true;
    SetCursor("ns-resize");
    CLfactor = parseFloat(rows[pRow-1]) / document.getElementById(rowns[pRow-1]).clientHeight;
    CRfactor = parseFloat(rows[pRow+1]) / document.getElementById(rowns[pRow+1]).clientHeight;
    Trows = rows.map(parseFloat);
    gWrow = pRow;
}

function SetCursor(cursor) {
    let page = document.getElementById("page");
    page.style.cursor = cursor;
}

function EndDrag() {
    isHDragging = false;
    isVDragging = false;
    SetCursor("auto");
}

function OnDrag(event) {
    if(isHDragging) {
        Tcols[gWcol-1] +=  (CLfactor * event.movementX);
        Tcols[gWcol+1] -=  (CLfactor * event.movementX);
        
        cols[gWcol-1]  = Math.max(Tcols[gWcol-1],0.01) + "fr";
        cols[gWcol+1]  = Math.max(Tcols[gWcol+1],0.01) + "fr";
        let newColDefn = cols.join(" ");
        page.style.gridTemplateColumns = newColDefn;
        
    } else if (isVDragging) {
        Trows[gWrow-1] +=  (CLfactor * event.movementY);
        Trows[gWrow+1] -=  (CLfactor * event.movementY);
        
        rows[gWrow-1]  = Math.max(Trows[gWrow-1],0.01) + "fr";
        rows[gWrow+1]  = Math.max(Trows[gWrow+1],0.01) + "fr";
        let newRowDefn = rows.join(" ");
        page.style.gridTemplateRows = newRowDefn;
        document.getElementById("footer").innerHTML = newRowDefn;
    }
    event.preventDefault()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接