如何在 React 中构建像素艺术绘图应用程序
近年来,像素画游戏强势回归,但这一次并非因为技术的限制,而是因为像素画本身就很棒。一些游戏开发者正在调整工作流程,将最终产品转化为像素画游戏,即使他们最初可能从3D建模开始。另一些人则选择在像素画编辑器中绘制精灵图的常规方式,而这正是我们今天要构建的。我们将讲解如何生成动态像素网格、如何设置颜色选择器、如何实际绘制像素,以及如何将完成的像素画图导出为PNG图像。
如果您更喜欢视频版本,您可以在 Youtube 上观看我制作它:
我通过在工作文件夹中运行 create-react-app 命令启动了一个新的 React 应用程序。
create-react-app pixels
在这个项目中,我将更多地关注 React 代码部分,但会提供一个 SandBox 链接,指向包含 CSS 文件的完整代码示例。我主要使用 flexbox 来居中元素,这几乎占了 CSS 代码的 80%。剩下的部分是添加自定义颜色和边距来区分元素。
首先 - 我从 App.js 中删除了所有不必要的代码,并导入了编辑器组件,它将成为该应用程序的核心。
import "../styles/App.scss"
import Editor from "./Editor"
function App() {
return (
<div className="App">
<Editor />
</div>
)
}
export default App
接下来我们需要设置编辑器。
import React, { useState } from "react"
import "../styles/editor.scss"
export default function Editor() {
return (
<div id="editor">
<h1>Pixel Editor</h1>
<h2>Enter Panel Dimensions</h2>
<div id="options">
<div className="option">
<input type="number" className="panelInput" />
<span>Width</span>
</div>
<div className="option">
<input type="number" className="panelInput" />
<span>Height</span>
</div>
</div>
<button className="button">Start Drawing</button>
</div>
)
}
我们有几个标题元素和一些输入字段,用于动态设置绘图面板的宽度和高度。此外,还有一个按钮,用于隐藏选项并初始化绘图面板,以及在用户想要重新开始时重置像素网格。为此,我们需要设置一些 useState 钩子。
import React, { useState } from "react"
import "../styles/editor.scss"
export default function Editor() {
const [panelWidth, setPanelWidth] = useState(16)
const [panelHeight, setPanelHeight] = useState(16)
const [hideOptions, setHideOptions] = useState(false)
const [hideDrawingPanel, setHideDrawingPanel] = useState(true)
const [buttonText, setButtonText] = useState("start drawing")
const [selectedColor, setColor] = useState("#f44336")
function initializeDrawingPanel() {
setHideOptions(!hideOptions)
setHideDrawingPanel(!hideDrawingPanel)
buttonText === "start drawing"
? setButtonText("reset")
: setButtonText("start drawing")
}
return (
<div id="editor">
<h1>Pixel Editor</h1>
{hideDrawingPanel && <h2>Enter Panel Dimensions</h2>}
{hideDrawingPanel && (
<div id="options">
<div className="option">
<input
type="number"
className="panelInput"
defaultValue={panelWidth}
onChange={e => {
setPanelWidth(e.target.value)
}}
/>
<span>Width</span>
</div>
<div className="option">
<input
type="number"
className="panelInput"
defaultValue={panelHeight}
onChange={e => {
setPanelHeight(e.target.value)
}}
/>
<span>Height</span>
</div>
</div>
)}
<button onClick={initializeDrawingPanel} className="button">
{buttonText}
</button>
</div>
)
}
使用 useState 我们可以控制绘图面板的宽度和高度。我还添加了一些属性来控制元素的可见性。设置尺寸并点击按钮开始绘图后,所有选项都会隐藏,直到我们点击重新设计的“重置”按钮。
如果没有颜色选项,绘图应用就没什么用。在这个项目中,我决定使用 React-color 插件,它有很多不同的颜色选择器选项。我选择了他们的 CirclePicker 组件,不过你也可以在他们的网站上找到所有可选组件的完整列表,它们的工作方式都大同小异。
您可以通过运行来安装它
npm install react-color
现在我们需要进行设置。
import React, { useState } from "react"
import "../styles/editor.scss"
import { CirclePicker } from "react-color"
export default function Editor() {
const [panelWidth, setPanelWidth] = useState(16)
const [panelHeight, setPanelHeight] = useState(16)
const [hideOptions, setHideOptions] = useState(false)
const [hideDrawingPanel, setHideDrawingPanel] = useState(true)
const [buttonText, setButtonText] = useState("start drawing")
const [selectedColor, setColor] = useState("#f44336")
function initializeDrawingPanel() {
setHideOptions(!hideOptions)
setHideDrawingPanel(!hideDrawingPanel)
buttonText === "start drawing"
? setButtonText("reset")
: setButtonText("start drawing")
}
function changeColor(color) {
setColor(color.hex)
}
return (
<div id="editor">
<h1>Pixel Editor</h1>
{hideDrawingPanel && <h2>Enter Panel Dimensions</h2>}
{hideDrawingPanel && (
<div id="options">
<div className="option">
<input
type="number"
className="panelInput"
defaultValue={panelWidth}
onChange={e => {
setPanelWidth(e.target.value)
}}
/>
<span>Width</span>
</div>
<div className="option">
<input
type="number"
className="panelInput"
defaultValue={panelHeight}
onChange={e => {
setPanelHeight(e.target.value)
}}
/>
<span>Height</span>
</div>
</div>
)}
<button onClick={initializeDrawingPanel} className="button">
{buttonText}
</button>
{hideOptions && (
<CirclePicker color={selectedColor} onChangeComplete={changeColor} />
)}
</div>
)
}
CirclePicker 中的 color 属性用于标记当前选择的颜色,useChangeComplete 是组件的事件,可用于触发操作。在我们的例子中,在从选择器中选择不同的颜色后,我们希望切换所选颜色的状态。
在编辑器中唯一要做的就是添加我们还需要构建的 DrawingPanel 组件。
我们以通常的方式导入组件:
import DrawingPanel from "./DrawingPanel"
我们可以将其添加到按钮正下方的代码中:
{
hideOptions && (
<DrawingPanel
width={panelWidth}
height={panelHeight}
selectedColor={selectedColor}
/>
)
}
以下是 DrawingPanel 组件的外观:
import React, { useRef } from "react"
import "../styles/drawingPanel.scss"
import Row from "./Row"
export default function DrawingPanel(props) {
const { width, height, selectedColor } = props
let rows = []
for (let i = 0; i < height; i++) {
rows.push(<Row key={i} width={width} selectedColor={selectedColor} />)
}
return (
<div id="drawingPanel">
<div id="pixels">{rows}</div>
</div>
)
}
根据输入的高度,我们生成相同数量的行,并将它们推送到 div 容器中,但我们还需要将宽度传递给每个 Row 组件,以便知道每行需要生成多少“像素”。接下来我们需要设置 Row 组件,但我们会回到 DrawingPanel 来设置导出为 PNG 格式。
import React from "react"
import "../styles/row.scss"
import Pixel from "./Pixel"
export default function Row(props) {
const { width, selectedColor } = props
let pixels = []
for (let i = 0; i < width; i++) {
pixels.push(<Pixel key={i} selectedColor={selectedColor} />)
}
return <div className="row">{pixels}</div>
}
如您所见,我们以与在绘图面板中设置行相同的方式生成每行像素。现在我们需要设置像素组件,我们快完成了!
import React, { useState } from "react"
import "../styles/pixel.scss"
export default function Pixel(props) {
const { selectedColor } = props
const [pixelColor, setPixelColor] = useState("#fff")
const [oldColor, setOldColor] = useState(pixelColor)
const [canChangeColor, setCanChangeColor] = useState(true)
function applyColor() {
setPixelColor(selectedColor)
setCanChangeColor(false)
}
function changeColorOnHover() {
setOldColor(pixelColor)
setPixelColor(selectedColor)
}
function resetColor() {
if (canChangeColor) {
setPixelColor(oldColor)
}
setCanChangeColor(true)
}
return (
<div
className="pixel"
onClick={applyColor}
onMouseEnter={changeColorOnHover}
onMouseLeave={resetColor}
style={{ backgroundColor: pixelColor }}
></div>
)
}
我希望有一个可视化的指示器来指示绘图操作,而不仅仅是通过 CSS 将光标变为指针。因此,我设置了一些 useState 钩子来实现这一点。这里的想法是,我们希望在鼠标悬停时暂时改变像素的颜色。如果鼠标悬停离开,我们希望将其恢复到原来的颜色,并将其存储为单独的状态。但是,如果在鼠标悬停离开之前点击它,我们希望永久设置该颜色,因此我们设置了一个辅助状态属性 canChangeColor 来防止 onMouseLeave 函数干扰颜色变化。
我们已经完成了绘图函数的设置,剩下的就是设置导出了。回到DrawingPanel组件!
首先我们需要安装导出插件:
npm install react-component-export-image
之后,我们需要使用 useRef hook 进行设置。该插件需要引用要导出到图像的组件或元素。
import React, { useRef } from "react"
import "../styles/drawingPanel.scss"
import Row from "./Row"
import { exportComponentAsPNG } from "react-component-export-image"
export default function DrawingPanel(props) {
const { width, height, selectedColor } = props
const panelRef = useRef()
let rows = []
for (let i = 0; i < height; i++) {
rows.push(<Row key={i} width={width} selectedColor={selectedColor} />)
}
return (
<div id="drawingPanel">
<div id="pixels" ref={panelRef}>
{rows}
</div>
<button onClick={() => exportComponentAsPNG(panelRef)} className="button">
Export as PNG
</button>
</div>
)
}
现在,如果您在面板中绘制内容并点击我们新的导出按钮,您将获得一张包含导出组件的新 PNG 图像。您还可以使用此插件将组件导出为 JPEG 和 PDF 格式。
以上就是我在这个项目中使用的所有代码(CSS 除外)。如果你有兴趣查看完整的工作版本,可以在 CodeSandbox 上查看该项目。
如果您有任何问题或意见,可以通过Twitter和Instagram联系我,我还会在那里发布有趣的代码花絮和设计。
我还定期将 React 和 Web 开发教程上传到 Youtube,所以如果您感兴趣的话,请随时通过订阅我的频道来支持我。
鏂囩珷鏉ユ簮锛�https://dev.to/alekswritescode/how-to-build-a-pixel-art-drawing-app-in-react-37n8