我正在尝试通过用鼠标拖动列分隔符(或调整占位符大小)来动态调整 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)?
仅使用 css 即可实现您想要实现的目标。我修改了你的例子。主要收获如下:
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>
解决方案是不使用明确的固定列大小(
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),工作正常并且尺寸更小。 (更新:看来该项目目前已被放弃,所以可能不是最好的选择)
这是一个具有可拖动装订线(调整大小处理程序)并使用 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
的数据属性的预定义分数大小