Skip to content
animorio
IT
Back to the library

The paintbrush logo

Intro, SVG, TextGSAPAdvancedCompressed weight: Demo 17 KBGSAP 28 KBFreeDownload the zip

A logo intro with a big paintbrush: it lands on the canvas, its bristles bend and splay, and it drags a wide, textured stroke that runs out of paint and turns dry at the end, with splashes and drips. The letters open in the paint in negative, a second yellow stroke fills them and at the end a drop falls from the brush. In the Playground you type your own name or upload your own logo.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The paintbrush logo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/syne@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="scena">
    <!-- the stage: the script measures it, paints the canvas weave, builds the strokes and the brush, and paints the logo -->
    <svg class="palco" data-stage role="img" aria-label="Daubry" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <!-- the weave of the primed canvas: a little tile of threads the script paints on a canvas -->
        <pattern id="weave" patternUnits="userSpaceOnUse" width="96" height="96">
          <image width="96" height="96" data-weave-image />
        </pattern>
        <!-- the frayed, streaky edges of a brush stroke: a noise stretched along the stroke pushes the edges about -->
        <filter id="fray" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" data-fray>
          <feTurbulence type="fractalNoise" numOctaves="2" result="streaks" data-fray-turb />
          <feDisplacementMap in="SourceGraphic" in2="streaks" xChannelSelector="R" yChannelSelector="G" data-fray-disp />
        </filter>
        <filter id="fray-2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" data-fray-2>
          <feTurbulence type="fractalNoise" numOctaves="2" result="streaks" data-fray-turb-2 />
          <feDisplacementMap in="SourceGraphic" in2="streaks" xChannelSelector="R" yChannelSelector="G" data-fray-disp-2 />
        </filter>
        <!-- the logo cut out of the paint: white keeps the paint, the black letters open in it -->
        <mask id="holes" maskUnits="userSpaceOnUse" data-holes-mask>
          <rect fill="#fff" data-holes-rect />
          <g data-holes></g>
        </mask>
        <!-- the brush is painted as wide as it goes: these two follow its tip -->
        <clipPath id="reveal-1"><rect data-reveal-1 /></clipPath>
        <clipPath id="reveal-2"><rect data-reveal-2 /></clipPath>
        <!-- the brush's shadow on the canvas: its own outline, soft and dark -->
        <filter id="brush-shadow" x="-40%" y="-40%" width="180%" height="180%">
          <feGaussianBlur in="SourceAlpha" stdDeviation="6" result="soft" data-shadow-blur />
          <feFlood class="ombra-tinta" result="tone" />
          <feComposite in="tone" in2="soft" operator="in" />
        </filter>
        <!-- the wet shine that runs along the fresh paint -->
        <linearGradient id="gloss-grad" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0" class="lucido-stop lucido-stop--via" />
          <stop offset="0.5" class="lucido-stop" />
          <stop offset="1" class="lucido-stop lucido-stop--via" />
        </linearGradient>
      </defs>

      <!-- the primed canvas -->
      <rect class="tela" data-wall />
      <rect class="tela-trama" fill="url(#weave)" data-weave />

      <g data-world>
        <!-- the paint: the second colour under the first, the drips, the first stroke with the logo cut out of it -->
        <g data-paint>
          <g clip-path="url(#reveal-2)" data-coat-2></g>
          <g data-drips></g>
          <g mask="url(#holes)" data-coat-1-holder>
            <g data-coat-1-place>
              <g clip-path="url(#reveal-1)" data-coat-1></g>
            </g>
            <g data-rims></g>
            <g data-ripples></g>
          </g>
          <g data-spots></g>
        </g>

        <!-- the brush and its shadow; 0 0 is the middle of the bristles' tip -->
        <g data-shadow-place opacity="0">
          <use href="#brush" filter="url(#brush-shadow)" />
        </g>
        <g data-brush-place>
          <g id="brush">
            <g data-handle-place>
              <g data-handle></g>
            </g>
            <path class="setole" data-bristles />
            <path class="setole-colore" data-bristles-paint />
            <path class="setole-fili" data-strands />
            <path class="setole-lucido" data-strands-light />
            <path class="ghiera" data-ferrule />
            <path class="ghiera-righe" data-ferrule-lines />
            <path class="ghiera-luce" data-ferrule-light />
            <g class="goccia-pennello" data-hanging>
              <line data-hanging-neck />
              <circle data-hanging-drop />
            </g>
          </g>
        </g>

        <g data-fx></g>
      </g>
    </svg>
    <p class="nota">Click or tap to see it again</p>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

