简体   繁体   English

根据屏幕宽度在 UI 中使用 React 更改元素行为

[英]Using React change element behavior in UI based on screen width

I'll write some information about my project.我会写一些关于我的项目的信息。

Framework: NextJs, below version 13 UI Framework: ChakraUI (Using predefined components, mostly Flex)框架: NextJs,低于版本 13 UI 框架: ChakraUI(使用预定义组件,主要是 Flex)

The idea is as following : I have tested and determined what screen sizes should require element behavior changes on the UI and decided to write code that will facilitate that functionality.想法如下:我已经测试并确定了哪些屏幕尺寸应该需要 UI 上的元素行为更改,并决定编写代码来促进该功能。

Doing things like, I'll avoid writing @media logic for every page/component, since I already know at which screen sizes changes will happen.做这样的事情,我将避免为每个页面/组件编写 @media 逻辑,因为我已经知道屏幕尺寸会发生变化。

This is pretty vague, so I'll add some context:这很含糊,所以我将添加一些上下文:

    if (typeof window === "undefined") return 6;
    const windowWidth: number = window.innerWidth;

    if (windowWidth <= 600) return 2;
    else if (windowWidth <= 900) return 3;
    else if (windowWidth <= 1200) return 4;
    else if (windowWidth <= 1400) return 5;

    return 6;

As you can see, basically I've decided that for some breakpoint widths, a number will be returned.如您所见,基本上我已经决定对于某些断点宽度,将返回一个数字。 Using this I can influence, for instance, how many items are displayed, in a row, on the screen, or what the width of an element should be etc.使用它我可以影响,例如,在屏幕上一行显示多少项目,或者元素的宽度应该是多少等。

Example of usage for displaying how many items should be in a line:显示一行中应包含多少项的用法示例:

width={ ${Math.floor(100 / displayItemsCount)}% } width={ ${Math.floor(100 / displayItemsCount)}% }

Or here is an example of influencing the width of an item:或者这里是一个影响项目宽度的例子:

width={displayItemsCount <= 2?宽度={displayItemsCount <= 2? '90%': displayItemsCount <= 4? '90%':displayItemsCount <= 4? '70%': '50%'} '70%': '50%'}

Note : To avoid confusion, displayItemsCount is the value that the above block of code returns.注意:为避免混淆, displayItemsCount是上述代码块返回的值。

Disclaimer : Since doing things like I imagined has caused me to do a lot of testing, research and has caused me issues, I'm also open for suggestions that are of the type: "This thinking is wrong, a better solution would be...".免责声明:由于像我想象的那样做事情导致我做了很多测试、研究并给我带来了问题,所以我也愿意接受以下类型的建议: “这种想法是错误的,更好的解决方案是。 ..”。

I know there a billion answers here regarding this issue, but I think they don't cover everything that I need here.我知道这里有十亿个关于这个问题的答案,但我认为它们并没有涵盖我在这里需要的一切。 Some of the code is from my SO research as well.一些代码也来自我的 SO 研究。

I'll write here some groundwork so that it's clear what's currently being used.我将在这里写一些基础工作,以便清楚当前使用的是什么。

Firstly, I wanted a debounce present, so that the code doesn't get called on every pixel change.首先,我想要一个 debounce present,这样代码就不会在每次像素变化时都被调用。 This is mostly for when the user is resizing the screen while using the Inspect Element tool.这主要用于用户在使用检查元素工具调整屏幕大小时。

function debounce(fn: Function, ms: number) {
    let timeoutId: ReturnType<typeof setTimeout>
    return function (this: any, ...args: any[]) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn.apply(this, args), ms);
    }
};

function determineDisplayItemsCount() {
    if (typeof window === "undefined") return 6;
    const windowWidth: number = window.innerWidth;

    if (windowWidth <= 600) return 2;
    else if (windowWidth <= 900) return 3;
    else if (windowWidth <= 1200) return 4;
    else if (windowWidth <= 1400) return 5;

    return 6;
};

Debounce calls the code whenever some amount time has passed after the screen was resized, while determineDisplayItemsCount returns a value based on the current screen size. Debounce会在调整屏幕大小后经过一段时间后调用代码,而determineDisplayItemsCount会根据当前屏幕大小返回一个值。

Disregard the naming of the determineDisplayItemsCount , this is a remnant of some old use-case, so it will be changed.忽略determineDisplayItemsCount的命名,这是一些旧用例的残余,因此将对其进行更改。

The second thing that I wanted, is that this code is present in a hook, so that it is easy to use.我想要的第二件事是,这段代码出现在一个钩子中,这样就很容易使用。

