@MrB33n

23 Followers
110 Following
371 Posts

Most devs reach for a library. The Canvas API already does this natively 🎨

I rebuilt a classic HTML5 text renderer in React — gradients, shadows, live controls, zero dependencies.

Read it → https://chat-to.dev/post?id=bWFXOVA0SnhNMVBVVFNGbmZwamdBZz09
#React #WebDev #JavaScript #Frontend

Drawing Text on the Canvas with React — The API You're Probably Underusing

The HTML5 Canvas API has been around for over a decade, yet it still catches people off guard the first time they use it. The idea is straightforward: the browser hands you a low-level rendering context, and you paint whatever you want, pixel by pixel. In this tutorial we will build an interactive **Text Arranger**, exactly the kind of project from the original 2014 post, but this time using React and the patterns we actually reach for today. By the end you will understand how the Canvas handles text, gradients, and shadows, and you will have a reusable component you can drop into any project. --- ## What the Canvas is and why it still matters The `<canvas>` element is essentially a bitmap that the browser exposes to JavaScript. Unlike SVG, which describes shapes in XML and keeps a manipulable node tree, the Canvas holds no state: when you clear and redraw, the previous image is simply gone. That makes it ideal for animations, games, and anything that demands high rendering throughput. For text specifically, the Canvas gives you fine-grained control over font family, weight, style, alignment, and baseline, along with native support for gradients and patterns via `fillStyle`. You cannot get that level of per-pixel control by applying CSS to a `<div>`. --- ## How the React approach differs The original post attached `addEventListener` directly to DOM elements and called a `drawScreen()` function on every change. In React, the mental model is different: state lives inside the component, and the canvas is redrawn as a side effect whenever that state changes. This maps naturally to `useState` for the controls and `useEffect` for the drawing cycle. ```jsx import { useRef, useState, useEffect } from "react" const CANVAS_WIDTH = 600 const CANVAS_HEIGHT = 300 export default function TextArranger() { const canvasRef = useRef(null) const [text, setText] = useState("Hello, Canvas!") const [fontSize, setFontSize] = useState(50) const [fontFamily, setFontFamily] = useState("serif") const [fontWeight, setFontWeight] = useState("normal") const [fontStyle, setFontStyle] = useState("normal") const [fillType, setFillType] = useState("color") // color | linearGradient | radialGradient const [color1, setColor1] = useState("#e63946") const [color2, setColor2] = useState("#457b9d") const [fillOrStroke, setFillOrStroke] = useState("fill") // fill | stroke | both const [alpha, setAlpha] = useState(1) const [shadowX, setShadowX] = useState(2) const [shadowY, setShadowY] = useState(2) const [shadowBlur, setShadowBlur] = useState(4) const [shadowColor, setShadowColor] = useState("#00000066") const [textAlign, setTextAlign] = useState("center") const [textBaseline, setTextBaseline] = useState("middle") useEffect(() => { const canvas = canvasRef.current const ctx = canvas.getContext("2d") // Clear the previous frame ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT) // Background ctx.globalAlpha = 1 ctx.shadowColor = "transparent" ctx.fillStyle = "#f1faee" ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT) ctx.strokeStyle = "#a8dadc" ctx.lineWidth = 3 ctx.strokeRect(6, 6, CANVAS_WIDTH - 12, CANVAS_HEIGHT - 12) // Text settings ctx.font = `${fontWeight} ${fontStyle} ${fontSize}px ${fontFamily}` ctx.textAlign = textAlign ctx.textBaseline = textBaseline ctx.globalAlpha = alpha // Shadow ctx.shadowColor = shadowColor ctx.shadowOffsetX = shadowX ctx.shadowOffsetY = shadowY ctx.shadowBlur = shadowBlur const x = CANVAS_WIDTH / 2 const y = CANVAS_HEIGHT / 2 const metrics = ctx.measureText(text) const w = metrics.width // Choose the fill style let style if (fillType === "color") { style = color1 } else if (fillType === "linearGradient") { const grad = ctx.createLinearGradient(x - w / 2, y, x + w / 2, y) grad.addColorStop(0, color1) grad.addColorStop(1, color2) style = grad } else if (fillType === "radialGradient") { const grad = ctx.createRadialGradient(x, y, 1, x, y, w / 2) grad.addColorStop(0, color1) grad.addColorStop(1, color2) style = grad } // Draw if (fillOrStroke === "fill" || fillOrStroke === "both") { ctx.fillStyle = style ctx.fillText(text, x, y) } if (fillOrStroke === "stroke" || fillOrStroke === "both") { ctx.strokeStyle = style ctx.lineWidth = 2 ctx.strokeText(text, x, y) } }, [ text, fontSize, fontFamily, fontWeight, fontStyle, fillType, color1, color2, fillOrStroke, alpha, shadowX, shadowY, shadowBlur, shadowColor, textAlign, textBaseline, ]) return ( <div> <canvas ref={canvasRef} width={CANVAS_WIDTH} height={CANVAS_HEIGHT} style={{ display: "block", marginBottom: "1rem" }} /> {/* Controls */} <label> Text <input value={text} onChange={e => setText(e.target.value)} /> </label> <label> Size: {fontSize}px <input type="range" min={10} max={200} value={fontSize} onChange={e => setFontSize(Number(e.target.value))} /> </label> <label> Font family <select value={fontFamily} onChange={e => setFontFamily(e.target.value)}> <option value="serif">Serif</option> <option value="sans-serif">Sans-serif</option> <option value="monospace">Monospace</option> <option value="cursive">Cursive</option> <option value="fantasy">Fantasy</option> </select> </label> <label> Weight <select value={fontWeight} onChange={e => setFontWeight(e.target.value)}> <option value="normal">Normal</option> <option value="bold">Bold</option> <option value="lighter">Lighter</option> </select> </label> <label> Style <select value={fontStyle} onChange={e => setFontStyle(e.target.value)}> <option value="normal">Normal</option> <option value="italic">Italic</option> <option value="oblique">Oblique</option> </select> </label> <label> Fill type <select value={fillType} onChange={e => setFillType(e.target.value)}> <option value="color">Solid color</option> <option value="linearGradient">Linear gradient</option> <option value="radialGradient">Radial gradient</option> </select> </label> <label> Color 1 <input type="color" value={color1} onChange={e => setColor1(e.target.value)} /> </label> <label> Color 2 <input type="color" value={color2} onChange={e => setColor2(e.target.value)} /> </label> <label> Mode <select value={fillOrStroke} onChange={e => setFillOrStroke(e.target.value)}> <option value="fill">Fill</option> <option value="stroke">Stroke</option> <option value="both">Both</option> </select> </label> <label> Alpha: {alpha} <input type="range" min={0} max={1} step={0.01} value={alpha} onChange={e => setAlpha(Number(e.target.value))} /> </label> <label> Shadow X: {shadowX} <input type="range" min={-50} max={50} value={shadowX} onChange={e => setShadowX(Number(e.target.value))} /> </label> <label> Shadow Y: {shadowY} <input type="range" min={-50} max={50} value={shadowY} onChange={e => setShadowY(Number(e.target.value))} /> </label> <label> Shadow blur: {shadowBlur} <input type="range" min={0} max={50} value={shadowBlur} onChange={e => setShadowBlur(Number(e.target.value))} /> </label> <label> Shadow color <input type="color" value={shadowColor.slice(0, 7)} onChange={e => setShadowColor(e.target.value)} /> </label> <label> Horizontal alignment <select value={textAlign} onChange={e => setTextAlign(e.target.value)}> <option value="center">Center</option> <option value="left">Left</option> <option value="right">Right</option> </select> </label> <label> Baseline <select value={textBaseline} onChange={e => setTextBaseline(e.target.value)}> <option value="middle">Middle</option> <option value="top">Top</option> <option value="bottom">Bottom</option> <option value="alphabetic">Alphabetic</option> <option value="hanging">Hanging</option> </select> </label> </div> ) } ``` --- ## Understanding each piece **`useRef` for the canvas** React should not manage the canvas as a controlled element, because what lives inside it is not JSX: it is the result of imperative calls to the drawing API. `useRef` gives us direct access to the DOM node without triggering a re-render. **`useEffect` as the rendering engine** The dependency array at the end of `useEffect` is what connects React's declarative world to the Canvas's imperative one. Every time any piece of state changes, the effect runs from scratch: it clears the canvas and redraws everything. This pattern is intentionally simple. In applications with continuous animations you would use `requestAnimationFrame` inside the effect, but for a static tool like this one, re-rendering on each change event is both sufficient and easy to follow. **Gradients** Creating a gradient on the Canvas always requires absolute coordinates. For a horizontal linear gradient centered on the text, we calculate the text width with `ctx.measureText(text).width` and position the gradient from `x - w/2` to `x + w/2`. For the radial version, we use the same center with different radii to produce a halo effect. Due to character limits, here is the final part.

