使用鼠标动态调整CSS网格布局中的列大小

问题描述 投票:0回答:3

我正在尝试通过用鼠标拖动列分隔符(或调整占位符大小)来动态调整 css 网格布局框的大小。

我在

resize: horizontal;
元素上设置
nav
来调整大小,当我拖动元素右下角的小调整大小手柄时,它会调整大小,但相邻列的宽度不会自动调整,从而导致重叠。这是一个损坏的 codepen

HTML

<main>
 <nav>#1</nav>
 <header>#2</header>
 <section>#3</section>
</main>

CSS

main {
    display: grid;
    border: 3px dotted red;
    grid-gap: 3px;
    grid-template-columns: 200px 1fr;
    grid-template-rows: 100px 1fr;
    height: 100%;
}

nav {
    grid-column: 1;
    grid-row: 1;
    grid-row: 1 / span 2;
    resize: horizontal;
    overflow: scroll;
    border: 3px dotted blue;
}

我期望 css 网格引擎能够自动处理这种情况,但显然它没有。

我尝试了 jquery-ui 可调整大小,但它似乎不适用于 css 网格。

我正在研究如何通过将网格属性

grid-template-columns/rows:
设置为动态值来使用jquery来做到这一点,但不清楚如何捕获通过调整大小句柄调整元素大小而引发的事件。 jquery resize 事件仅在
window
对象上触发,而不是在 dom 元素上触发。

有什么方法可以做到这一点而不必处理低级鼠标事件(如dragstart/dragend)?

javascript jquery css resize css-grid
3个回答
55
投票

仅使用 css 即可实现您想要实现的目标。我修改了你的例子。主要收获如下:

  1. 最重要的是,尽量不要在语义布局标签中插入原始内容。使用标题、段落和列表标签,而不是文本和 br 标签。这使您的代码更易于阅读和推理。您的许多问题都是由于网格区域中回流的处理方式而发生的。
  2. 使用网格模板来简化布局,因为它将使稍后的断点回流变得更容易。
  3. 使用溢出:自动;并指定调整大小:垂直/水平。如果不设置溢出,调整大小将会失败。
  4. 使用最小/最大宽度/高度值来创建调整大小的边界。

body {
    margin: 10px;
    height: 100%;
}

main {
    display: grid;
    border: 3px dotted red;
    padding: 3px;
    grid-gap: 3px;
    
    grid-template: 
    "nav head" min-content
    "nav main" 1fr
        / min-content 1fr;
}

nav {
    grid-area: nav;
    border: 3px dotted blue;
    overflow: auto;
    resize: horizontal;
    
    min-width: 120px;
    max-width: 50vw;
}

header {
    grid-area: head;
    border: 3px dotted orange;
    overflow: auto;
    resize: vertical;
    
    min-height: min-content;
    max-height: 200px;
}

section {
    grid-area: main;
    border: 3px dotted gray;
}
<main>
  <nav>
    <ul>
      <li>Nav Item</li>
      <li>Nav Item</li>
      <li>Nav Item</li>
      <li>Nav Item</li>
      <li>Nav Item</li>
      <li>Nav Item</li>
    </ul>
  </nav>

  <header>
    <h1>Header Title</h1>
    <small>Header Subtitle</small>
  </header>

  <section>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  </section>
</main>


16
投票

解决方案是使用明确的固定列大小(

grid-template-columns: 200px 1fr;
),而是使用相对列大小,例如
grid-template-columns: 0.2fr 1fr;
——然后网格CSS引擎将处理相邻框的大小调整。接下来的事情是在网格框中添加嵌套的 div,将它们的最小高度/宽度设置为 100%,并通过例如调整它们的大小。 jqueryui 可调整大小 或任何其他库。

修复了jsfiddle

/* Javscript */

$('.left_inner').resizable();
$('.right_top_inner').resizable();
$('.right_bottom_inner').resizable();
/* CSS */

    .grid {
        display: grid;
        grid-template-columns: 0.2fr 1fr;
        grid-template-rows: 1fr 4fr;
        grid-gap: 3px;
        position: relative;
    }

    .left {
        grid-row: 1 / span 2;
    }

    .right_top {
        grid-column: 2;
        grid-row: 1;
    }

    .right_bottom {
        grid-column: 2;
        grid-row: 2;
    }

    .left_inner {
        background-color: #fedcd2;
        padding: 0;
        width: 100%;
        min-width: 100%;
        height: 100%;
        min-height: 100%;
        text-align: center;
    }

    .right_top_inner {
        background-color: #f9cf00;
        padding: 0;
        width: 100%;
        min-width: 100%;
        height: 100%;
        min-height: 100%;
        text-align: center;
    }

    .right_bottom_inner {
        background-color: #f8eee7;
        padding: 0;
        width: 100%;
        min-width: 100%;
        height: 100%;
        min-height: 100%;
        text-align: center;
    }
<!-- HTML -->

