如何将Canvas绘图应用的函数式JS转为面向对象式?附相关疑问
Hey there! Let's break down your questions step by step, and then walk through how to adapt those functional code snippets into your existing OOP-based canvas app.
Understanding Functional vs. OOP JavaScript in Your Canvas App
1. Core Logic Differences Between Functional and OOP JS
- Functional Approach: Focuses on standalone functions that take input, process it, and return output—state (like
hasInputor canvas context) is usually stored outside functions. It’s great for simple, single-purpose tasks, but can get messy as your app grows (tracking scattered state across multiple functions becomes hard to manage). - OOP Approach: Encapsulates state (data like
contextReal,hasInput, or loaded images) and behavior (methods likeaddInput,applyFilter) together in classes. Each tool (text insertion, image filter) is an instance of a class that manages its own state, which makes organizing complex canvas tools far cleaner and avoids global state chaos.
2. Converting Functional Code to OOP
Let’s use your text insertion and image filter code as examples to refactor into classes that fit your existing PaintFunction structure.
Example 1: Text Insertion Tool (Refactored to OOP)
Original Functional Snippet (Simplified)
var canvas = document.getElementById('myCanvas'), ctx = canvas.getContext('2d'), font = '14px sans-serif', hasInput = false; canvas.onclick = function(e) { if (hasInput) return; addInput(e.clientX, e.clientY); } function addInput(x, y) { var input = document.createElement('input'); input.type = 'text'; input.style.position = 'fixed'; input.style.left = (x - 4) + 'px'; input.style.top = (y - 4) + 'px'; input.onkeydown = handleEnter; document.body.appendChild(input); input.focus(); hasInput = true; } function handleEnter(e) { if (e.key === 'Enter') { ctx.font = font; ctx.fillText(e.target.value, parseInt(e.target.style.left)+4, parseInt(e.target.style.top)+14); e.target.remove(); hasInput = false; } }
Refactored OOP Class (Extends PaintFunction)
class AddText extends PaintFunction { constructor(contextReal, contextDraft) { super(); // Initialize state and dependencies this.contextReal = contextReal; this.contextDraft = contextDraft; this.font = '14px sans-serif'; this.hasInput = false; this.canvas = document.getElementById('myCanvas'); // Bind event handler to the class instance (so `this` refers to the class) this.canvas.addEventListener('click', this.handleCanvasClick.bind(this)); } handleCanvasClick(e) { if (this.hasInput) return; this.addInput(e.clientX, e.clientY); } addInput(x, y) { const input = document.createElement('input'); input.type = 'text'; input.style.position = 'fixed'; input.style.left = `${x - 4}px`; input.style.top = `${y - 4}px`; input.addEventListener('keydown', this.handleEnter.bind(this)); document.body.appendChild(input); input.focus(); this.hasInput = true; } handleEnter(e) { if (e.key === 'Enter') { const input = e.target; // Draw text to the real canvas using the class's context this.contextReal.font = this.font; const drawX = parseInt(input.style.left) + 4; const drawY = parseInt(input.style.top) + 14; this.contextReal.fillText(input.value, drawX, drawY); input.remove(); this.hasInput = false; } } }
Example 2: Image Filter Tool (Refactored to OOP)
Refactored OOP Class
class ImageFilter extends PaintFunction { constructor(contextReal, contextDraft) { super(); this.contextReal = contextReal; this.canvas = document.getElementById('canvas'); this.photo = new Image(); this.photo.crossOrigin = 'anonymous'; // Handle CORS for external images this.photo.onload = () => this.renderImage(); } // Load an image (call this from your UI, e.g., a file input button) loadImage(src) { this.photo.src = src; } renderImage() { const width = window.innerWidth; const scale = width / this.photo.naturalWidth; const height = this.photo.naturalHeight * scale; this.canvas.width = width; this.canvas.height = height; this.contextReal.drawImage(this.photo, 0, 0, width, height); } // Example: Sepia filter implementation applySepiaFilter() { const imageData = this.contextReal.getImageData(0, 0, this.canvas.width, this.canvas.height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189)); data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168)); data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131)); } this.contextReal.putImageData(imageData, 0, 0); } }
3. Constructor/this Logic, Class Usage, and Shared Functions
Constructor & this Thinking Flow
- Identify State: First, list what data the class needs to track. For
AddText, it’s the canvas contexts, font style, andhasInputstatus. - Initialize Dependencies: Pass external dependencies (like
contextRealfrom your app’s core) to the constructor, then assign them tothisso all class methods can access them. - Bind Event Handlers: When adding event listeners inside a class, use
.bind(this)to ensurethisrefers to the class instance (not the event target, which is the default behavior).
How to Use the Class in Your App
- Instantiate the Class: When the user selects the tool, create a new instance with the required contexts:
const realCtx = document.getElementById('myCanvas').getContext('2d'); const draftCtx = document.getElementById('draftCanvas').getContext('2d'); const textTool = new AddText(realCtx, draftCtx); const filterTool = new ImageFilter(realCtx, draftCtx); - Trigger Class Methods: Bind UI actions to class methods (e.g., a "Load Image" button or "Apply Sepia" button):
// Load image from a file input document.getElementById('imageInput').addEventListener('change', (e) => { const file = e.target.files[0]; const src = URL.createObjectURL(file); filterTool.loadImage(src); }); // Apply sepia filter document.getElementById('sepiaBtn').addEventListener('click', () => { filterTool.applySepiaFilter(); });
Using Shared Functions from common.js
If you have utility functions in common.js, you have a few options:
- Import and Call Directly: Use ES modules to import the function and call it inside your class methods:
import { calculateCanvasScale } from './common.js'; class ImageFilter extends PaintFunction { renderImage() { const scale = calculateCanvasScale(this.photo.naturalWidth, window.innerWidth); // ... rest of render code } } - Attach as Static Methods: If the function is tool-specific, turn it into a static class method:
class AddText extends PaintFunction { static calculateTextDrawPosition(x, y) { return { x: x + 4, y: y + 14 }; } handleEnter(e) { const pos = AddText.calculateTextDrawPosition(parseInt(e.target.style.left), parseInt(e.target.style.top)); this.contextReal.fillText(e.target.value, pos.x, pos.y); } }
内容的提问来源于stack exchange,提问作者Rick
相关产品推荐
相关产品推荐

