javascript 密集网格布局中的响应式居中,具有重复自动拟合功能

ldxq2e6h  于 2023-03-28  发布在  Java
关注(0)|答案(2)|浏览(89)

我有一个响应式网格,其中每个项目都有自己的详细信息部分,默认情况下是隐藏的。单击项目会在详细信息部分添加一个类以显示它。
HTML/CSS看起来像这样:

<div class="grid">
    <div class="item">Item 1</div>
    <div class="details">Item 1 Details</div>

    <!-- ... -->

    <div class="item">Item n</div>
    <div class="details">Item n Details</div>
</div>
.grid {
    display: grid;
    grid-auto-flow: dense;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.item {
    /* nothing relevant */
}

.details {
    grid-column: 1 / -1;
    max-height: 0;
    padding: 0;
    opacity: 0;
    overflow: hidden;
}

.details.selected {    
    max-height: unset;
    padding: 1rem 2rem;
    opacity: 1;
}

详细信息跨越整行,密集的自动流允许其他项目填充空白。
这个效果很好,有足够的物品看起来很棒。
然而,让我们假设宽度为1000px,这意味着5个项目可以彼此相邻。如果只有1个或2个项目,则它们右侧的空白空间太多。我想将这些项目居中,但使用当前的CSS,网格填充了空单元格。这似乎是由grid-column: 1/ -1引起的。
这是它目前的样子,只有两个项目:

这是我希望它看起来的样子:

在这两种情况下,如果有5个以上的项目,它看起来会像这样,这很好。我也可以将底行居中,以防有灵活的解决方案。

我怎样才能实现这种行为呢?它不需要是网格。也可以是flex,或者任何其他解决方案,如果dense行为可以用它来实现的话。
有点相关的问题,不是我现在的重点,但很高兴有:
我想在细节中添加一个渐变效果,这就是为什么它使用paddingmax-heightopacity而不是仅仅将其设置为display: none。如果选择了一个项目,而我选择了同一行中的另一个项目,则在过渡期间会显示两个细节容器,这会导致网格布局出现奇怪的变化。
JS解决方案就可以了。它不需要是纯CSS。

7kqas0il

7kqas0il1#

我不太明白你的目的...
这个片段给你提供了最少的布局,让我知道,如果这不是一个好的…

#mainContainer{
        display:grid;
        width: 840px;
        justify-content: center;
        align-content: center;
        margin-left:auto;
        margin-right:auto;
        padding:10px;
    }
    #container{
        display:grid;
        width:800px;
        gap: 10px;
        grid-template-columns: repeat(5,20%);
        grid-template-rows: repeat(3,50px);
        font-family: sans-serif;
        justify-content: center;
        align-content: center;
    }
    #cell_0_0_1{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 1;
        grid-column-end: 2;
    }
    #cell_0_0_2{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 2;
        grid-column-end: 3;
    }
    #cell_0_0_3{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 3;
        grid-column-end: 4;
    }
    #cell_0_0_4{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 4;
        grid-column-end: 5;
    }
    #cell_0_0_5{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 5;
        grid-column-end: 6;
    }
    #cell_1_0_6{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 1;
        grid-column-end: 6;
    }
    #cell_2_0_1{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 1;
        grid-column-end: 2;
    }
    #cell_2_0_2{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 2;
        grid-column-end: 3;
    }
    #cell_2_0_3{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 3;
        grid-column-end: 4;
    }
    #cell_2_0_4{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 4;
        grid-column-end: 5;
    }
    #cell_2_0_5{
        display:grid;
        justify-content: center;
        align-content: center;
        grid-column-start: 5;
        grid-column-end: 6;
    }
    .greenBorder{
        border:4px solid green;
    }
    .redBorder{
        border:4px solid red;
    }
    .blueBorder{
        border:4px solid blue;
    }
<div id="mainContainer" class="redBorder">
        <div id="container" >
            <div id="cell_0_0_1" class="greenBorder">20%</div>
            <div id="cell_0_0_2" class="greenBorder">20%</div>
            <div id="cell_0_0_3" class="greenBorder">20%</div>
            <div id="cell_0_0_4" class="greenBorder">20%</div>
            <div id="cell_0_0_5" class="greenBorder">20%</div>

            <div id="cell_1_0_6" class="blueBorder">100%</div>
            
            <div id="cell_2_0_1"></div>
            <div id="cell_2_0_2" class="greenBorder">20%</div>
            <div id="cell_2_0_3"></div>
            <div id="cell_2_0_4" class="greenBorder">20%</div>
            <div id="cell_2_0_5"></div>
        </div>
    </div>
