"Hand" drawn scribbles
2025.05.31#showcaseNo javascript here, these little 'scribbles' are actually a result of SVG animation via CSS using the stroke-dashoffset
property. Check it out 👁️
HTML Setup
<div class="paper">
<svg class=" drawn-path">
<path d="..." pathLength="1" />
</svg>
<img class="pencil" src="data:image/png;base64..." />
</div>
🔑 points:
.paper
sets up a container that looks like actual notebook paper 📃.The svg
.drawn-path
element is responsible for holding thepath
element which will contain instructions for what is actually is drawn in thed
attribute.The
pathLength
attribute helps in saying "treat the entire length of this path as 1 unit". Without it you'll see the path broken up into segments.The
.pencil
will contain this -> base64 string background image that "follows" the same drawing path to make it look like a proper scribble.Note on the
<path />
- The path needs to be a single stroke as animating multiple strokes are not supported.
- You can't set the SVG
d
attribute value via CSS.
Make .paper look like paper
Our piece of 'paper' has the following variables and containment definitions.
.paper {
--duration: 2s;
--size: 200px;
--half-size: calc(var(--size) / 2);
position: relative;
display: grid;
place-content: center;
width: var(--size);
height: var(--size);
/* notebook lines */
background: linear-gradient(to bottom, white 19px, deepskyblue 20px);
background-size: 100% 20px;
background-repeat: repeat-y;
}
🔑 points:
position:relative
is going to help contain the drawn path and pencil icon on the paperdisplay: grid
andplace-content: center
are a more modern approach to centering elements that we use here to get.drawn-path
and.pencil
to be on top of each other later on- The proximity of the
white 19px, deepskyblue 20px
in thelinear-gradient
essentially turn our gradient into a solid line because the gradient is entirelywhite
until19px
and immediately transitions todeepskyblue
at20px
. - Then the
background-size
y value of20px
combined with thebackground-repeat
only doingrepeat-y
will recreate the same gradient all the way to the end of the piece of 'paper'
Centering the drawing and pencil
To ensure the drawing and pencil act as two differnt layers we need to prep the following:
.drawn-path,
.pencil {
position: absolute;
grid-column: 1;
grid-row: 1;
transform: translate(
calc(-1 * var(--half-size)),
calc(-1 * var(--half-size))
);
}
grid-colum
andgrid-row
are key so that these elements occupy the same cell of the grid- Since everything is perfectly centered on the piece of paper (from
place-content:center
in the parent.paper
), we need thetransform
here to help pull the starting point of where the drawing and the pencil technically start to be from to the top left corner.
Drawing a straight line
Path expresssions in SVGs can be expressed through a combination of letters and numeric coordinates. A simple line can look as follows:
M 100 20 L 100 180
Funky lookin', but here's how it breaks down:
Command | Description |
---|---|
M 100 20 | Move (without drawing) to coordinates (100,20) |
L 100 180 | Start drawing a line down to (100,180) |
We plug this in to the markup on the the d
attribute of the path
element
<path d="M 100 20 L 100 180" pathLength="1" />
Here's how that would look when animated:
The actual origin of the SVG
(0,0) in our display's coordinate system usually means to start from the top-left corner. Our SVG line from above looks like it followed that principle but in reality for SVG (0,0) means start from the "middle". We adjusted for that by what we did with the transform
previously but if we had let SVG do it's own thing the drawing would look 'shifted' and go off the page. We also need to give a height
and width
that matches the size of the paper so the drawn path is visible.
.drawn-path {
height: var(--size);
width: var(--size);
& path {
fill: none;
stroke: gray;
stroke-width: 2;
stroke-dasharray: 1;
stroke-dashoffset: 1;
animation: draw var(--duration) linear infinite;
}
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}
🔑 points:
- We use CSS nesting with the
& path
to better target the SVG element - For
fill:none
, we only care about the 'edges' of our drawing, in the case of a line there is nothing to 'fill' in so,none
is a good value for our scribbling purposes stroke
is the color of the line andstroke-width
controls the thickness- Think of
stroke-dasharray
as the answer to "How many segments do you want your line to be composed of ?". In this case we want a solid single line therefore we use1
which also helps us stay in conjunction with thepathLength
attribute that was set. - Think of
stroke-dashoffset
as the answer to "How much of the line do you want drawn in ?" In this case a positive value of 1 actually shifts things backwards so nothing will be drawn in. By animating this with thedraw
animation we take thestroke-dashoffset
value to 0 which essentially 'draws' it back in.
Here's a visual of what would happen if we didn't do the earlier transform: translate(...)
on a heart shape path.
Make the pencil work
Finally the pencil, not as complex as the SVG portion, a bit of manual manipulation to get the led tip on the drawing path so that our pencil 'follows' the drawing. That can be acheived with:
.pencil {
top: -6px;
left: 6px;
width: 21px;
height: 21px;
offset-path: path(...); /* The M 100 100 L 100 20 .... etc*/
offset-rotate: 0deg;
offset-distance: 0%;
animation: write var(--duration) linear infinite;
}
@keyframes write {
to {
offset-distance: 100%;
}
}
🔑 points:
top
andleft
are 'trial and error' values to get the led tip directly on the SVG drawing pathwidth
andheight
are what the dimensions of the base 64 string pencil generates ->offset-path
this is what gives the pencil a path to follow. We use the same value used on thepath
element and supply to the CSS functionpath(...)
more infooffset-rotate
prevents the pencil from actually rotating along the path drawn. We want the pencil to stay upright while moving.offset-distance
is where in the path to start. We start at the beginning, hence0
- Our animation
write
then takes care of the rest. It matches the duration, timing-function and repeats infintely to stay in sync with the svg being drawn in. Theoffset-distance
is set to100%
during its animation so the pencil starts moving.
Put it all together and you can draw single stroke elements like the good ol' cool S 😄
Try to tweak the codepen and if you need a good editor to create some paths of your own to plug in checkout out -> svg-path-editor
-zan0