使用React可以根据屏幕宽度改变UI中的元素行为



我会写一些关于我的项目的信息。

框架:NextJs,低于版本13UI框架:ChakraUI(使用预定义组件,主要是Flex)

想法如下:我已经测试并确定了哪些屏幕大小需要在UI上更改元素行为,并决定编写有助于实现该功能的代码。

我会避免为每个页面/组件编写@media逻辑,因为我已经知道屏幕大小会发生变化。

这是相当模糊的,所以我将添加一些上下文:

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;

正如您所看到的,基本上我已经决定,对于一些断点宽度,将返回一个数字。使用它,我可以影响,例如,在屏幕上一行显示多少个项目,或者一个元素的宽度应该是多少等等。

显示一行中应包含多少项目的用法示例:

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

或者这里有一个影响物品宽度的例子:

width={displayItemsCount<=2?'90%':displayItemsCount<=4?'70%':'%0%'}

注意:为了避免混淆,displayItemsCount是上面代码块返回的值。

免责声明:由于像我想象的那样做的事情让我做了很多测试、研究,并给我带来了问题,我也愿意接受以下类型的建议:"这种想法是错误的,更好的解决方案是&">

我知道这里有十亿个关于这个问题的答案,但我认为它们并不能涵盖我在这里需要的一切。一些代码也来自我的SO研究。

我将在这里写一些基础知识,以便清楚地了解当前使用的内容

首先,我想要一个去抖动的礼物,这样代码就不会在每次像素变化时被调用。这主要适用于用户在使用"检查元素"工具时调整屏幕大小的情况。

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在调整屏幕大小后经过一定时间后调用代码,而determineDisplayItemsCount则返回基于当前屏幕大小的值。

忽略确定DisplayItemsCount的命名,这是一些旧用例的残余,因此它将被更改。

我想要的第二件事是,这个代码存在于一个钩子中,这样就很容易使用。

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;
}

如您所见,在useEffect中,只要屏幕宽度发生变化,就会调用handleResize,但在经过额外的250毫秒之后。我还补充道,如果新的count与当前的displayItemsCount相同,则状态不会改变。

结果是,我现在可以进入任何页面/组件,并执行以下操作:const displayItemsCount = useDisplayItemsCount();。然后用这个值来满足我的需要。

问题

  • 第一期

当调用useDisplayItemsCount时,displayItemsCount的初始值将基于determineDisplayItemsCount[/strong>函数设置。这导致水合错误,因为我必须使用窗口,并且组件尚未安装。我的想法是,如果determineDisplayItemsCount中有以下行:if (typeof window === "undefined") return 6;,我会避免水合错误,但不,它仍然存在。

  • 第二期

所以我决定,见鬼,我们就这样吧,初始值是6:const [displayItemsCount, setDisplayItemsCount] = useState<number>(6);,但这会导致从手机(即较小的屏幕)加载页面时,useDisplayItemsCount在开始时返回6。只有在用户在页面上滚动一点或与某些内容交互后,才会调用钩子内的useEffect

我想我的问题是,如何使此代码正确工作,而不会导致水合错误

如前所述,一个很好的答案也是一种替代方法。

注意:也许值得一提的是,使用网格并不能真正解决我的问题,因为我想在任何情况下都避免编写@media。

使用CSS网格,实际上不需要一个媒体查询就可以创建完全响应的网格列。使用minmax函数,使用auto-fitauto-fill,可以设置网格项的最小和最大大小。列数将根据可用空间自动调整以适应或填充。

运行下面的代码片段,然后使用全屏链接来了解我的意思。

/*
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>

感谢@JohnLi和@jme11提供的信息。你的答案组合在一起对我来说是个解决方案。

我现在使用Chakra断点来处理组件的样式,同时使用网格来显示位置。

这真的很有帮助。如果可以的话,我会接受这两个答案!

最新更新