Tim Cook stepping down as Apple CEO in September. John Ternus, the engineer behind iPad, AirPods, and iPhone Air, takes over. One of the most carefully planned leadership transitions in tech history. 🍎

https://chat-to.dev/view_trend?id=dFVpc3NZS1VzODcyNEcySXJaVDBwdz09

#Apple #TimCook #JohnTernus #Tech

Tim Cook to become Apple Executive Chairman John Ternus to become Apple CE

What if AGI isn’t coming… but already here?

Most people are still debating the future while ignoring what’s happening now.

This will make you rethink everything 👇
https://comuniq.xyz/post?t=880

#AI #AGI #Tech #Future #ArtificialIntelligence

AGI Is Here Already? | Comuniq

Join Comuniq to share and explore ideas on technology, science, art, and more.

You Can Now Run Steam Games on Android Handhelds and It Changes Everything https://playstationcouch.com/post.php?id=612 #gaming #android #linux #game
You Can Now Run Steam Games on Android Handhelds and It Changes Everything

Welcome to PlayStation Couch, your ultimate destination for all things video games. Explore the latest trends, advanced techniques, and valuable tips to enhance your gaming skills and maximize your gaming experience.

ChatGPT Just Went Down Worldwide and Everyone Noticed Fast https://chat-to.dev/post?id=QWF2elg4QUNGcytVSWtnV2ZwRTdHUT09 #samaltman #chatgpt #ai #techonology
ChatGPT Just Went Down Worldwide and Everyone Noticed Fast

