CSS 书籍布局(水平手风琴)

4

我试图创建一个书籍布局的页面,即一个带有一些选项卡的页面,用户可以逐个展开。

这里是一个工作示例:https://codesandbox.io/s/book-layout-l28gh?file=/src/App.js:0-1419

import { useState } from "react";

const dataset = [
  { name: "A section", description: "page A" },
  { name: "B section", description: "page B" },
  { name: "C section with long title", description: "page C" },
  { name: "D section", description: "page D" }
];

export default function App() {
  return <Page />;
}

function Page({}) {
  const [openSection, setOpenSection] = useState(0);

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh"
      }}
    >
      {dataset.map((datum, i) => {
        const { name } = datum;
        const isOpen = i === openSection;

        return (
          <div
            key={name}
            style={{
              height: "100%",
              backgroundColor: isOpen ? "white" : "lightgray",
              border: `1px solid ${isOpen ? "white" : "black"}`,
              padding: 10,
              flex: 1,
              flexGrow: isOpen ? 1 : 0,
              transition: "all 2s ease"
            }}
          >
            <div
              style={{
                cursor: "pointer",
                writingMode: isOpen ? "horizontal-tb" : "vertical-rl",
                transition: "all 2s ease"
              }}
              onClick={() => setOpenSection(i)}
            >
              {name}
            </div>
          </div>
        );
      })}
    </div>
  );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

如果你进行测试,你会发现一些问题:

  1. 当你展开一个部分时,标题不会平滑地跟随它过渡从垂直变为水平。应该是平滑旋转的。
  2. 有时候,我不确定具体什么时候,当你点击标题时,所有卡片似乎都靠得更近了。
  3. 另一个要求是使灰色区域全部可点击,但当它打开时显然存在问题。

为什么?出了什么问题?是否有更好的方法来设计这样的布局呢?

4个回答

5

我按照你说的解决了所有问题。

问题 #1 -> 已解决: 你应该使用white-space:nowrap, 如果你的文本很长,那么在盒子的末尾会换行到下一行。但是 white-space:nowrap 不会让它换行。(我不建议这样做,因为对于太长的标题文本来说并不好)

问题 #2 -> 已解决: 当你点击某个项目并在操作(打开盒子)中间时,再点击另一个项目时就会出现此问题。这是由于display:flexflexGrow:1引起的。

你使用了弹性布局和justifyContent:"center"。因此,当你点击并且在点击另一个项目时,你的包装器变得更小,所有卡片似乎靠得更近了。flexGrow: 1会将它们分开。 所以flexGrow:5 是解决办法。

问题 #3 -> 已解决: 设置onClick的位置不正确。你应该在盒子上设置onClick事件,而不是在文本上设置。光标的条件是你想要的。(项目已被选中,因此光标必须是default,否则必须是poiner)。

奖励 :) 如果你给你的包装器设置一个小的宽度,那么你的旋转框会更漂亮。 它在文本开头旋转,很棒。

import { useState } from "react";

const dataset = [
  { name: "A section" },
  { name: "B section" },
  { name: "C section with long title" },
  { name: "D section" },
  { name: "E section" },
];

function Page() {
  const [openSection, setOpenSection] = useState(1);

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh",
      }}
    >
      {dataset.map((datum, i) => {
        const { name } = datum;
        const isOpen = i === openSection;
        return (
          <div
            key={name}
            onClick={() => setOpenSection(i)}
            style={{
              height: "100%",
              backgroundColor: isOpen ? "white" : "lightgray",
              border: `1px solid ${isOpen ? "white" : "black"}`,
              padding: "20px 30px",
              flex: 1,
              flexGrow: isOpen ? 5 : 0,
              transition: "all 1s linear",
              boxSizing: "border-box",
              cursor: openSection !== i ? 'pointer' : 'default',

              "&:first-child": {
                left: 0,
              },

              "&:last-child": {
                right: 0,
              },
            }}
          >
            <div
              style={{
                transform: `rotate(${isOpen ? "0" : "90"}deg)`,
                transition: "all 1s linear",
                width: 1,
                whiteSpace: "nowrap",
              }}
            >
              {name}
            </div>
          </div>
        );
      })}
    </div>
  );
}

export default Page;