7z5jn7bk

7z5jn7bk2#

我怎样才能实现这种行为呢?它不需要是网格,也可以是Flex,或者其他任何解决方案
一行上有两个项目的图像类似于具有以下属性设置的弹性框布局:

.grid {
    display: flex;
    justify-content: space-evenly;
    flex-wrap: wrap;
}

根据浏览器视口的宽度,当两个元素单独 Package 到一行时,布局将类似于您希望在布局中实现的效果:

x一个一个一个一个x一个一个二个x
要获得可展开/可折叠的.details元素,请将每个.item元素 Package 在 Package 器中,并在该 Package 器中包含.details元素。

<div class="item-wrapper">
    <div class="item">
        <div class="item-content">Item 6 parent element</div>
    </div>
    <div class="details">
        <div class="details-content">
            <p>
                Item 2 Details has a lot more content
                than other elements of similar type.
            </p>
            <p>
                The content shows the display of the
                details content div is not a fixed height.
            </p>
        </div>
    </div>
</div>

.details元素的position被设置为absolute。它的位置将相对于网格元素而不是其父元素.item-wrapper,以便在网格的宽度上拉伸.details

.grid-flex-layout {
    position: relative;
    display: flex;
    justify-content: space-evenly;
    flex-wrap: wrap;
    border: var(--border-size) solid red;
    padding: 1rem;
}

.item-wrapper .details {
    position: absolute;
    top: auto;
    left: 0;
    width: 100%;
    height: 0;
    opacity: 0;
    overflow: hidden;
    display: grid;
    align-items: start;
}

下面引用的.max-height技术(或.details高度的手动设置)可用于展开/折叠.details元素。
我想为细节添加一个渐变效果,这就是为什么它使用填充,最大高度和不透明度,而不仅仅是设置它来显示:none。如果选中一个项目,而我在同一行中选择了另一个项目,则在过渡期间会显示两个细节容器,这会导致网格布局出现奇怪的变化。JS解决方案也可以。它不需要是纯CSS。
我已经使用基于堆栈溢出postmax-height来展开/折叠我的项目。它作为一个纯CSS的解决方案工作得很好。正如你所说的,它在一个响应式网格布局中工作得不好,导致“奇怪的变化”。
为了获得更平滑的展开/折叠过渡,下面的代码片段 * 手动 * 计算.details元素中内容的height,当内容高度不相同时。max-height仅限CSS的解决方案适用于.details内容,该内容与CSS样式规则中设置的值相同。
这个代码片段使用了.scrollHeight,但是和其他返回元素维度的属性一样,它将使用force layout/reflow
CSS animation“让浏览器优化性能和效率”,而不是代码片段中使用的多个CSS转换。
该解决方案依赖于CSS变量来共享JavaScript和CSS样式规则之间的值--这些值会影响内容高度。

document.addEventListener("DOMContentLoaded", init);

let gridEl;
let compStyles;
let borderSize = 0,
  detailsPadding = 0,
  padding = 0;

function init() {
  gridEl = document.querySelector(".grid");

  gridEl.querySelectorAll(".grid .item")
    .forEach(el => {
      el.addEventListener("click", itemClick);
    });

  compStyles = window.getComputedStyle(gridEl);

  // Set 'borderSize' variable that represents the
  // size of the border set for the '.details-content'
  // element.
  borderSize = getCssVariable('--border-size');

  detailsPadding = getCssVariable('--details-padding');
  padding = getCssVariable('--top-bottom-padding');
}

function getCssVariable(cssVar) {
  let value = compStyles.getPropertyValue(cssVar);
  if (value.indexOf("rem") > -1) {
    value = convertRemToPixels(value);
  } else if (value.indexOf("px") > -1) {
    value = parseInt(value, 10);
  }
  return value;
}

function itemClick(e) {
  // The '.details' element for the clicked '.item'
  // target should be the next element.
  const detailsEl = e.currentTarget.nextElementSibling;

  if (!detailsEl.classList.contains("details")) {
    console.error("The next element is not a its '.details' sibling.");
    return;
  }

  if (detailsEl.classList.contains("selected")) {
    removeSelected(detailsEl);
  } else {
    // Hide current displayed '.details' element
    const selEl = gridEl.querySelector(".details.selected");
    if (selEl) {
      removeSelected(selEl);
    }
    // Show selected '.details' element
    addSelected(detailsEl);
  }
}