30 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The paintbrush logo” from Animorio and fit it into my site

What it does

On a white primed canvas, with the weave of the threads just showing, a big flat brush comes in from the top left: a green lacquered handle with a gilded ring and tip, a metal ferrule with its crimps, pale bristles dipped in vermilion, and a drop hanging from the corner, wobbling. The brush stops over the canvas with its bristles swinging, winds up backwards and then comes down with a slap: the bristles squash and fan out, the canvas gives a little jolt, paint splashes out behind in drops and sprays. Then it drags a wide, textured stroke: the bristles bend backwards, the stroke keeps darker ridges and shiny streaks of wet paint, its edges are frayed and a few drops fly off the sides. Little by little the brush presses less and the paint runs out: at the end the stroke breaks into dry, broken streaks where the canvas shows through. The brush lifts off with a flick of the wrist, the bristles snap back and quiver, and it leaves. Drips start from the bottom edge: a bead swells, runs down, slows and stops. Then the stroke changes: a glint runs along it, the paint swells a touch and the letters of a made-up brand, Daubry, open in the red one by one, in negative, each with a bounce, a ripple in the paint and a darker rim where the paint was pushed aside. The brush comes back from the left loaded with yellow and gives a second, faster stroke: the yellow goes under the red, fills the letters and peeks out below the stroke and past its end. The brush stops high over the end of the stroke, a yellow drop gathers on the bristles and stretches; a little jerk and the drop falls, stretches in the air and splats on the canvas with spatters around it and a small run. The brush leaves. The paint dries, the shiny streaks fade and now and then a glint passes over the stroke; after a few seconds all the paint runs down and vanishes and it starts again, with a stroke that is a little different every time. A click restarts it. A picture can take the place of the word: it opens in the paint and fills with yellow, or with its own colours.

How it works

One size

The stage is an SVG whose viewBox is as big as the window, so one unit is one pixel. The letters are measured on a canvas in the same typeface (measureText, actualBoundingBoxAscent for the true height) and everything is counted in letter heights: the stroke is as tall as the logo plus a margin, starts one height before it and ends, dry, a little after; the brush is as wide as the stroke. On a tall screen the logo takes the whole width and the dry ends may run off the edge.

The stroke

The body is a single path: an upper and a lower edge moved by a sum of sines, a fat round start where the brush landed and an end that breaks into fingers of paint. A filter pushes the edges about with a feDisplacementMap driven by a feTurbulence stretched along the stroke (low frequency across, high frequency up and down), so the edges turn into threads. On top lie the dark ridges and the shiny streaks, all parallel to the stroke; towards the end, canvas-coloured streaks with a rising gradient and a random dash let the canvas show through, and past the end broken streaks remain. The stroke is uncovered by a clipPath whose rectangle grows with the same ease as the brush.

The brush

Each bristle is a quadratic curve from the mouth of the ferrule to the tip. Three numbers do it all: pressure shortens the tuft and fans it out, drag moves the control point so that the tips trail behind, and how much paint there is decides how far up the coloured part goes (the same piece of curve, worked out with de Casteljau). The ferrule goes from the flat mouth to the round handle, and the handle is drawn once along its own axis. The shadow is a use of the brush with a blur filter, shifted further and paler the further the brush is from the canvas.

