简体   繁体   中英

How to add an outline to a code-generated, oddly-shaped group of buttons in React JS

I am working on a react project, and have a grid of buttons. Shown here .

When a group of them is pressed, I want them all to be outlined. Sort of Like this.

The big issue that I'm facing is that the size of the grid can change, so the buttons aren't hard coded. They're generated with code. And every solution I've found online so far is for hard-coded examples. I am very new to both front-end development and React, so I'm not sure how to go about doing this!

Here's the functional (as in minimal viable product) css I'm using:

/* So the size of the grid can easily be changed. */
/* The amount of buttons required is the grid size squared */
:root {
  --grid-size: 3;
}

/* The container for the squares */
.keenboard {
  display: grid;
  grid-template-columns: repeat(var(--grid-size), 1fr);
}

/* The buttons themselves */
.keensquare {
  width: 60px;
  height: 60px;
  border: 1px solid rgb(153, 153, 153);
  background-color: rgb(255,255,255);
}

and here's the functional React JS code:

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";

class KeenSquare extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      toggled: false,
    };
  }

  handleClick = (event) => {
    this.setState({ toggled: !this.state.toggled });
  };

  render() {
    return <button className="keensquare" onClick={this.handleClick}></button>;
  }
}

class KeenBoard extends React.Component {
  render() {
    document.documentElement.style.setProperty("--grid-size", this.props.size);

    // Creates an array, and then run a mapping function on it that sets every item in that array to be its own index
    const board = Array.from(
      { length: this.props.size ** 2 },
      (_, index) => index
    );

    return (
      <div className="keenboard">
        {board.map((i) => (
          <KeenSquare key={i}></KeenSquare>
        ))}
      </div>
    );
  }
}

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="gameboard" id="gameboard">
          <KeenBoard size={this.props.size} />
        </div>
      </div>
    );
  }
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Game size={9}></Game>);

Thanks in advance to anyone willing to help!

If I understand your question correctly, whichever button is pressed, you want to put a border around all neighbouring buttons?

This is not a complete answer because I don't fully understand your code (I'm a beginner too,), but I'll cover off as much as I can from a logic stand-point, hope it helps!

I don't fully understand what's happening with your id generator, but it would be useful if your buttons IDs started from 1 (not 0). That means if you have a 3x3 board, you know that your IDs 3, 6 and 9 are on the right side etc.

With that knowledge, you can determine whether you're looking for another button above/to the right etc, and grab that button.

let above = buttonID <= this.props.size ? null : document.getElementById(buttonID - this.props.size); //if button is on top row, set "above" to null
let left = buttonID % this.props.size === 1 ? null : document.getElementById(buttonID - 1); // if button is on left
let right = buttonID % this.props.size === 0 ? null : document.getElementById(buttonID + 1); //if button is on right
let below = buttonID > this.props.size * this.props.size - this.props.size ? null : document.getElementById(buttonID + this.props.size); //if button is on bottom

Now that you've got the (up to) 4 surrounding buttons contained in variables, you can then create new 4 new CSS rules, and assign class names to those 4 variables.

eg:

above.className = "above";

And your four CSS rules would be something like:

.above {
box-sizing: border-box; /* Otherwise the thicker border will change the overall size */
border.left: solid 3px red;
border.top: solid 3px red;
border.right: solid 3px red;
}

I hope that's enough of an answer to get you there!

Sorry, not sure if I should be adding another answer or editing the previous one as it's a very different solution. I'm very new to React so I put this together in just vanilla JavaScript, however you should still be able to copy just the JavaScript and CSS side of it.

HTML:

<div id="parent"></div>

CSS:

        #parent{
            display: flex;
            flex-wrap: wrap;
            width: 200px;
            height: 200px;
            border: solid 1px black;
        }
        div div{
            box-sizing: border-box;
            display: inline-block;
            width: 40px;
            height: 40px;
            border: solid 1px black;
            margin: 0px;
        } 
        .on{
            background-color: grey;
        }
        .off{
            background-color: white;
        }

JavaScript:

        let gridSize = 5;
        const parent = document.getElementById("parent");
        parent.style.width = gridSize * 40 + "px";
        parent.style.height = gridSize * 40 + "px";
        for (let i = 0; i < gridSize * gridSize; i++) {
            const div = document.createElement("div")
            div.id = i+1;
            div.className = "off";
            parent.appendChild(div);
            div.addEventListener('click', change);
        }
        function change(e){
            const target = e.target;
            const above = e.target.id <= gridSize ? null : document.getElementById(e.target.id - gridSize); //if button is on top row, set "above" to null
            const left = e.target.id % gridSize === 1 ? null : document.getElementById(e.target.id - 1); // if button is on left
            const right = e.target.id % gridSize === 0 ? null : document.getElementById(parseInt(e.target.id, 10) + 1); //if button is on right
            const below = e.target.id > gridSize * gridSize - gridSize ? null : document.getElementById(parseInt(e.target.id, 10) + gridSize); //if button is on bottom

            if(e.target.className ==="off"){
                e.target.className = "on";
                if(above && above.className === "on"){
                    above.style.borderBottom = "solid 1px lightgrey";
                    e.target.style.borderTop = "solid 1px lightgrey";
                }
                else{
                    e.target.style.borderTop = "solid 3px red";
                }

                if(left && left.className === "on"){
                    left.style.borderRight = "solid 1px lightgrey";
                    e.target.style.borderLeft = "solid 1px lightgrey";
                }
                else{
                    e.target.style.borderLeft = "solid 3px red";
                }

                if(right && right.className === "on"){
                    right.style.borderLeft = "solid 1px lightgrey";
                    e.target.style.borderRight = "solid 1px lightgrey";
                }
                else{
                    e.target.style.borderRight = "solid 3px red";
                }

                if(below && below.className === "on"){
                    below.style.borderTop = "solid 1px lightgrey";
                    e.target.style.borderBottom = "solid 1px lightgrey";
                }
                else{
                    e.target.style.borderBottom = "solid 3px red";
                }
            }
            else{
                e.target.className = "off";
                e.target.style.border = "solid 1px black"
                if(above && above.className === "on"){
                    above.style.borderBottom = "solid 3px red";
                }
                if(left && left.className === "on"){
                    left.style.borderRight = "solid 3px red";
                }
                if(right && right.className === "on"){
                    right.style.borderLeft = "solid 3px red";
                }               
                if(below && below.className === "on"){
                    below.style.borderTop = "solid 3px red";
                }
            }        
        }

I'm sure there's a way to shrink that JavaScript down and make it more readable, but it works

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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