More Animatable Notation #
There are many link-tags in the homepage of my website used to introduce school, project, and other that related to me. In order to add some decorations to then, I firstly think about using some notation around the tags when hovering on them.
There are 2 challenges to deal with:
- Handwriting-like notation is preferred.
- The notation should be animatable when mouse moving in and leaving out.
Handwriting-like style #
I found it is easy to paint a rough notation using SVG after looking up MDN: d. As an example, I wants draw a static wavy line. Just using Quadratic Bézier curve (Q/q
command in path data), imo, is enough to draw a wavy line.
When q
up and down with same dx
and dy
, we got a static wavy line:
And adding some randomness to dx
and dy
:
Animation #
What about the second challenge? Take a look at the logo on the top left of this page. It is a SVG image with an animation which is inspired by Animated SVG Logo. It use animation css keyframe of strokeDashArray
to realize the paint-animation of stroke, from 0 length
to length 0
.
Inspired by Animated line drawing in SVG, I use pure Javascript to make it animate and use strokeDashOffset
to realize the animation as below:
- Init
strokeDashArray
andstrokeOffset
both tolength
. - Transform
strokeOffset
fromlength
to0
.
Note that there are some bug of strokeDashArray
in browser that it will display a small dot when dashLength is 0. Setting the opacity to 0 whenever dashLength is 0 can work, or just use the strokeDashOffset
solution which is more elegant.
Below are two examples of the animation using animate()
:
Warning
Percentage value for strokeDashArray
and strokeOffset
is mentioned in MDN Documents, but is unavailable for broswer now, thus getTotleLength()
should be called to get the dashLength after create the SVGPathElement. We can also see from this that a pure Javascript implementation is necessary。
Rough Notation #
I’m surprised that Slidev’s v-mark function accurs to me after completing the code above. It uses Rough Notation as a solution to mark anything using vue directive. And Rough.js it uses can generate handwrite-style path datas easyly.
Barriers #
It seems that the wavy lines above can animate while showing and hiding. But using animate()
make it hard to record the strokeDashArray
or strokeOffset
. Back to the question I mentioned at the beginning, how to make the notation animatable when mouse moving in and leaving out? Just imagine that when I move the mouse out before the paint animation is completed, erase animation should be triggered begin with the current length of the stroke. Howerver, it is hard to get the current length of the stroke when using animate()
, or maybe use Animation.currentTime
which is not supported greatly in current browsers.
I think it is a good idea to use requestAnimationFrame()
to control the animation. It is a highly flexible api that can record anything by ourselves. The parameters of requestAnimationFrame()
is as below, which make it available to control the duration.
declare function requestAnimationFrame(
callback: FrameRequestCallback): number;
interface FrameRequestCallback {
(time: DOMHighResTimeStamp): void;
}
type DOMHighResTimeStamp = number;
Notation Stretcher #
Now you can use it in your project by installing package from npm:
npm install notation-stretcher
Basic Usage #
import { notate } from 'notation-stretcher'
const foo = document.querySelector('.bar')
Toggle the notation by mouseover and mouseleave:
const n = notate(foo, 'box', { color: '#2f81f7' })
foo.addEventListener('mouseover', () => n.show(2000))
foo.addEventListener('mouseleave', () => n.hide(500))
Show and hide the notation Infinity times:
const n = notate(foo, '=', {
color: '#a371f7'
opacity: 0.3,
zIndexOffset: -1,
})
n.onShowed(() => setTimeout(() => n.hide(500), 1000))
n.onHidden(() => setTimeout(() => n.show(2000), 500))
n.show(2000)
Show the notation once immediately:
const n = notate(foo, '_', { color: '#f85149' })
n.show(0)
Custom Your Own Notation Path #
The types of paths
parameter is as below:
export type Constructor<T> = (w: number, h: number) => T
export type Path = string | Drawable
export type OrArrayOf<T> = T | Array<T>
export type OrConstructorOf<T> = T | Constructor<T>
export type PathConstructor = OrConstructorOf<OrArrayOf<Path>>
You can pass a path data with type string[]
or Drawable[]
(see Rough.js) to the paths
parameter of notate()
. If path datas are depended on the size of the element, you can pass a function with width
and height
as parameters to paths
.
import PathGenerator from 'notation-stretcher/path-generator'
import PathAnimator from 'notation-stretcher/path-animator'
// PathRoughOptions is a subset of RoughOptions
const pg = new PathGenerator({ maxRandomnessOffset: 0.4 })
const paths = (w, h) => [
pg.line(0, 0, w, h),
pg.arc(0, 0, w, h, Math.PI / 2, Math.PI),
]
const pa = new PathAnimator(foo, paths, { opacity: 0.8 })
pa.show(2000)
// onUnmounted(() => pa.remove())
Known Issues #
There also are some parts of stroke will showed when the length is zero. When I zoom in on the browser interface, the problem will disappear 🤨. Maybe it is a bug of browser. Or who can tell me why? 🤔