Drawing Chinese Chess Board - Using CSS Pseudo Elements
Compared to chess:
Drawing a chess board is slightly more difficult:
The main reason is that chess moves are in the grid, and chess moves are at intersections. Therefore, the layout of chess can use background-color: #fff
and background-color: #aaa
mark odd and even grids - this is also provided by CSS. nth-child()
This pseudo class can be convenient and efficient to complete the task, but the processing of Chinese chess is relatively more troublesome.
However, after some searching, I found that there is a relatively simple way to draw a chessboard, that is, through before
and after
these two pseudo elements, the final result is as follows:
The blank chessboard is as follows:
The specific implementation is as follows:
-
The parent component manages the checkerboard array
import { useState } from "react"; import Jiang from "../../types/pieces/Jiang"; import Piece from "../piece/Piece"; import styled from "styled-components"; const ChessboardUIWrapper = styled.div` border: 2px solid ${ BASE_BLACK}; margin: 0 auto; padding: 2em; width: calc(${ TILE_SIZE} * ${ NUM_OF_COLS}); position: relative; @media (max-device-width: 1024px) { padding: 0.5em; } @media (max-width: 768px) { padding: 0.8em; width: 80%; } @media (max-device-width: 1024px) and (orientation: landscape) { max-width: calc(${ NUM_OF_COLS} * ${ TILE_SIZE_MOBILE_LANDSCAPE}); } `; const ChessPiecesUI = styled.div` display: grid; grid-template-columns: repeat(${ NUM_OF_COLS}, ${ TILE_SIZE}); @media (max-width: 768px) { grid-template-columns: repeat(${ NUM_OF_COLS}, ${ TILE_SIZE_MOBILE}); } @media (max-device-width: 1024px) and (orientation: landscape) { grid-template-columns: repeat( ${ NUM_OF_COLS}, ${ TILE_SIZE_MOBILE_LANDSCAPE} ); } `; const ChessboardUI = () => { const chessboard = new Chessboard(); // 9 * 10 const [board, setBoard] = useState(Array(90).fill("")); const renderPieces = () => { return ( <ChessPiecesUI> { board.map((piece, idx) => ( <Piece key={ idx} idx={ idx} piece={ piece} /> ))} </ChessPiecesUI> ); }; return <ChessboardUIWrapper>{ renderPieces()}</ChessboardUIWrapper>; };
-
child component render grid
import { FC } from 'react'; import ChessPiece from '../../types/pieces/ChessPiece'; import blankPiece from '../../assets/imgs/blank.svg'; import styled from 'styled-components'; interface TileProps { idx: number; piece?: ChessPiece; } const Piece: FC<TileProps> = ({ idx, piece }) => { let hasBoderBtm = Math.floor(idx / 9) !== 9 && Math.floor(idx / 9) !== 4; // keep borders for river if ((idx % 9 === 0 || idx % 9 === 8) && Math.floor(idx / 9) !== 9) { hasBoderBtm = true; } const hasBorderRight = idx % 9 !== 8; const image = piece ? piece.getImages : blankPiece; const alt = piece ? piece.getTypes.toString() : 'blank piece'; return ( <ChessPieceUI hasBefore={ hasBoderBtm} hasAfter={ hasBorderRight}> <Img src={ image} alt={ alt} className="chess-piece__img" /> </ChessPieceUI> ); }; const before = ` &:before { content: ''; position: absolute; left: calc(50% - 1px); top: 50%; border-right: 1px solid black; height: 100%; }`; const after = ` &:after { content: ''; position: absolute; left: 50%; top: calc(50% - 1px); border-top: 1px solid black; width: 100%; }`; const ChessPieceUI = styled.div<{ hasBefore: boolean; hasAfter: boolean }>` position: relative; ${ ({ hasBefore }) => hasBefore && before} ${ ({ hasAfter }) => hasAfter && after} `; const Img = styled.img` max-width: 100%; position: relative; z-index: 1; `; export default Piece;
Because of the use of CSS in JS, the amount of code looks longer.
The specific logical analysis is as follows:
90 grids are generated in the parent component (the chess can be placed at 90), and then the data of the piece is passed to the child component.
The child component before
will after
generate vertical and horizontal borders through and two pseudo selectors, the details are as follows:
The difference between using border-top
, border-bottom
, border-right
, here border-left
is not very big, as long as one is horizontal and the other is vertical, the difference is nothing more than the adjustment of the position, and finally the rowNum and colNum that need to be hidden. Without any conditional controls, before
and after
all rendered chessboards are as follows:
What I need to hide here is the one on the far right of the border-top
chessboard and the one on the bottom of the chessboard border-right
. Of course, because the space between the Chu River and Han borders is still needed, the Chu River and Han borders alsocol = 0
need to be reserved.col = 8
border-right
I use a one-dimensional array here, and the control of the condition will be a little more troublesome. If it is a two-dimensional array, it can row
be col
compared directly by and .
In general, this use of pseudo elements accidentally discovered this time is also a break from fixed thinking. Later, if there are other similar requirements, you can completely pass the pseudo elements. First, the processing of the event handler will be much more convenient (refs are required to use canvas), and secondly, it will also write a lot less code (I heard that canvas drawing is actually quite troublesome. ), and thirdly, the support for the mobile terminal (compared to the use of image cutting, the canvas mobile terminal is not very convenient and cannot be compared) is also much better.
By the way, I attach a few pictures of the effect of the mobile terminal. Of course, the size of the grid, padding and margin need to be adjusted, but this is just a matter of modifying two or three lines of code in CSS or constant.
Add a completed picture:
The Mizi grid is made for SVG.