DEV Community

Cover image for I've created an awesome painting app using React and Canvas API
Adrian Bece
Adrian Bece

Posted on • Edited on

I've created an awesome painting app using React and Canvas API

I've just submitted this app for Hashnode Christmas hackathon so I wanted to talk about it here in more detail.

I didn't have a chance to work with HTML canvas and the Canvas API, so this hackathon gave me a nice reason to dive right into it.

I also wanted to add something unique to make the result more magic, so I added a dynamic color brush and dynamic width brush. Turns out that this effect indeed looks more magic and dream-like.

Tech stack

  • React (with custom React hooks)
  • Canvas API
  • Native color picker and range inputs
  • Font awesome icons
  • Netlify hosting

Intro screen

Since I'm primarily a frontend developer and I want to pay special attention to design and details, I've wanted to create a nice splash screen for the app. I was inspired by the watercolor and paint set box designs.

I remember when I was buying paint sets for school, I was impressed by the images on the boxes. They showed a beautiful painting and were basically communicating "You can paint this beautiful image with this set". So I wanted to mimic that feeling with the splash screen.

If you wonder how I managed to overlay a gradient on the heading text, here is a code snippet.



  background: linear-gradient(
    90deg,
    hsl(0, 100%, 50%),
    hsl(211, 100%, 50%) 50%,
    hsl(108, 100%, 40%)
  );
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;


Enter fullscreen mode Exit fullscreen mode

Custom hook

I've added the painting functionality with event listeners and Canvas API using a custom hook that returns a bunch of states and functions that are required for switching brushes, setting up a Canvas ref, and keeping track of active states.

Dynamic color and brush width

This is where the magic happens. In the magic brush mode, I'm shifting the Hue value of HSL color for each paint event. The resulting effect is a wonderful color gradient. I've also added controls to change the color gradient saturation and lightness for more options and moods.



ctx.current.strokeStyle = `hsl(${hue.current},${selectedSaturation.current}%,${selectedLightness.current}%)`;
ctx.current.globalCompositeOperation = "source-over";

hue.current++;

if (hue.current >= 360) hue.current = 0;


Enter fullscreen mode Exit fullscreen mode

Similar to the magic brush mode, I've also added a dynamic width mode that changes brush size value up and down between the minimum and maximum value. When combined with the magic brush mode, you can create some awesome art and effects.



  const dynamicLineWidth = useCallback(() => {
    if (!ctx || !ctx.current) {
      return;
    }
    if (ctx.current.lineWidth > 90 || ctx.current.lineWidth < 10) {
      direction.current = !direction.current;
    }
    direction.current ? ctx.current.lineWidth++ : ctx.current.lineWidth--;
  }, []);


Enter fullscreen mode Exit fullscreen mode

App demo

https://magic-painter.netlify.app/

Source code

Post the art you create with the app in the comments! :)

If you enjoyed this post on my hackathon project for Hashnode, check out my hackathon project for DEV x DigitalOcean


These articles are fueled by coffee. So if you enjoy my work and found it useful, consider buying me a coffee! I would really appreciate it.

Buy Me A Coffee

Thank you for taking the time to read this post. If you've found this useful, please give it a ❤️ or 🦄, share and comment.

Top comments (31)

Collapse
 
golangch profile image
Stefan Wuthrich

Collapse
 
adrianbdesigns profile image
Adrian Bece

Thank you!

Collapse
 
dhruvgarg79 profile image
Dhruv garg
const cursor = `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="%23000000" opacity="0.3" height="${width}" viewBox="0 0 ${width} ${width}" width="${width}"><circle cx="${widthHalf}" cy="${widthHalf}" r="${widthHalf}" fill="%23000000" /></svg>') ${widthHalf} ${widthHalf}, auto`;
Enter fullscreen mode Exit fullscreen mode

can you please explain this line from source code?

Collapse
 
adrianbdesigns profile image
Adrian Bece

Sure. I'm replacing the custor with an dynamic background-image svg that is a circle with a certain radius.

Collapse
 
dhruvgarg79 profile image
Dhruv garg

got it. thanks.

Collapse
 
samymp profile image
SamSam

I've had to do a similar project during my studies, which made me notice something I struggled fixing when I coded it : when you resize the window, the cursor no longer paints where you click but a few (or several) pixels away.

It's up to you to try and fix that if you want, but good job anyway :)

Collapse
 
jeremierousseau profile image
JeremieRousseau

yes if your canvas is not clipped to the border top and border left, you must to use something as this :

scrolledY = Math.ceil( window.scrollY );

 x = event.clientX - Id("canvasToDraw").offsetLeft;
 y = event.clientY + scrolledY  - Id("canvasToDraw").offsetTop; 
Enter fullscreen mode Exit fullscreen mode

to compute where is your point on the canvas and this point with the border, and on a android tablet i use this :
x = Math.floor( event.touches[0].clientX - Id("canvasToDraw").getBoundingClientRect().left );
y = Math.floor( event.touches[0].clientY + scrolledY - Id("canvasToDraw").getBoundingClientRect().top );

and you must manage the scroll, so the scroll is at 0 with variable scrollY.

make a real software is fun, but that can become a real headache sometime...

Collapse
 
jeremierousseau profile image
JeremieRousseau

My work and my paint app of nears 10 thousand of lines of code in js/html5/css : essaie.fr/

But me I use the fact that html can create a matrix of pixels, to work pixels by pixels, and I created a system at a moment where if you click on a canvas you create a matrix of pixels to be used on another matrix of pixels to making your own brush, but it's slower that your solution, my app is in black and white to be used with a eink screens/ereader, and I tried to implements all of tools than an artist can image, and finished by to try to reinvent the wheel. I search few scripts in C to translate in JS to implements more features as an antialiasing post filter, but it's difficult to find documentation to do something beyond the documentation as MDN documentation and others tutorials on the web.

Collapse
 
adrianbdesigns profile image
Adrian Bece

Wow, that is impressive! I don't think JS can handle advanced transforms like antialiasing, but keep working on it. It is very sophisticated!

My app is a bit simpler since it's done for a hackathon in a few days, but I could also add more functionalities later down the line.

Collapse
 
bkis profile image
bkis

Nice! Adding configurable boundaries for the oscillating color/size of the magic brush would increase the possibilities a lot! A color change is very useful, but if it always goes through the whole rainbow spectrum, you'll always have a kids room's wall painting in the end ;)

Collapse
 
zimlearn profile image
Dr Abstract

Tidy - it looks like you might have fun with ZIM - it is a canvas framework with components built in at zimjs.com - there is a Gen Art section on the front that has results of various drawing works including from zimjs.com/genpen. You may also want to apply damping to your motion to make the drawing smoother. You can see that at work at zimjs.com/angels - there is a book of Angels there but also a link to the tool on the first page at left. Cheers!

Collapse
 
dineshrathee12 profile image
Dinesh Rathee

Very Nice

Collapse
 
joachimzeelmaekers profile image
Joachim Zeelmaekers

Amazing! Great job! 👌

Collapse
 
lyavale95 profile image
LyAVALE95

Amazing, conngrats

Collapse
 
adithyarafk profile image
AdithyaR-afk

Beautiful!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.