<script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/css/smoothness/jquery-ui-1.10.0.custom.min.css" />
<script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/jquery-ui.js"></script>

    <main class="grid">
     <aside class='left'>
      <div class="left_inner">
        drag the bottom right handle to resize
      </div>
     </aside>
     <section class="right_top">
      <div class="right_top_inner">right_top_inner</div>
     </section>
     <section class="right_bottom">
      <div class="right_bottom_inner">right_bottom_inner</div>
     </section>
    </main>

❗️ 虽然这在最简单的场景中有效,但在现实生活中的用例中会出现问题。我尝试了jquery-ui布局,它工作得更好一些(这里有一个demo),但是库已经过时了,并且框架有问题,所以我使用了angular-split-pane(基于角度1),工作正常并且尺寸更小。 (更新:看来该项目目前已被放弃,所以可能不是最好的选择)


0
投票

这是一个具有可拖动装订线(调整大小处理程序)并使用 CSS 网格

的解决方案

// DOM utils:
const el = (sel, par = document) => document.querySelector(sel);
const els = (sel, par = document) => document.querySelectorAll(sel);
const elNew = (tag, prop = {}) => Object.assign(document.createElement(tag), prop);

// Resizable
let isResizing = false;
const resizableGrid = (elParent, idx) => {

  const isVert = elParent.classList.contains("panes-v");
  const elsPanes = elParent.querySelectorAll(":scope > .pane");

  let fr = [...elsPanes].map(() => 1 / elsPanes.length);
  let elPaneCurr = null;
  let paneIndex = -1;
  let frStart = 0;

  const frToCSS = () => {
    elParent.style[isVert ? "grid-template-rows" : "grid-template-columns"] = fr.join("fr ") + "fr";
  };

  const pointerDown = (evt) => {
    if (isResizing || !evt.target.closest(".gutter")) return;
    isResizing = true;
    elPaneCurr = evt.currentTarget;
    fr = [...elsPanes].map((elPane) => isVert ? elPane.clientHeight / elParent.clientHeight : elPane.clientWidth / elParent.clientWidth);
    paneIndex = [...elsPanes].indexOf(elPaneCurr);
    frStart = fr[paneIndex];
    frNext = fr[paneIndex + 1];
    addEventListener("pointermove", pointerMove);
    addEventListener("pointerup", pointerUp);
  };

  const pointerMove = (evt) => {
    evt.preventDefault();
    const paneBCR = elPaneCurr.getBoundingClientRect();
    const parentSize = isVert ? elParent.clientHeight : elParent.clientWidth;
    const pointer = {
      x: Math.max(0, Math.min(evt.clientX - paneBCR.left, elParent.clientWidth)),
      y: Math.max(0, Math.min(evt.clientY - paneBCR.top, elParent.clientHeight))
    };
    const frRel = pointer[isVert ? "y" : "x"] / parentSize;
    const frDiff = frStart - frRel;
    fr[paneIndex] = Math.max(0.05, frRel);
    fr[paneIndex + 1] = Math.max(0.05, frNext + frDiff);
    frToCSS();
  };

  const pointerUp = (evt) => {
    removeEventListener("pointermove", pointerMove);
    removeEventListener("pointerup", pointerUp);
    isResizing = false;
  };

  [...elsPanes].slice(0, -1).forEach((elPane, i) => {
    elPane.append(elNew("div", {
      className: "gutter"
    }));
    elPane.addEventListener("pointerdown", pointerDown);
  });
  frToCSS();
};

els(".panes").forEach(resizableGrid);
* { margin: 0; box-sizing: border-box; }
html, body { height: 100dvh; font: 1.1rem/1.4 sans-serif; display: flex; flex-direction: column; }
nav { padding: 1rem; }
main { flex: 1; }

/* Resizable grid */

.panes {
  touch-action: none;
  display: grid;
}

.pane {
  position: relative;
  overflow: hidden;
}

.gutter {
  position: absolute;
  bottom: 0;
  right: 0;
  z-index: 3;
  background: #000;
}

.panes-h > .pane > .gutter {
  cursor: col-resize;
  top: 0;
  bottom: 0;
  width: 8px;
}

.panes-v > .pane > .gutter {
  cursor: row-resize;
  left: 0;
  height: 8px;
}
<nav>Resizable grid example</nav>
<main class="panes panes-v">
  <div class="pane panes panes-h">
    <div class="pane" style="background: #0bf;">HTML</div>
    <div class="pane" style="background: #f0b;">CSS</div>
    <div class="pane panes panes-v" style="background: #fb0;">
      <div class="pane" style="background: #fb0;">JavaScript</div>
      <div class="pane" style="background: #333; color: #fff;">$ |</div>
    </div>
  </div>
  <div class="pane" style="background: #ccc;">OUTPUT</div>
</main>

如何运作

它使用

grid-template-rows
grid-template-columns
并在装订线手柄拖动时动态修改它们的
fr
单位值。 装订线是动态生成的。

我留给读者的一些缺失的改进是添加,即:

data-fr="0.2"
作为每个
.pane

的数据属性的预定义分数大小
© www.soinside.com 2019 - 2024. All rights reserved.