1
  1. 平滑过渡: 添加 white-space:nowrap 以防止文本在缩小大小时收缩。将标题div的位置设置为绝对定位,以便其宽度不会影响布局和过渡。
  2. 点击错误: 无法重现。有时候codesandbox不能正确更新。您需要刷新输出页面并重新测试。如果在我的更新代码中也发生了这种情况,请检查。
  3. 使灰色区域可点击: 将鼠标指针和单击处理程序移动到具有isOpen条件的父级。

修改后的codesandbox:

import { useState } from "react";

const dataset = [
  { name: "A section" },
  { name: "B section" },
  { name: "C section with long title" },
  { name: "D section" },
  { name: "E section" }
];

export default function App() {
  return <Page />;
}

function Page({}) {
  const [openSection, setOpenSection] = useState(0);

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh"
      }}
    >
      {dataset.map((datum, i) => {
        const { name } = datum;
        const isOpen = i === openSection;

        return (
          <div
            key={name}
            style={{
              height: "100%",
              backgroundColor: isOpen ? "white" : "lightgray",
              border: `1px solid ${isOpen ? "white" : "black"}`,
              flex: 1,
              flexGrow: isOpen ? 1 : 0,
              transition: "all 2s ease",

              //my changes
              padding: 0,
              flexBasis: "1.2rem",
              cursor: !isOpen ? "pointer" : "auto",
              position: "relative"
            }}
            onClick={!isOpen ? () => setOpenSection(i) : null}
          >
            <div
              style={{
                transition: "all 2s ease",

                //my changes
                transform: `rotate(${isOpen ? "0" : "90"}deg) 
                translateX(${isOpen ? "0" : "50"}%)`,
                whiteSpace: "nowrap",
                width: isOpen ? "100%" : "1rem",
                position: "absolute",
                top: isOpen ? "1rem" : "0",
                left: isOpen ? "1rem" : "0",
                fontWeight: "bold"
              }}
            >
              {name}
            </div>
          </div>
        );
      })}
    </div>
  );
}


0

尝试将以下代码添加到您的程序中,使灰色区域可点击并实现平滑过渡效果

import { useState } from "react";

const dataset = [
  { name: "A section" },
  { name: "B section" },
  { name: "C section with long title" },
  { name: "D section" },
  { name: "E section" }
];

export default function App() {
  return <Page />;
}

function Page({}) {
  const [openSection, setOpenSection] = useState(0);

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh"
      }}
    >
      {dataset.map((datum, i) => {
        const { name } = datum;
        const isOpen = i === openSection;

        return (
          <div
            key={name}
            style={{
              height: "100%",
              backgroundColor: isOpen ? "white" : "lightgray",
              border: `1px solid ${isOpen ? "white" : "black"}`,
              padding: 10,
              flex: 1,
              flexGrow: isOpen ? 1 : 0,
              cursor: "pointer",
              writingMode: isOpen ? "horizontal-tb" : "vertical-rl",
   transition: "all 2s ease"
            }}
          >
            <div
              style={{
               transition: "all 2s ease"
              }}
              onClick={() => setOpenSection(i)}
            >
              {name}
            </div>
          </div>
        );
      })}
    </div>
  );
}

0

我会按照您介绍问题的顺序进行回答。

  1. writing-mode 不能用于标题动画效果。您可以尝试使用transform 属性旋转文本。
  2. 如果您想让整个灰色区域可点击,您应该将onClick 函数和 cursor: "pointer" 属性移动到该元素的父级 div 中,即标题所在的元素。

1
我认为你提出的第二个问题的解决方案行不通,因为在这种情况下,整个区域都可以被点击,而不仅仅是灰色区域。我只想在卡片关闭时可点击卡片区域。 感谢提供1分建议。那主要问题呢?上述奇怪的故障如何解决? - whitecircle
抱歉!我的错。然后,您可以按以下方式使用已声明的条件变量isOpen: cursor: isOpen ? "default" : "pointer", 对于onClick函数: onClick={ isOpen ? ()=>{} : () => setOpenSection(i)} - adabuyaman
我尝试使用 CSS 旋转替换 writing-mode,但结果有点奇怪: // writingMode: isOpen ? "horizontal-tb" : "vertical-rl", transform: rotate(${isOpen ? "0" : "90"}deg)``我更新了 CodeSandbox。 - whitecircle

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