// https://stackoverflow.com/a/42769683
// 'rem' parameter includes unit string (e.g. "2rem")
function convertRemToPixels(rem) {
  return parseFloat(rem, 100) * parseFloat(getComputedStyle(document.documentElement).fontSize);
}

function addSelected(detailsEl) {
  detailsEl.classList.add("selected");

  let height = detailsEl.querySelector(".details-content").scrollHeight;
  height = height + padding + borderSize * 2;

  const styles = {
    height: height + "px",
    opacity: 1,
    padding: detailsPadding + "px"
  };
  Object.assign(detailsEl.style, styles);
}

function removeSelected(detailsEl) {
  detailsEl.classList.remove("selected");

  const styles = {
    height: 0,
    opacity: 0,
    padding: 0
  };
  Object.assign(detailsEl.style, styles);
}
/* Layout */

.grid {
  --details-padding: 0.5rem;
  --top-bottom-padding: 1rem;
  --border-size: 3px;
}

.grid {
  display: grid;
  grid-auto-flow: dense;
  /* https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit
        "To achieve wrapping, we can use the auto-fit or
        auto-fill keywords. These keywords tell the browser 
        to handle the column sizing and element wrapping for
        us so that the elements will wrap into rows when the
        width is not large enough to fit them in without any overflow."
    */
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.item {
  display: grid;
  /*height: 10rem;*/
  /* Fixed height */
  /*height: fit-content;*/
  /* Stretch height to the content within the '.item' element */
  height: auto;
  /* Stretch height to the largest '.item' element in the row */
}

.item-content {
  height: auto;
}

.details {
  grid-column: 1 / -1;
  opacity: 0;
  display: grid;
  overflow: hidden;
  /* https://stackoverflow.com/questions/3508605/how-can-i-transition-height-0-to-height-auto-using-css
        https://medium.com/dailyjs/mimicking-bootstraps-collapse-with-vanilla-javascript-b3bb389040e7 */
  /*max-height: 0;*/
  /*transition: height 0.8s cubic-bezier(0, 1, 0, 1), opacity 0.5s;*/
  height: 0;
  transition: height 0.8s, opacity 0.5s, padding 0.3s;
}

.details.selected {
  /*max-height: 99em;
        transition: max-height 0.8s ease-in-out;*/
  opacity: 1;
}

.details-content {}

/* Styles */

.grid {
  border: var(--border-size) solid red;
  padding: 1rem;
}

.item {
  cursor: pointer;
}

.item-content {
  border: var(--border-size) solid green;
  padding: 1rem;
}

.item,
.details.spacing {
  padding: var(--details-padding);
}

.details-content {
  display: grid;
  box-sizing: border-box;
  padding: var(--top-bottom-padding);
  border: var(--border-size) solid blue;
}

.details-content>p:last-of-type {
  margin-bottom: 0;
}
<div class="grid">
  <div class="item">
    <div class="item-content">Item 1</div>
  </div>
  <div class="details">
    <div class="details-content">Item 1 Details</div>
  </div>
  <div class="item">
    <div class="item-content">Item 2<br />(extra content)</div>
  </div>
  <div class="details">
    <div class="details-content">
      <p>
        Item 2 Details has a lot more content than other elements of similar type.
      </p>
      <p>
        The content shows the display of the details content div is not a fixed height.
      </p>
    </div>
  </div>
  <div class="item">
    <div class="item-content">Item 3</div>
  </div>
  <div class="details">
    <div class="details-content">Item 3 Details</div>
  </div>
  <div class="item">
    <div class="item-content">Item 4</div>
  </div>
  <div class="details">
    <div class="details-content">Item 4 Details</div>
  </div>
  <div class="item">
    <div class="item-content">Item 5</div>
  </div>
  <div class="details">
    <div class="details-content">Item 5 Details</div>
  </div>
  <div class="item">
    <div class="item-content">Item 6</div>
  </div>
  <div class="details">
    <div class="details-content">
      <p>Item 6 Details</p>
    </div>
  </div>
  <div class="item">
    <div class="item-content">
      <p>Item 7<br />has more content<br />than other '.item' elements.</p>
    </div>
  </div>
  <div class="details">
    <div class="details-content">
      <p>Item 7 Details is also<br />a little longer than the previous elements.</p>
    </div>
  </div>
</div>

相关问题