You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Flexbox与JavaScript的Etch-a-Sketch网格容器尺寸问题

Etch-a-Sketch 项目问题与解决方案

我正在完成The Odin Project的Etch-a-Sketch项目,希望实现超出作业要求的功能,且仅使用Flexbox和JavaScript(不使用CSS Grid)。遇到以下两个问题:

  1. 当设置gridContainer.style.height = "100vh"时,容器高度略超出屏幕范围,目前使用97vh替代,是否存在更优解决方案?
  2. 希望仅在移动端屏幕下将容器设置为width=100vw(替换原97vh的高度设置),但尝试CSS媒体查询和JavaScript媒体查询后,容器高度仍大于宽度,无法保持正方形形态,请问该如何实现?

相关代码

HTML代码

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="./styles.css" />
    <script src="./script.js" defer></script>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Etch-a-Sketch</title>
</head>

<body>

<div id="gridUser">

    <h1>Etch-a-Sketch</h1>

    <form id="gridForm">

        <p><label for="gridInput">Enter a number of squares per side: 1...100</label></p>
        <p><input id="gridInput" type="text"></p>

        <p><legend>Choose mouse hover color</legend></p>
        <p>

            <input id="gridRadioYellow" name="gridRadio" value ="yellow" type="radio" checked>
            <label for="gridRadioYellow"> Yellow</label>

            <input id="gridRadioRandomize" name="gridRadio" value ="randomize" type="radio">
            <label for="gridRadioRandomize"> Randomize</label>

            <input id="gridRadioRandomizeEverySquare" name="gridRadio" value ="randomizeEverySquare" type="radio">
            <label for="gridRadioRandomizeEverySquare"> Randomize every square</label>

        </p>

        <p><button type="submit">Submit</button></p>

    </form>

    <div id="gridText"></div><br/>

</div>

<div id="gridContainer"></div>

</body>
</html>

CSS代码

* {
    box-sizing: border-box;
    font-family: Arial, sans-serif;
    text-align: center;
}

body {
    display: flex;
    flex-flow: row wrap;
    justify-content: flex-start;
}

#gridUser, #gridContainer {
    flex: 1 1 0%;
}

#gridInput {
    text-align: left;
}

JavaScript代码

// also add functional for yellow color (not randomize) and for randomize every square

let gridContainer = document.querySelector("#gridContainer");
let gridSize = 16*16;
let gridSquares = "";
let mouseSquares = [];
let mouseHoveringColor = "";

getGridPerSide();

function getGridPerSide() {

    let gridForm = document.querySelector("#gridForm");

    gridForm.addEventListener("submit", (e) => {
        e.preventDefault();

        let gridInput = document.querySelector("#gridInput");
        let gridRadio = document.querySelectorAll(`input[name="gridRadio"]`);
        let gridRadioChecked = "";
        let gridText = document.querySelector("#gridText");

        mouseHoveringColor = "";

        for (const gridRadioOne of gridRadio) {
            if (gridRadioOne.checked) {
                gridRadioChecked = gridRadioOne.value;
                break;
            } 
        }

        if (isNaN(gridInput.value) || gridInput.value < 1 || gridInput.value > 100) {
            gridText.innerHTML = `Enter a <em>right</em> number <strong>from 1 to 100</strong>`;
        } else if (gridRadioChecked === "yellow") {
            gridText.innerHTML = `
                The grid has <em>${gridInput.value}*${gridInput.value}</em> squares<br/>
                The mouse hover color is <strong>yellow</strong><br/>
            `;
            mouseHoveringColor = "yellow"; // value for doMouseHovering()
        } else if (gridRadioChecked === "randomize") {
            gridText.innerHTML = `
                The grid has <em>${gridInput.value}*${gridInput.value}</em> squares<br/>
                The mouse hover color is <strong>randomize</strong><br/>
            `;
            mouseHoveringColor = "randomize"; // value for doMouseHovering()
        } else {
            gridText.innerHTML = `
                The grid has <em>${gridInput.value}*${gridInput.value}</em> squares<br/>
                The mouse hover color is <strong>randomize every square</strong><br/>
            `;
            mouseHoveringColor = "randomizeEverySquare"; // value for doMouseHovering()
        }

        gridSize = gridInput.value * gridInput.value;

        createGrid();

    });
}