If you tried using ChatGPT today and thought your internet was acting up, relax, it wasn’t just you. This Monday, the platform went down for a lot of people around the world. The first reports started around 10 AM New York time, when users began noticing the chat wouldn’t load or would suddenly stop responding. Within a short time, thousands of users were already complaining. Some couldn’t even log into their accounts, while others could get in but couldn’t access past conversations or generate responses. OpenAI confirmed there was an issue and said it was investigating a problem affecting both ChatGPT and Codex. The company didn’t explain exactly what caused the outage, but made it clear it was an internal issue, not something on the user’s side. The good news is that the service started coming back for some users a few hours later, although there were still some instability issues in certain cases. Moments like this show just how much tools like ChatGPT have become part of everyday life. When it goes down, it feels like half the internet goes down with it. https://www.techradar.com/news/live/chatgpt-down-april-2026

You Can Now Run Steam Games on Android Handhelds and It Changes Everything

Welcome to PlayStation Couch, your ultimate destination for all things video games. Explore the latest trends, advanced techniques, and valuable tips to enhance your gaming skills and maximize your gaming experience.

The Silent Crisis Killing Our Children — And What We Keep Refusing to Do About It https://comuniq.xyz/post?t=973 #war #teenager #gun #eua #trump #news #technology
AGI Is Here Already? | Comuniq

Join Comuniq to share and explore ideas on technology, science, art, and more.

They Have Millions. You Have Bills. This Is Why That Gap Keeps Growing. https://comuniq.xyz/post?t=969 #elonmusk #finace #business #technology #billgates
AGI Is Here Already? | Comuniq

Join Comuniq to share and explore ideas on technology, science, art, and more.

Forget it, you won't be making any more money as a freelancer https://chat-to.dev/post?id=eUxGVzFLa0FOL3NFM3FUNE02RnRXQT09 #freelancer #developer #code #programming
Forget it, you won't be making any more money as a freelancer

In this turbocharged internet age we live in — where everyone thinks they’re a genius and money has become the new religion — anyone trying to build software independently is basically signing a certificate of irrelevance. The world is overflowing with inflated egos and companies that worship profit above all else. And as if that weren’t bad enough, now we have these so-called 'intelligent machines.' They devour the code we write, spit out recycled versions, and somehow manage to convince a bunch of clueless people that they’re better than us — the real programmers. It’s like we’re being replaced by cheap knockoffs of our own brilliance — and the crowd is giving them a standing ovation. ![freelancer](https://imageio.forbes.com/specials-images/imageserve/1032845198/0x0.jpg?format=jpg&height=600&width=1200&fit=bounds)

The framework built by a father in 2006 dominated the internet before any tech giant could https://comuniq.xyz/post?t=966 #framework #jquery #javascript #programming #code #technology
AGI Is Here Already? | Comuniq

Join Comuniq to share and explore ideas on technology, science, art, and more.