给定一个 2x5 css
grid
覆盖整个视口定义为:
#grid-wrapper {
display: grid;
grid-template-columns: 50% 50%;
grid-template-rows: 50px 50px 50px calc(100vh - 200px) 50px;
grid-template-areas:
"header header"
"common-toolbar common-toolbar"
"left-toolbar right-toolbar"
"left-panel right-panel"
"footer footer";
width: 100vw;
height: 100vh;
}
left-panel
和 right-panel
网格单元都是 overflow-y: scroll;
可滚动 div
。
我希望
common-toolbar
和 (left/right)-toolbar
的行高不被定义为固定(50px
)高度,而是自动调整到其内容高度(工具栏高度,不是静态的),而 (left/right)-panel
仍然需要剩余的可用viewport高度。
类似的东西:
grid-template-rows: 50px auto auto calc(100vh - sum_of_height_of_other_rows) 50px;
这可以在
grid
中实现吗?
编辑:片段
const squares = [
['■', 'Black square'],
['□', 'White square'],
['▢', 'White square with rounded corners'],
['▣', 'White square containing small black square'],
['▤', 'Square with horizontal fill'],
['▥', 'Square with vertical fill'],
['▦', 'Square with orthogonal crosshatch fill'],
['▧', 'Square with upper left to lower right fill'],
['▨', 'Square with upper right to lower left fill'],
['▩', 'Square with diagonal crosshatch fill '],
]
const fruits = [
['☕', 'hot beverage'],
['⛾', 'restaurant'],
['🍅', 'tomato'],
['🍊', 'tangerine'],
['🍏', 'green apple'],
['🥑', 'avocado'],
['🍔', 'hamburger'],
['🍕', 'slice of pizza'],
['🍙', 'rice ball'],
['🍞', 'bread'],
['🍣', 'sushi'],
['🍨', 'ice cream'],
['🍭', 'lollipop'],
['🍲', 'pot of food'],
['🍷', 'wine glass'],
['🍼', 'baby bottle'],
['🍓', 'strawberry'],
]
const chess = [
['♔', 'white chess king'],
['♕', 'white chess queen'],
['♖', 'white chess rook'],
['♗', 'white chess bishop'],
['♘', 'white chess knight'],
['♙', 'white chess pawn'],
['♚', 'black chess king'],
['♛', 'black chess queen'],
['♜', 'black chess rook'],
['♝', 'black chess bishop'],
['♞', 'black chess knight'],
['♟', 'black chess pawn']
]
const fillToolbar = (toolbar, fillWith) => {
fillWith.forEach(([symbol, name]) => {
const $button = document.createElement('button')
$button.setAttribute("title", name)
$button.textContent = symbol;
toolbar.append($button)
})
}
fillToolbar(document.getElementById("common-toolbar"), squares)
fillToolbar(document.getElementById("left-toolbar"), fruits)
fillToolbar(document.getElementById("right-toolbar"), chess)
const fillText = (panel, count) => {
panel.innerHTML = '';
if (count > 0) {
panel.innerHTML =
"<p>start.<p> "
+ "<p>THE TEXT IS HERE LONG.<p> ".repeat(count)
+ "<p>end.<p> "
}
}
const count = 1000;
fillText(document.getElementById("left-panel"), count)
fillText(document.getElementById("right-panel-content"), count)
body {
margin: 0;
}
#grid-wrapper {
display: grid;
grid-template-columns: calc(50%-5px) 50%;
grid-template-rows: 50px 50px 50px calc(100vh - 200px) 50px;
grid-template-areas:
"header header"
"common-toolbar common-toolbar"
"left-toolbar right-toolbar"
"left-panel right-panel"
"footer footer";
width: 100vw;
height: 100vh;
column-gap: 10px;
}
#header {
background-color: aqua;
grid-column: span 2;
grid-area: header;
}
#common-toolbar {
background-color: blue;
grid-column: span 2;
grid-area: common-toolbar;
}
#left-toolbar {
background-color: blueviolet;
grid-area: left-toolbar;
}
#left-panel {
background-color: brown;
grid-area: left-panel;
overflow-y: scroll;
}
#right-toolbar {
background-color: chartreuse;
grid-area: right-toolbar;
}
#right-panel {
background-color: chocolate;
grid-area: right-panel;
}
#right-panel-content {
overflow-y: scroll;
height: 100%;
}
#footer {
background-color: crimson;
grid-column: span 2;
grid-area: footer;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test box</title>
</head>
<body>
<div id="grid-wrapper">
<div id="header">The head</div>
<div id="common-toolbar">Common Toolbar 1 2 3</div>
<div id="left-toolbar"></div>
<div id="left-panel"><div id="left-panel-content"></div></div>
<div id="right-toolbar">Right Toolbar l1 l2 l3</div>
<div id="right-panel"><div id="right-panel-content"></div></div>
<div id="footer">This is the footer</div>
</div>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
一种方法如下,这个主题有很多可能的变体(当然还有很多其他的);解释性注释在代码中:
/* some simple utility functions to avoid copious typing where possible */
const D = document,
create = (tag, props) => Object.assign(D.createElement(tag), props),
createSampleText = (wrapper = "p", count = 0) => {
let text = Array.from({
length: count
})
.map(() => words[random(0, words.length)])
.join(" ")
.replace(/[a-z]/, (match) => match.toUpperCase())
return create(wrapper, {
textContent: text
})
},
fragment = () => D.createDocumentFragment(),
get = (selector, context = D) => context.querySelector(selector),
getAll = (selector, context = D) => [...context.querySelectorAll(selector)],
random = (min, max) => Math.floor(Math.random() * (max - min) + min),
words = [
"lorem",
"ipsum",
"dolor",
"sit",
"amet",
"consectetur",
"adipisicing",
"elit",
"provident",
"unde",
"velit",
"totam",
"mollitia",
"iste",
"voluptatibus",
"iure",
"repellat",
"modi",
"non",
"expedita",
"facere",
"ducimus",
"maiores",
"tenetur",
"magnam",
"voluptate",
"placeat",
"perferendis",
"debitis",
"accusantium",
"hic",
"nisi",
"omnis",
"corrupti",
"quas",
"saepe",
"laborum",
"voluptates",
"ratione",
"porro",
"cupiditate",
"aliquam",
"quo",
"sint",
"doloribus",
"deleniti",
"ad",
"eum",
"consequatur",
"veniam",
"nemo",
"optio",
"quos",
"quasi",
"molestias",
"adipisci",
"delectus",
"eos",
"quae",
"et",
"necessitatibus",
"quis",
"libero",
"magni",
"enim",
"officiis",
"reprehenderit",
"laudantium",
"aut",
"accusamus",
"quisquam",
"ab",
"dolores",
"minus",
"rerum",
"consequuntur",
"illo",
"alias",
"id",
"praesentium",
"quam",
"error",
"ipsa",
];
/* an object created from your posted content-generation */
const content = [{
// String, CSS selector for the element fow which the
// following array would be the content:
selector: "common-toolbar",
content: [
["■", "Black square"],
["□", "White square"],
["▢", "White square with rounded corners"],
["▣", "White square containing small black square"],
["▤", "Square with horizontal fill"],
["▥", "Square with vertical fill"],
["▦", "Square with orthogonal crosshatch fill"],
["▧", "Square with upper left to lower right fill"],
["▨", "Square with upper right to lower left fill"],
["▩", "Square with diagonal crosshatch fill "],
],
},
{
selector: "left-toolbar",
content: [
["☕", "hot beverage"],
["⛾", "restaurant"],
["🍅", "tomato"],
["🍊", "tangerine"],
["🍏", "green apple"],
["🥑", "avocado"],
["🍔", "hamburger"],
["🍕", "slice of pizza"],
["🍙", "rice ball"],
["🍞", "bread"],
["🍣", "sushi"],
["🍨", "ice cream"],
["🍭", "lollipop"],
["🍲", "pot of food"],
["🍷", "wine glass"],
["🍼", "baby bottle"],
["🍓", "strawberry"],
],
},
{
selector: "right-toolbar",
content: [
["♔", "white chess king"],
["♕", "white chess queen"],
["♖", "white chess rook"],
["♗", "white chess bishop"],
["♘", "white chess knight"],
["♙", "white chess pawn"],
["♚", "black chess king"],
["♛", "black chess queen"],
["♜", "black chess rook"],
["♝", "black chess bishop"],
["♞", "black chess knight"],
["♟", "black chess pawn"],
],
},
];
// iterating over the content Array of Objects:
content.forEach(
// using destructuring assignment to retrieve the property-values of the
// named properties, and assigning them to variables of the same name as
// the property:
({
selector,
content
}) => {
// using getAll(), which takes a CSS selector and returns an Array of
// element nodes retrieved by the underlying document.querySelectorAll();
// iterating through the returned Array using Array.prototype.forEach()
getAll(`.${selector}`).forEach(
// el: a reference (the name is entirely irrelevant so long as it's valid
// in JavaScript) to the current element/node of the Array of elements/
// nodes:
(el) => {
// creating a document fragment to hold the content we're generating:
let frag = fragment()
// appending a new <span> element, with the textContent matching the
// (capitalised) 'selector' variable; this is created via the create()
// function defined above:
frag.append(
create("span", {
textContent: `${selector.replace(/[a-z]/, (a) => a.toUpperCase())}:`,
})
);
// here we iterate over the content Array, again using Array.prototype.forEach():
content.forEach(
// using Array destructuring, by which the first element of the Array is assigned
// to the variable 'text', and the second assigned to the variable 'title':
([text, title]) =>
// again using create() to create <button> elements, with the textContent of
// the emoji/glyph, the title set to the title property, and the type of
// the <button> set to "button" (in order to guard against accidental
// form submission (dependant on the use-case):
frag.append(
create("button", {
textContent: text,
title,
type: "button"
})
)
);
// we append the document fragment to the current element:
el.append(frag);
});
});
// retrieving all '.panel' elements, again using getAll(), along with Array.prototype.forEach():
getAll(".panel").forEach((el) => {
// creating a document fragment:
let frag = fragment();
// using Array.from() to create an Array of length 35:
Array.from({
length: 35
// iterating over the created Array; again using Array.prototype.forEach():
}).forEach(() => {
// in each iteration we call the created <p> element, each of which has
// 20 words:
frag.append(createSampleText("p", 20))
});
// appending the document fragment to the current '.panel' element:
el.append(frag)
});
/*
a simple, naive reset to remove browser default margins, padding, and font-size;
also ensuring that all elements are sized according to the same sizing algorithm,
that of 'border-box' which includes borders and padding within the assigned element
sizes:
*/
*,
::before,
::after {
box-sizing: border-box;
font-family: inherit;
font-size: inherit;
margin: 0;
padding: 0;
}
/*
ensuring that the <body> fills the viewport,
and setting the default font-family for the document:
*/
body {
block-size: 100vh;
font-family: system-ui;
}
main {
/* using a logical property ('block-size') to set the element's size on
the block-axis, the axis on which block elements are placed; in
most European languages (left-to-right, top-to-bottom) this is
equivalent to height (depending on the user's preferred language and
layout): */
block-size: 100%;
}
section {
/* filling 100% of the block-axis of the parent: */
block-size: 100%;
/* using CSS grid, as required by the question: */
display: grid;
/* defining two columns, each of 1 fraction (1fr) of the available space): */
grid-template-columns: repeat(2, 1fr);
/* defining four rows:
max-content: allowing the content of this row to take the maximum size
to fit its contents,
min-content: allowing only the minimum size in order to show the content,
1fr: one fraction of the available space after the explicit rows,
and those that have sizes constrained to their content have
been calculated,
min-content: as above */
grid-template-rows: max-content min-content 1fr min-content;
}
section > * {
/* defining the background-color of the various children of the <section>
element, using the var function which will take the value of the --bgc
custom property (if it's defined), and will default to papayawhip if
the custom property is not available: */
background-color: var(--bgc, papayawhip);
/* padding on the block axis, the first value relates to the
padding-block-start (in top-to-bottom languages this equates to 'padding-top',
and the second to padding-block-end, which equates to 'bottom' in the same
top-to-bottom languages: */
padding-block: 0.5em 0.3em;
/* padding on the inline-axis, the axis on which inline content is laid out (and
is perpendicular to the block-axis, so is the horizontal axis in left-to-right
languages), the use of a single value sets that value to both the
padding-inline-start and padding-inline-end: */
padding-inline: 0.7rem;
}
header,
.common-toolbar,
footer {
/*
these elements are positioned to start in the first grid-column (1),
and end in the last (-1), taking up the full grid-row:
*/
grid-column: 1 / -1;
}
h1 {
font-size: 2.5rem;
font-weight: 600;
}
header {
/* setting the --bgc custom property for the <header> element: */
--bgc: lavender;
}
.common-toolbar {
/* setting the --bgc custom property for the .common-toolbar element(s): */
--bgc: thistle;
/* and centering the content via the justify-content property: */
justify-content: center;
}
.panel {
--bgc: plum;
/* these elements have hidden overflow on the y axis: */
overflow-y: scroll;
/* for no particular reason I chose to use CSS nesting to set the property of
<p> elements found within the .panel element(s); note that this selects
all <p> elements, not just children; if you did wish to select only children
then:
& > p {...}
could be used instead: */
p {
margin-block: 0.5rem;
padding: 0.2rem;
}
}
footer {
--bgc: orchid;
}
.toolbar {
/* using flex layout on all .toolbar elements: */
display: flex;
/* blurring the content that appears "behind" the
.toolbar elements: */
backdrop-filter: blur(2px);
/* using color-mix() to set the background-colour of the elements,
this is the oklch colour space ('in oklch'), and produces a colour
that is 80% var(--bgc), and 20% #fff1 (a mostly-transparent white) */
background-color: color-mix(in oklch, var(--bgc) 80%, #fff1);
/* defining:
flex-direction: row;
flex-wrap: wrap;
in the flex shorthand
*/
flex-flow: row wrap;
gap: 0.5em;
padding: 0.2em;
/* using position: sticky to have the elements remain in place regardless
of the scroll of the parent's content: */
position: sticky;
top: 0;
left: 0;
}
.toolbar:not(.common-toolbar)>*:first-child {
flex-grow: 1;
}
.toobar button {
padding: 0.2em;
}
<main>
<section>
<header>
<h1>Arbitrary Heading</h1>
</header>
<div class="common-toolbar toolbar"></div>
<!-- please note that I've nested the 'left-toolbar' and 'right-toolbar'
within the relative panels; this isn't required but it more easily
associates the related content/controls (although obviously the CSS
that I've written is designed to work with this HTML) -->
<div class="left-panel panel">
<div class="left-toolbar toolbar left"></div>
</div>
<div class="right-panel panel">
<div class="right-toolbar toolbar right"></div>
</div>
<footer>Footer content</footer>
</section>
</main>
但是,如果您希望(或需要)将
.left-toolbar
和 .right-toolbar
元素保留在 .panel
元素之外,则以下方法也适用:
/* some simple utility functions to avoid copious typing where possible */
const D = document,
create = (tag, props) => Object.assign(D.createElement(tag), props),
createSampleText = (wrapper = "p", count = 0) => {
let text = Array.from({
length: count
})
.map(() => words[random(0, words.length)])
.join(" ")
.replace(/[a-z]/, (match) => match.toUpperCase())
return create(wrapper, {
textContent: text
})
},
fragment = () => D.createDocumentFragment(),
get = (selector, context = D) => context.querySelector(selector),
getAll = (selector, context = D) => [...context.querySelectorAll(selector)],
random = (min, max) => Math.floor(Math.random() * (max - min) + min),
words = [
"lorem",
"ipsum",
"dolor",
"sit",
"amet",
"consectetur",
"adipisicing",
"elit",
"provident",
"unde",
"velit",
"totam",
"mollitia",
"iste",
"voluptatibus",
"iure",
"repellat",
"modi",
"non",
"expedita",
"facere",
"ducimus",
"maiores",
"tenetur",
"magnam",
"voluptate",
"placeat",
"perferendis",
"debitis",
"accusantium",
"hic",
"nisi",
"omnis",
"corrupti",
"quas",
"saepe",
"laborum",
"voluptates",
"ratione",
"porro",
"cupiditate",
"aliquam",
"quo",
"sint",
"doloribus",
"deleniti",
"ad",
"eum",
"consequatur",
"veniam",
"nemo",
"optio",
"quos",
"quasi",
"molestias",
"adipisci",
"delectus",
"eos",
"quae",
"et",
"necessitatibus",
"quis",
"libero",
"magni",
"enim",
"officiis",
"reprehenderit",
"laudantium",
"aut",
"accusamus",
"quisquam",
"ab",
"dolores",
"minus",
"rerum",
"consequuntur",
"illo",
"alias",
"id",
"praesentium",
"quam",
"error",
"ipsa",
];
/* an object created from your posted content-generation */
const content = [{
// String, CSS selector for the element fow which the
// following array would be the content:
selector: "common-toolbar",
content: [
["■", "Black square"],
["□", "White square"],
["▢", "White square with rounded corners"],
["▣", "White square containing small black square"],
["▤", "Square with horizontal fill"],
["▥", "Square with vertical fill"],
["▦", "Square with orthogonal crosshatch fill"],
["▧", "Square with upper left to lower right fill"],
["▨", "Square with upper right to lower left fill"],
["▩", "Square with diagonal crosshatch fill "],
],
},
{
selector: "left-toolbar",
content: [
["☕", "hot beverage"],
["⛾", "restaurant"],
["🍅", "tomato"],
["🍊", "tangerine"],
["🍏", "green apple"],
["🥑", "avocado"],
["🍔", "hamburger"],
["🍕", "slice of pizza"],
["🍙", "rice ball"],
["🍞", "bread"],
["🍣", "sushi"],
["🍨", "ice cream"],
["🍭", "lollipop"],
["🍲", "pot of food"],
["🍷", "wine glass"],
["🍼", "baby bottle"],
["🍓", "strawberry"],
],
},
{
selector: "right-toolbar",
content: [
["♔", "white chess king"],
["♕", "white chess queen"],
["♖", "white chess rook"],
["♗", "white chess bishop"],
["♘", "white chess knight"],
["♙", "white chess pawn"],
["♚", "black chess king"],
["♛", "black chess queen"],
["♜", "black chess rook"],
["♝", "black chess bishop"],
["♞", "black chess knight"],
["♟", "black chess pawn"],
],
},
];
// iterating over the content Array of Objects:
content.forEach(
// using destructuring assignment to retrieve the property-values of the
// named properties, and assigning them to variables of the same name as
// the property:
({
selector,
content
}) => {
// using getAll(), which takes a CSS selector and returns an Array of
// element nodes retrieved by the underlying document.querySelectorAll();
// iterating through the returned Array using Array.prototype.forEach()
getAll(`.${selector}`).forEach(
// el: a reference (the name is entirely irrelevant so long as it's valid
// in JavaScript) to the current element/node of the Array of elements/
// nodes:
(el) => {
// creating a document fragment to hold the content we're generating:
let frag = fragment()
// appending a new <span> element, with the textContent matching the
// (capitalised) 'selector' variable; this is created via the create()
// function defined above:
frag.append(
create("span", {
textContent: `${selector.replace(/[a-z]/, (a) => a.toUpperCase())}:`,
})
);
// here we iterate over the content Array, again using Array.prototype.forEach():
content.forEach(
// using Array destructuring, by which the first element of the Array is assigned
// to the variable 'text', and the second assigned to the variable 'title':
([text, title]) =>
// again using create() to create <button> elements, with the textContent of
// the emoji/glyph, the title set to the title property, and the type of
// the <button> set to "button" (in order to guard against accidental
// form submission (dependant on the use-case):
frag.append(
create("button", {
textContent: text,
title,
type: "button"
})
)
);
// we append the document fragment to the current element:
el.append(frag);
});
});
// retrieving all '.panel' elements, again using getAll(), along with Array.prototype.forEach():
getAll(".panel").forEach((el) => {
// creating a document fragment:
let frag = fragment();
// using Array.from() to create an Array of length 35:
Array.from({
length: 35
// iterating over the created Array; again using Array.prototype.forEach():
}).forEach(() => {
// in each iteration we call the created <p> element, each of which has
// 20 words:
frag.append(createSampleText("p", 20))
});
// appending the document fragment to the current '.panel' element:
el.append(frag)
});
*,
::before,
::after {
box-sizing: border-box;
font-size: inherit;
margin: 0;
padding: 0;
}
body {
block-size: 100vh;
font-family: system-ui;
}
main {
background-color: lavenderblush;
block-size: 100%;
}
section {
align-self: stretch;
block-size: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: max-content repeat(2, min-content) 1fr min-content;
/* having seen this demo in full-page, I added the following line to prevent
the content from being impractically wide; the min() declaration will
return the smallest value of the supplied comma-separated values and
is able to compare relative and absolute sizes; in the following the
element will take 100% of the inline-axis (100vw) until that 100vw
evaluates to a size larger than 1300px at which point the size will
be constrained to 1300px: */
inline-size: min(100vw, 1300px);
/* the following is added simply to center the element on the inline-axis: */
margin-inline: auto;
}
section > * {
background-color: var(--bgc, papayawhip);
padding-block: 0.5em 0.3em;
padding-inline: 0.7rem;
}
header,
.common-toolbar,
footer {
grid-column: 1 / -1;
}
h1 {
font-size: 2.5rem;
font-weight: 600;
}
header {
--bgc: lavender;
}
.panel {
--bgc: plum;
overflow-y: scroll;
p {
margin-block: 0.5rem;
padding: 0.2rem;
}
}
footer {
--bgc: orchid;
}
.toolbar {
display: flex;
backdrop-filter: blur(2px);
gap: 0.5em;
padding: 0.2em;
}
.common-toolbar {
--bgc: thistle;
justify-content: center;
}
.toolbar:not(.common-toolbar) {
--bgc: mediumpurple;
}
.toolbar:not(.common-toolbar)>*:first-child {
flex-grow: 1;
}
.toobar button {
padding: 0.2em;
}
<!--
HTML below from a posted code snippet on Stack Overflow: https://stackoverflow.com/questions/77330430/setting-css-grid-row-height-to-remaining-viewport-height,
author: Alice Oualouest,
user-page: https://stackoverflow.com/u/5565079/
-->
<main>
<section>
<header>
<h1>Arbitrary Heading</h1>
</header>
<div class="common-toolbar toolbar"></div>
<!--
the left and right toolbar elements are now children
of the wrapping <section> element, rather than nested
within their "associated" .panel elements:
-->
<div class="left-toolbar toolbar left"></div>
<div class="right-toolbar toolbar right"></div>
<div class="left-panel panel">
</div>
<div class="right-panel panel">
</div>
<footer>Footer content</footer>
</section>
</main>
参考资料:
align-self
。backdrop-filter
。background-color
。block-size
。box-sizing
。display
。flex-grow
。font-family
。font-size
。font-weight
。gap
。grid-column
。grid-template-columns
。grid-template-rows
。inline-size
。justify-content
。left
。margin
。margin-block
。margin-inline
。overflow-y
。padding
。padding-block
。padding-inline
。position
。top
。