function createGrid() {

    gridContainer.innerHTML = ""; // grid clean for new submit

    for (let i=0; i < (gridSize); i++) {

        gridSquares = document.createElement("div");
        gridSquares.classList.add("gridSquares");
        gridContainer.appendChild(gridSquares);

        gridSquares.style.cssText = `
            width: calc(100% / ${gridInput.value});
            height: calc(100% / ${gridInput.value});
            background-color: #eee;
            border: 1px solid #ccc;
        `;

        gridContainer.style.cssText = `
            height: 97vh;
            width: auto;
            display: flex;
            flex-wrap: wrap;
            aspect-ratio: 1/1;
        `;

        mouseSquares.push(gridSquares); // create array for doMouseHovering()

    }

    doMouseHovering();

}

function doMouseHovering() {

// need mouseBackgroundColor for calculate "randomize" (NOT for "randomizeEverySquare")

    let mouseBackgroundColor = "";

    if (mouseHoveringColor === "yellow") {
        mouseBackgroundColor = "yellow";
    } else if (mouseHoveringColor === "randomize") {
        let r = Math.floor(Math.random() * 256);
        let g = Math.floor(Math.random() * 256);
        let b = Math.floor(Math.random() * 256);
        mouseBackgroundColor = `rgb(${r}, ${g}, ${b})`;
    }

    mouseSquares.forEach(gridSquares => {

        gridSquares.addEventListener('mouseover', () => {
            if (mouseHoveringColor === "yellow") {
                gridSquares.style.backgroundColor = mouseBackgroundColor;   
            } else if (mouseHoveringColor === "randomize") {
                gridSquares.style.backgroundColor = mouseBackgroundColor;
            } else {
                let r = Math.floor(Math.random() * 256);
                let g = Math.floor(Math.random() * 256);
                let b = Math.floor(Math.random() * 256);
                gridSquares.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
            }

        });

    });

}

问题1:容器高度超出屏幕的最优解决

问题根源是浏览器默认给body设置了8px左右的margin,导致100vh的容器加上margin后总高度超过视口。

解决步骤

  1. 重置body的默认边距,在CSS中修改:
body {
    display: flex;
    flex-flow: row wrap;
    justify-content: flex-start;
    margin: 0; /* 新增:清除默认margin */
    padding: 0; /* 新增:清除默认padding */
}
  1. 将容器的基础样式移到CSS中,避免JS重复设置:
#gridContainer {
    display: flex;
    flex-wrap: wrap;
    aspect-ratio: 1/1; /* 保持正方形 */
    height: 100vh;
    width: auto;
    max-height: 100vh; /* 防止溢出 */
    overflow: hidden; /* 隐藏超出内容 */
}
  1. 简化JS中createGrid函数里的容器样式设置,仅保留动态需要的部分(如果有的话),移除原有的gridContainer.style.cssText整段代码。

问题2:移动端保持正方形容器

核心是用aspect-ratio: 1/1配合媒体查询,让容器在移动端以宽度为基准,桌面端以高度为基准,自动维持正方形比例。

解决步骤

  1. 在CSS中添加媒体查询,区分移动端和桌面端:
/* 移动端(屏幕宽度≤768px) */
@media (max-width: 768px) {
    #gridContainer {
        width: 100vw;
        height: auto;
        max-width: 100vw;
    }
}

/* 桌面端(屏幕宽度>768px) */
@media (min-width: 769px) {
    #gridContainer {
        height: 100vh;
        width: auto;
        max-height: 100vh;
    }
}
  1. 给body添加横向溢出隐藏,避免移动端出现横向滚动条:
body {
    /* 原有样式 */
    overflow-x: hidden; /* 新增 */
}
  1. 移除JS中硬编码的容器宽高设置,让CSS媒体查询主导响应式变化,JS仅负责生成网格方块的尺寸。

额外优化建议

  1. 将网格方块的固定样式移到CSS类中,减少JS DOM操作:
.gridSquares {
    background-color: #eee;
    border: 1px solid #ccc;
}

然后在JS的createGrid函数中,仅动态设置宽高:

gridSquares.style.width = `calc(100% / ${gridInput.value})`;
gridSquares.style.height = `calc(100% / ${gridInput.value})`;
  1. 每次创建新网格时清空mouseSquares数组,避免重复绑定事件:
function createGrid() {
    gridContainer.innerHTML = "";
    mouseSquares = []; // 新增:清空数组
    // 后续代码...
}

内容的提问来源于stack exchange,提问作者OlKlym

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 11:07:32