The logo in negative and the second colour

The red stroke has a mask: a white rectangle with the letters on it in black, each scaled up from zero with elastic.out around its own centre, so they open as holes in the paint. The same letters, with a dark outline only, make the ridge round the holes. The yellow stroke lies under the red one: it shows only through the holes, under the edge and past the end, so it fills the logo. If IMAGE holds an address, a filter turns the dark and coloured parts of the picture black for the mask, and feMorphology dilate makes the rim.

Direction

The whole intro is one timeline, with the labels start, arrive, press, stroke, lift, reveal, fill, drop, rest and leave and timeScale for the speed. The brush follows the stroke through an object that moves along and at every step reads the height of the stroke at that point; an inverse of the ease tells when the brush passes a point, so splashes and drips start at the right moment. The hanging drop stretches, comes away with the jerk and becomes an ellipse that falls with power2.in and splats on the canvas with elastic.out. The first painting is always the same (a random generator with a fixed seed), so the poster always shows the same logo.

Values to change

NameFileDefaultWhat it changes
NAMEscript.js'Daubry'The logo as a word: it opens in the paint letter by letter.
IMAGEscript.js''The logo as a picture: the address of a file, or upload it from your computer. If there is one, it takes the place of the word.
PAINTscript.js1How loaded the brush is: splashes and drips (0 = clean strokes, 2 = paint dripping everywhere).
PRESSUREscript.js1How hard the brush presses: how much the bristles bend and splay, the slap on the canvas and the jolt.
DRYscript.js1How long the dry, broken end of each stroke is (0.2 short, 2.5 long).
TINTscript.jstrueA picture is filled with the second colour; with false it is filled with its own colours.
SPEEDscript.js1The speed of the whole animation: 2 is twice as fast, 0.5 half as fast.
RESTscript.js4How many seconds the painted logo stays on show before the paint runs off.
LOOPscript.jstrueWhether the paint runs off and it is painted again. With false the logo stays, with a glint now and then.

Try them in the Playground

When to use it

To present a logo with a gesture full of colour and handwork: the opening of a painter-decorator's site, a paint shop, an art school, a festival, a streetwear brand or a venue, a preview for a client, a loading screen, a social post. It works with a written wordmark and with a picture.

On phones

The same animation with the sizes adapted: on a tall screen the logo is almost as wide as the screen, the dry ends of the strokes run off the edge and the big brush comes in and out at the sides. A tap restarts it.

With reduced motion

Nothing moves: the brush fades in over the canvas, then the red stroke with its drips fades in and the brush fades away; the letters appear in negative in the stroke and then the yellow that fills them. If the animation starts again, the paint leaves with a fade.

Pitfalls

  • Changing svgOrigin on an element GSAP has already scaled shifts it: GSAP makes up for it with a translation. The landing jolt and the swelling of the paint used two different origins and on a phone the stroke ended up higher than the letters. Now every element has a single origin, set when the scene is built.
  • The glint, a parallelogram moved with x, must show only on the paint: the mask has to sit on a still group around it, because a mask on the element that moves moves with it.
  • The tips of the bristles must be at least as tall as the stroke, or the paint shows above the brush cut straight by the clipPath: the tuft fans out with the pressure and the clipPath stays a little behind the tip.
  • An opacity set with gsap.set goes into the style and wins over the opacity attribute: the brush's shadow, written as an attribute, stayed full and dark. Now it is written into the style, multiplied by the brush's own opacity.
  • A drop falling from a tilted brush must start at the lowest corner of the bristles, worked out with the same pose the brush has at that moment of the timeline (its scale grows with the distance from the canvas).
  • The first painting uses a random generator with a fixed seed: with Math.random the drips and splashes of the poster changed at every test.

Libraries

GSAP 3.15.0, Syne (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 17 KB; the libraries: GSAP 28 KB.

Testing

Tested on 11 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All animations