function useDisplayItemsCount() {

    const [displayItemsCount, setDisplayItemsCount] = useState<number>(determineDisplayItemsCount());

    useEffect(() => {
        const debounceHandleResize = debounce(
            function handleResize() {
                let count: number = determineDisplayItemsCount();
                if (count != displayItemsCount)
                    setDisplayItemsCount(count);
            }, 250);
        window.addEventListener('resize', debounceHandleResize)

        return () => {
            window.removeEventListener('resize', debounceHandleResize)
        }
    }, []);
    
    return displayItemsCount;
}

As you can see, in the useEffect the handleResize gets called whenever the screen changes width, but after an additional 250ms passes by.如您所见,在useEffect中,只要屏幕改变宽度,就会调用handleResize ,但会在额外的 250 毫秒过去之后。 I also added if the new count is the same as the current displayItemsCount , then the state won't change.我还添加了如果新计数与当前displayItemsCount相同,则 state 不会改变。

The result is that I can go into any page/component now and go something like: const displayItemsCount = useDisplayItemsCount();结果是我现在可以 go 进入任何页面/组件和 go 类似: const displayItemsCount = useDisplayItemsCount(); . . Then use that value for whatever I need.然后将该值用于我需要的任何东西。

Issues问题

  • First issue首要问题

When useDisplayItemsCount gets called, the initial value of displayItemsCount will be set based on the determineDisplayItemsCount function. This causes a hydration error , since I have to use window and the component wasn't mounted yet.调用 useDisplayItemsCount时, displayItemsCount的初始值将根据determineDisplayItemsCount function 设置。这会导致水合作用错误,因为我必须使用window并且组件尚未安装。 My thinking was that if I had the following line in determineDisplayItemsCount : if (typeof window === "undefined") return 6;我的想法是,如果我在determineDisplayItemsCount中有以下行: if (typeof window === "undefined") return 6; , I'd avoid the hydration error , but nope it's still there. ,我会避免水合作用错误,但不,它仍然存在。

  • Second issue第二期

So I decided heck, let's just have it so the initial value is 6: const [displayItemsCount, setDisplayItemsCount] = useState<number>(6);所以我决定见鬼,让我们让它的初始值为 6: const [displayItemsCount, setDisplayItemsCount] = useState<number>(6); , but that results in the fact that when the page is loaded from a phone, aka smaller screen, useDisplayItemsCount returns 6 at the start. ,但这会导致当页面从手机(即较小的屏幕)加载时, useDisplayItemsCount在开始时返回 6。 The useEffect inside the hook gets called only after the user scrolls a little bit on the page, or interacts with something.钩子中的useEffect仅在用户在页面上滚动一点或与某些东西交互后才会被调用。

I guess my question is, how to make this code work correctly, without causing a hydration error?我想我的问题是,如何使这段代码正常工作,而不会导致水合作用错误?

As stated, a great answer would also be an alternative approach.如前所述,一个很好的答案也是一种替代方法。

Note : Probably worth stating that using Grid doesn't really solve my problems, since I want to avoid writing @media in all cases.注意:可能值得一提的是,使用 Grid 并不能真正解决我的问题,因为我想避免在所有情况下都写 @media 。

With CSS grid, you actually don't need to have a single media query to create grid columns that are fully responsive.使用 CSS 网格,您实际上不需要使用单个媒体查询来创建完全响应的网格列。 Using the minmax function, with auto-fit or auto-fill you can set a minimum and maximum size for your grid items.使用minmax function,通过auto-fitauto-fill ,您可以为网格项目设置最小和最大尺寸。 The number of columns will automatically adjust to fit or fill according to the space available.列数将根据可用空间自动调整以适应或填充。

Run the code snippet below and then use the full screen link to see what I mean.运行下面的代码片段,然后使用全屏链接看看我的意思。

 /* With CSS Grid, you can create a fully responsive grid in two declarations on the parent. */.container { display: grid; grid-gap: .75rem; grid-template-columns: repeat(auto-fit, minmax(175px, 1fr)); }.item { aspect-ratio: 1; background-color: #ccc; border: 1px solid; display: grid; font: bold 2rem sans-serif; place-content: center; width: 100%; }
 <div class="container"> <div class="item">1</div> <div class="item">2</div> <div class="item">3</div> <div class="item">4</div> <div class="item">5</div> <div class="item">6</div> <div class="item">7</div> <div class="item">8</div> <div class="item">9</div> </div>

Thank you @JohnLi and @jme11 for the information.感谢@JohnLi 和@jme11 提供的信息。 A combination of your answers was the solution for me.您的答案组合是我的解决方案。

I'm now using Chakra breakpoints to handle the styling on the components, while grid is being used to display the location.我现在使用 Chakra 断点来处理组件的样式,同时使用网格来显示位置。

This was really helpful.这真的很有帮助。 If I could I'd accept both answers!如果可以的话,我会接受这两个答案!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM