As a full-stack developer, I’ve taken on various challenges in the world of WordPress plugin development, but none has been quite as eye-catching—and challenging—as the Decision Wheel plugin. The core idea behind the Decision Wheel is simple: a spinning wheel where each segment represents a decision or prize, providing users with an engaging way to make choices. However, implementing it with a rich, animated, and highly interactive UI, while ensuring optimal performance and code maintainability, has been anything but trivial.

This blog post details the technical architecture, dependencies, styling, scripts, and the evolving nature of this project. I’m still ironing out some kinks—particularly in the realm of text rotation for each slice—but the progress has been significant.

Tech Stack and Dependencies

At the core of this plugin, I aimed for a clean, modular, and future-proof stack:

  • WordPress API: The plugin hooks into WordPress, using shortcodes and admin settings to allow users to configure their custom wheels.
  • GSAP (GreenSock Animation Platform): GSAP powers the wheel’s smooth animations, ensuring the spinning behavior is polished and engaging.
  • Howler.js: This library is used for sound effects when the wheel spins and when the winning slice is selected.
  • Canvas API: A critical component, Canvas is used for rendering the wheel, slice colors, and text in a performant way.
  • Confetti.js: For that satisfying final touch, confetti rains down whenever a selection is made—powered by the lightweight Confetti.js.

The dependencies are loaded via WordPress’s wp_enqueue_script and wp_enqueue_style, ensuring they are only loaded on the front-end where the plugin is active.

WordPress Plugin Structure

I structured the plugin using a modular approach, separating concerns by file:

  • Main Plugin File: Handles WordPress hooks, enqueues the necessary scripts, and defines the shortcode.
  • Admin Settings Page: Allows users to define the wheel’s title, items, and optionally slice colors.
  • JavaScript Logic: The spinning behavior, animations, text rendering, and event handling are implemented in main.js.
  • Styling: Basic layout and button styles are handled by a CSS file.

The project follows best practices for plugin development, including sanitization of user inputs and leveraging wp_localize_script to pass PHP data to JavaScript.

The Journey: Building the Decision Wheel

Initial Setup: Shortcodes and Admin Settings

The first step was creating a customizable shortcode that users could insert into any post or page to display the decision wheel. The shortcode generates a <canvas> element where the wheel would be rendered. In the admin settings page, I provided inputs for defining the wheel title, items, and slice colors, ensuring everything was sanitized using sanitize_text_field and wp_sanitize_textarea.

I used the wp_localize_script function to pass dynamic data (like slice items and colors) from PHP to JavaScript. This allows the wheel to be highly dynamic—if the admin updates the wheel items in the settings, the changes are immediately reflected in the front-end rendering.

wp_localize_script('dw-main', 'dwData', [
    'wheel_items' => dw_get_wheel_items(),
    'slice_colors' => dw_get_slice_colors(),
    'sounds' => [
        'spin' => plugin_dir_url(__FILE__) . 'sounds/spin.mp3',
        'win' => plugin_dir_url(__FILE__) . 'sounds/win.mp3',
    ]
]);

Canvas Drawing and Animations

Next up was the real meat of the project: rendering the wheel dynamically inside the canvas element. The core challenge was splitting the wheel into slices, assigning colors to each slice, and writing text along the arc of each slice.

The Canvas API allowed me to draw arcs and rotate the entire canvas to simulate spinning. Here’s a simplified look at how each slice is drawn:

function drawWheel() {
    const arcSize = (2 * Math.PI) / items.length;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();
    ctx.translate(centerX, centerY);

    for (let i = 0; i < items.length; i++) {
        const angle = i * arcSize;
        ctx.beginPath();
        ctx.moveTo(0, 0);
        ctx.arc(0, 0, outerRadius, angle, angle + arcSize);
        ctx.closePath();

        const gradient = ctx.createLinearGradient(0, 0, outerRadius, outerRadius);
        gradient.addColorStop(0, sliceColors[i]);
        gradient.addColorStop(1, 'white');
        ctx.fillStyle = gradient;
        ctx.fill();
    }
    ctx.restore();
}

The wheel spins thanks to GSAP, which provides smooth animation control. I chose GSAP for its ease of use and performance benefits.

gsap.to(wheel, {
    rotation: spinAngle,
    duration: 5,
    ease: "power4.out",
    onUpdate: () => {
        startAngle = (wheel.rotation % 360) * (Math.PI / 180);
        drawWheel();
    },
    onComplete: showResult
});

Challenges with Text Rendering

Here’s where the difficulty began to ramp up: text rendering. Initially, the text for each slice was drawn in a simple, static way—just horizontally placed at an angle. This worked for shorter text but broke down when longer phrases were used, and the client wanted text to follow the curvature of the wheel.

To get the text to curve correctly, I had to rotate each character individually, a technique made trickier by the dynamic nature of the text. The code below handles this logic:

const anglePerChar = arcSize / text.length;

for (let j = 0; j < text.length; j++) {
    const char = text[j];
    ctx.save();
    ctx.rotate(j * anglePerChar - (arcSize / 2));
    ctx.fillText(char, 0, 0);
    ctx.restore();
}

However, this has proven to be one of the most complex aspects of the project. The text alignment varies based on its length and the angle of each slice. While the current version is readable, I am still optimizing it to ensure every piece of text follows the arc smoothly, without appearing distorted.

Confetti and Sound Effects

To enhance the UX, I added a burst of confetti using the lightweight Confetti.js whenever the wheel completes its spin. I also incorporated Howler.js to add audio feedback—a satisfying “clicking” noise during the spin and applause when the user wins.

function showResult() {
    spinSound.stop();
    winSound.play();

    // Confetti burst
    confetti({
        particleCount: 150,
        spread: 70,
        origin: { y: 0.6 }
    });
}

The Ongoing Challenge: Rotating Text

While the bulk of the functionality is complete, one of the trickiest remaining aspects is perfecting the way text is displayed. Rotating and curving text dynamically for each slice—especially for variable-length phrases—is a challenge I’m actively working on.

The difficulty lies in the fact that the Canvas API doesn’t provide built-in support for rotating text along a curve, so I’ve had to calculate everything manually. This includes determining where to place each character, rotating it to align with the slice, and adjusting the font size dynamically based on the slice size.

I’m exploring alternatives such as SVG for text rendering, as SVG supports better control over curved paths and might simplify the process.

Conclusion: A Work in Progress

The Decision Wheel Plugin has evolved into a technical showcase for advanced front-end interaction within WordPress. I’ve integrated multiple technologies—Canvas, GSAP, Howler.js, and Confetti.js—to create an engaging and customizable plugin. While the project is functional, I am still continually improving it, especially in terms of text rendering.

Current Project Demo: https://kevinchamplin.com/decision-wheel/