So I have recently started learning web dev (including React since a few days ago), and I have tried to implement a simple drawing website that lets you change the brush size, color, and save the picture when you're finished.
I would appreciate any advice regarding style, good practices in React, etc.!
main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
index.css
body, html {
margin: 0;
padding: 0;
}
App.jsx
import './App.css'
import DrawingCanvas from './DrawingCanvas'
import ColorButton from './ColorButton'
import { useState } from 'react'
function App() {
const [color, setColor] = useState('black')
const [strokeSize, setStrokeSize] = useState(10)
const [canvasPNGDataURL, setCanvasPNGDataURL] = useState('')
const colors = ['black', 'white', 'red', 'yellow', 'blue', 'green', 'purple', '#ff69b4', '#ff8c00', '#00ffff']
return (
<div className='outer-container'>
<div className='top-bar'>
<input className='stroke-size-input' value={strokeSize} type="text" onChange={(e) => {setStrokeSize(e.target.value)}}/>
<div className='tool-bar'>
{colors.map((color) => {
return <ColorButton key={color} color={color} colorSetFunction={setColor}></ColorButton>
})}
</div>
<a className="save-button" href={canvasPNGDataURL} download="my-picture">Save</a>
</div>
<DrawingCanvas style="flex-grow: 1;" strokeSize={parseInt(strokeSize)} color={color} canvasPNGDataSetter={setCanvasPNGDataURL}></DrawingCanvas>
</div>
)
}
export default App
App.css
.top-bar {
background-color: black;
height: 120px;
display: flex;
align-items: center;
}
.top-bar > * {
flex-shrink: 0;
}
.stroke-size-input {
height: 50%;
width: 120px;
margin-left: 40px;
margin-right: 40px;
font-family: Arial, sans-serif;
font-size:xx-large;
}
.tool-bar {
flex-grow: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: space-around;
}
.save-button {
position: relative;
height: 40%;
width: 100px;
margin-left: 40px;
margin-right: 40px;
background-color: black;
font-family: Arial, sans-serif;
text-decoration: none;
color: white;
font-weight: bold;
font-size: xx-large;
inset: 0;
text-align: center;
align-content: center;
}
.outer-container {
display: flex;
flex-direction: column;
}
ColorButton.jsx
import React from "react";
import PropTypes from "prop-types";
import "./ColorButton.css";
export default function ColorButton({ color, colorSetFunction }) {
function applyColor() {
colorSetFunction(color);
}
return (
<button onClick={() => {applyColor()}} style={{ backgroundColor: color }}></button>
);
}
ColorButton.propTypes = {
color: PropTypes.string.isRequired,
colorSetFunction: PropTypes.func.isRequired
};
ColorButton.css
button {
height: 80px;
width: 80px;
border: white solid 1px;
cursor: pointer;
}
button:active {
opacity: 0.5;
}
DrawingCanvas.jsx
import { useEffect, useRef, useState } from 'react'
import './DrawingCanvas.css'
import PropTypes from 'prop-types';
function DrawingCanvas({ strokeSize, color, canvasPNGDataSetter }) {
let canvasContext = useRef(null)
const [isDrawing, setIsDrawing] = useState(false)
useEffect(() => {
let canvas = document.querySelector('#main-canvas')
canvasContext.current = canvas.getContext('2d')
canvasContext.current.lineJoin = 'round';
canvasContext.current.lineCap = 'round';
canvasContext.current.strokeSize = strokeSize;
}, [])
useEffect(() => {
canvasContext.current.strokeStyle = color;
}, [color])
useEffect(() => {
canvasContext.current.fillStyle = 'white';
canvasContext.current.fillRect(0, 0, 1000, 1000)
}, [])
useEffect(() => {
const canvas = document.getElementById('main-canvas')
canvasPNGDataSetter(canvas.toDataURL('image/png'))
})
useEffect(() => {
canvasContext.current.lineWidth = strokeSize
}, [strokeSize])
function drawIfMousePressed(event) {
if (event.buttons !== 1) {
closeDrawPath()
return
}
drawNextLine(event)
}
function drawNextLine(event) {
let rect = event.target.getBoundingClientRect()
let x = event.clientX - rect.left
let y = event.clientY - rect.top
if (!isDrawing) {
openDrawPath()
canvasContext.current.moveTo(x, y)
} else {
canvasContext.current.lineTo(x, y)
canvasContext.current.stroke()
}
}
function closeDrawPath() {
canvasContext.current.closePath()
setIsDrawing(false)
}
function openDrawPath() {
canvasContext.current.beginPath()
setIsDrawing(true)
}
return (
<div className="drawing-canvas-container">
<canvas className="drawing-canvas" id="main-canvas" height="1000" width="1000" onMouseMove={drawIfMousePressed} onMouseOut={closeDrawPath}></canvas>
</div>
)
}
DrawingCanvas.propTypes = {
strokeSize: PropTypes.number.isRequired,
color: PropTypes.string.isRequired,
canvasPNGDataSetter: PropTypes.func.isRequired
};
export default DrawingCanvas
DrawingCanvas.css
.drawing-canvas-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
outline: solid green;
padding-top: 75px;
padding-bottom: 75px;
}
.drawing-canvas {
outline: solid black;
height: 1000px;
width: 1000px;
}