Hone logo
Hone
Problems

Vue Canvas Renderer Component

This challenge involves creating a reusable Vue.js component that leverages the HTML5 Canvas API to render dynamic graphics. This is a fundamental skill for building interactive visualizations, games, or custom UI elements within a Vue application.

Problem Description

You are tasked with building a Vue.js component named CanvasRenderer that acts as a wrapper for an HTML5 <canvas> element. This component should accept a prop that defines the drawing operations to be performed on the canvas. The component should handle the initialization of the canvas context and re-render the content whenever the drawing instructions change.

Key Requirements:

  • Component Structure: Create a single-file Vue component (CanvasRenderer.vue) using TypeScript.
  • Canvas Element: The component must render an HTML <canvas> element.
  • Drawing Instructions Prop: The component should accept a prop, let's call it drawInstructions, which is an array of objects. Each object will represent a drawing command.
  • Drawing Command Interface: Define a TypeScript interface for the drawing commands. This interface should be flexible enough to accommodate various canvas drawing operations (e.g., fillRect, strokeRect, arc, lineTo, stroke, fill, beginPath, moveTo, closePath). Each command object should specify the type of operation and any necessary parameters.
  • Canvas Context Management: Initialize the 2D rendering context of the canvas within the component.
  • Rendering Logic: Implement a method that iterates through the drawInstructions and executes the corresponding canvas API calls.
  • Re-rendering: The canvas content should be re-rendered whenever the drawInstructions prop changes. A watch effect is recommended for this.
  • Canvas Sizing: The canvas element should automatically adjust its width and height attributes based on the parent container's size or a provided prop (for simplicity, let's assume it takes width and height props that are passed down to the canvas element's attributes).

Expected Behavior:

When the CanvasRenderer component is used in a parent component, and the drawInstructions prop is populated, the specified shapes and lines should be drawn on the canvas. If drawInstructions is updated (e.g., by user interaction or data changes), the canvas should clear and redraw according to the new instructions.

Edge Cases to Consider:

  • Empty drawInstructions: The component should gracefully handle an empty or undefined drawInstructions array (i.e., render a blank canvas).
  • Invalid Drawing Instructions: While not strictly enforced by this challenge, consider how an invalid command type or incorrect parameters might be handled (for this challenge, assume valid instructions).
  • Canvas Element Availability: Ensure the canvas context is only accessed after the canvas element is mounted and available in the DOM.

Examples

Example 1:

<template>
  <div>
    <CanvasRenderer :width="300" :height="150" :drawInstructions="myDrawingCommands" />
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
import CanvasRenderer from './CanvasRenderer.vue';

// Define the structure for drawing commands
interface DrawInstruction {
  type: string;
  [key: string]: any; // Allow for various parameters
}

export default defineComponent({
  components: {
    CanvasRenderer,
  },
  setup() {
    const myDrawingCommands = ref<DrawInstruction[]>([
      { type: 'fillStyle', color: 'blue' },
      { type: 'fillRect', x: 10, y: 10, width: 50, height: 50 },
      { type: 'strokeStyle', color: 'red' },
      { type: 'lineWidth', width: 2 },
      { type: 'strokeRect', x: 70, y: 10, width: 50, height: 50 },
      { type: 'beginPath' },
      { type: 'moveTo', x: 10, y: 70 },
      { type: 'lineTo', x: 130, y: 70 },
      { type: 'stroke' },
    ]);

    return {
      myDrawingCommands,
    };
  },
});
</script>

Output: A 300x150 canvas displaying:

  • A blue filled rectangle at (10, 10) with dimensions 50x50.
  • A red outlined rectangle at (70, 10) with dimensions 50x50 and a line width of 2.
  • A red horizontal line from (10, 70) to (130, 70).

Explanation: The myDrawingCommands array defines a sequence of operations. fillStyle sets the color for subsequent fills, fillRect draws a filled rectangle. strokeStyle and lineWidth set the properties for subsequent strokes. strokeRect draws an outlined rectangle. beginPath, moveTo, lineTo, and stroke are used to draw a simple line.

Example 2:

<template>
  <div>
    <CanvasRenderer :width="200" :height="200" :drawInstructions="circleCommands" />
    <button @click="updateCircle">Change Circle</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
import CanvasRenderer from './CanvasRenderer.vue';

interface DrawInstruction {
  type: string;
  [key: string]: any;
}

export default defineComponent({
  components: {
    CanvasRenderer,
  },
  setup() {
    const circleCommands = ref<DrawInstruction[]>([
      { type: 'fillStyle', color: 'green' },
      { type: 'beginPath' },
      { type: 'arc', x: 100, y: 100, radius: 50, startAngle: 0, endAngle: Math.PI * 2, anticlockwise: false },
      { type: 'fill' },
    ]);

    const updateCircle = () => {
      // Modify the drawing instructions to draw a smaller, red circle
      circleCommands.value = [
        { type: 'fillStyle', color: 'red' },
        { type: 'beginPath' },
        { type: 'arc', x: 100, y: 100, radius: 30, startAngle: 0, endAngle: Math.PI * 2, anticlockwise: false },
        { type: 'fill' },
      ];
    };

    return {
      circleCommands,
      updateCircle,
    };
  },
});
</script>

Output: Initially, a 200x200 canvas displays a large green filled circle. After clicking the "Change Circle" button, the canvas clears and displays a smaller red filled circle.

Explanation: The initial circleCommands draw a green circle. The updateCircle function replaces the entire circleCommands array with new instructions, causing the CanvasRenderer to re-render with the new red, smaller circle.

Constraints

  • The drawInstructions prop will be an array of objects conforming to the DrawInstruction interface.
  • The type property of DrawInstruction will be a string representing a valid CanvasRenderingContext2D method or property (e.g., fillRect, fillStyle, beginPath, arc, stroke).
  • Parameters within DrawInstruction objects will be correctly formatted for the corresponding Canvas API.
  • width and height props passed to CanvasRenderer will be non-negative numbers.
  • The rendering should be performant enough to handle several hundred drawing instructions without noticeable lag.

Notes

  • You will need a way to access the <canvas> element within your Vue component's template. A ref is the standard Vue 3 way to do this.
  • Consider using requestAnimationFrame for smoother re-rendering, especially if your drawing operations are complex or animated (though not strictly required for this challenge's core functionality).
  • The DrawInstruction interface should be defined in a way that allows for a variety of parameters. A good starting point is an interface with a type and an index signature (e.g., [key: string]: any). You might want to refine this for stronger typing if you anticipate a very specific set of commands.
  • Think about how to map the type string to the actual CanvasRenderingContext2D methods. A simple switch statement or a mapping object can be effective.
  • Remember to clear the canvas before re-rendering if the drawInstructions change to avoid drawing over old content.
Loading editor...
typescript