Projects
Independent
This site is my portfolio. It and
Charm's Jobs
are my first web projects.
The
repository
is available on GitHub.
Originally, my plan was to create a facsimile of
my old resume
and publish it to the web. To accomplish that plan, I started
teaching myself HTML, CSS, and JavaScript.
MDN Web Docs
, an invaluable web developer wiki, soon became my primary
learning resource.
Fast-forward several months later and I have a resume, a
portfolio with two projects, and logos based on my
cats — and they talk.
The Charm (black cat) and Shelby (brown cat) logos are based on
artwork by Charlotte Ma. I drew them with an SVG editing
program,
Boxy SVG
. All the other SVG artwork, including the seasonal logo
artwork, associated animations, and paw cursors, is my own work.
The fonts that I use,
Open Sans
and
Comic Neue
, are by Steve Matteson and Craig Rozynski, respectively. The
icons, except for the sun/moon icon, are
Material Icons
by Google.
One feature that I built depended on solving cubic parametric
equations (cubic BΓ©zier curves). Solving these equations is
something your browser does every time it renders CSS Animations
or Web Animations, and I needed the same functionality for my
smooth scroller implementation. Instead of implementing my own
root-finding algorithms to solve the equations, I decided to
port
WebKit's algorithm
.
Everything else — the design, the scrolling
systems, the type and talk system, the interactive elements, the
logic,
everything — all down to the smallest detail, was
built with JavaScript, HTML, and CSS. No libraries. No
frameworks.
Features
Momentum Scrolling System — This momentum scrolling system for
non-touch-screen devices simulates the touch-based scrolling
systems found on smartphones and tablets.
This feature is intended to be used by mouse-users since mouse
drivers typically do not include momentum-scrolling features.
It may conflict with trackpads that provide momentum-scrolling
features.
The way it works is familiar: faster flicks produce more
momentum, resulting in scrolls that travel farther and last
longer. Slower flicks have the opposite effect. Flicking in
the same direction in quick succession leads to momentum
accumulation, i.e. faster scrolling.
When the border of a scroll container is hit by a momentum
scroll, it responds by bouncing. Harder impacts produce larger
bounces.
If a user tries to scroll beyond a border, the scroll
container will stretch in the direction of the pointer while
the pointer is being held down, but will then rebound when the
pointer is let go.
One-dimensional and two-dimensional scroll containers are
supported and both axes may be scrolled simultaneously.
The system depends on a JavaScript module that I built, called
MomentumScroller
. It has its own repository and documentation. Briefly,
however, instantiation of a MomentumScroller simply requires a
reference to the scroll container. Optionally, you may chain
various setters, either during or after instantiation, to set
custom grab and grabbing cursors, set the deceleration rate,
and more.
After instantiation, pointer flicks on the scroll container
will generate momentum scrolls.
Below is a demonstration of a momentum scroller container
along with controls for adjusting the number of axes, the
deceleration level, and the bounciness level.
Note that this demonstration will be disabled on touch-screen
devices since they provide their own native momentum scrolling
systems.
The Yowl, by Charlotte
Ma, is used as the background image of the two-dimensional
scroll container in this demonstration.
Momentum Scroller Demo
Momentum scrolling is disabled
Axes
Horizontal
Both
Vertical
Deceleration
None
Minimum
Low
Medium
High
Maximum
Bounciness
None
Minimum
Low
Medium
High
Maximum
Distance
-
Elapsed Time
-
The generation of momentum scrolls is simply an application of
kinematic equations
. After deceleration is set, the only missing variable needed
to calculate scroll duration, distance, and direction is
pointer velocity. The momentum accumulation feature is
accomplished by adjusting a velocity multiplier up or down
depending on the direction and timing criteria between
scrolls.
Scrolling will stop if scroll duration has elapsed, or if one
of the two edges of a one-dimensional scroller has been
reached, or if one of the four vertices of a two-dimensional
scroller has been reached, or if there is a "pointerdown"
event on the scroll container.
Notably, MomentumScroller is a system that works well with
other scrollers and elements that depend on "pointerdown"
events. By default, it works well with commonly used elements
such as anchors, buttons, and text boxes, but it can easily be
customized to work with any element that it needs to share
"pointerdown" events with.
For example, there are interactive elements on this site, such
as links and video galleries, that are descendants of the main
momentum scroller and which also need to respond to pointer
events. Logic was created to resolve these pointer event
conflicts so that the user experience is intuitive. Nested
MomentumScrollers also work perfectly well together.
The momentum scrolling system is enabled by default on
non-touch-screen devices, but it may be disabled if desired.
The preferred setting is remembered for future visits.
Advanced Smooth Scrolling — This smooth scrolling implementation
provides advanced, easy-to-use smooth scrolling functionality.
It allows you to customize smooth scrolling properties, such
as duration and easing. Additionally, it includes advanced
features such as scroll events and promises.
Every smooth scroll on this site is accomplished with this
implementation.
One-dimensional and two-dimensional scroll containers are
supported and both axes may be scrolled simultaneously.
This implementation depends on a JavaScript module that I
built, called
SmoothScroller
. It has its own repository and documentation. Briefly,
however, smooth scrolling may be accomplished by calling the
static SmoothScroller.scroll() method or by calling an
instance's scroll() method.
Below is a demonstration smooth scroller container along with
controls for adjusting the easing, duration, x-position, and
y-position.
Although the easing selector only includes five options, the
scroll method's easing argument accepts any valid set of
control points (e.g. [ P1x, P1y,
P2x, P2y ]), along with the standard
keywords you may use when writing CSS transitions and
animations (e.g. "ease", "ease-in", "linear", etc.).
The results of the returned promise will appear once the
scroll has completed.
Note that if momentum scrolling is enabled, this smooth
scroller container will simultaneously function as a momentum
scroller container, allowing you to test the interactions
between the different scroller types.
The image used as the background in this demonstration is
The Scream
by Edvard Munch.
Smooth Scroller Demo
Easing
Linear
Ease-In
Ease
Ease-Out
Ease-In-Out
X
-
Y
-
Elapsed Time
-
Interrupted
-
Whereas a momentum scroll's position over time is calculated
by solving kinematic equations, a smooth scroll's position
over time is calculated by solving cubic parametric equations.
This is where WebKit's cubic BΓ©zier root-finding algorithm
comes in.
I supply the algorithm with control points and a time ratio
and it returns a scroll ratio, which I then convert to a
scroll position.
I created this implementation because the standard method for
producing a smooth scroll
Element.scrollTo({
behavior: "smooth",
top: 200,
})
produces inconsistent results across browsers in terms of
duration and easing, is
not supported by Safari
, and — most
importantly — does not tell you if the
scroll made it to the intended destination (e.g. top: 200 in
the above example).
Unlike an instant scroll, a smooth scroll can be interrupted.
You cannot assume that the scroll made it. Some parts of my
code depend on knowing the outcome of a smooth scroll, and
this information is provided by the promise that is returned.
Intersection Observers
, which I use on this site, may also work to solve this
problem, but I prefer that the solution be built into the
scrolling method itself.
Responsive Video Galleries — All video galleries are aware of their
location relative to the viewport, as well as their location
relative to other video galleries, and they use this data to
intelligently decide when to play, stop, or scroll content.
Section Scroller — At the top of the portfolio is the
section scroller. It emerges from the slogan during the
loading animation sequence, and it is used for quickly
navigating the portfolio. It also automatically scrolls itself
when necessary to keep in sync with the main content.
Original Logos — Based on my two kittehs, Charm and
Shelby, these original logos blink and talk when you tap them.
Festive, seasonal logo themes automatically appear during
certain times of the year.
October
π
Seasonal logo themes automatically activate
The current kitteh is
and the current theme
is . Using the
selectors below, you can switch between Charm and Shelby and
the various themes.
Kitteh Logo and Theme Demo
Kitteh
Charm
Shelby
Theme
None
Auto
October
Halloween
November
December
I also created SVGs in the shape of Charm's and Shelby's paws
that serve as the grab and grabbing cursors while the momentum
scrolling system and selectors are in use.
Charm's and Shelby's paws serve as the grab and grabbing
cursors
Original Opening Animation Sequence — The logo, slogan, section scroller,
and other elements have been carefully choreographed to create
a delightful opening animation sequence.
Other animations, such as the ripple effect on buttons and
links, and the transition between the sun and moon icons, are
original code, but were inspired by other designs.
Type and Talk System — This typewriter-style messaging system
hearkens to the old-school dialogue systems of 80s and 90s
video games.
Messages are typed, character-by-character, inside a bubble
below the Charm or Shelby logo. If an audio source is
provided, it will play as the message is typed. The syntax is:
TypeAndTalk.submitMessage(message, options)
The message parameter expects a string, which may also include
commands within the string. The {blink} and {longblink}
commands make the Charm or Shelby logo do a normal or long
blink, respectively. The {0–5000} command delays typing
for the next 0–5000 milliseconds.
The options parameter, if supplied, allows setting the
audioSource, delayStart, delayEnd, and delayBetweenChars
properties.
If an audioSource is set, it will play while the message is
typing. However, the audio will be muted until the user
unmutes the sound by tapping the sound button inside the
message bubble.
The delayStart and delayEnd options allow you to delay the
beginning of typing and the clearing away of the message,
respectively, by a number of milliseconds. The arguments must
be a number between 0 and 5000. Numbers outside this range
will be adjusted.
The delayBetweenChars option expects a number between 0 and
120, which sets the number of milliseconds delay between typed
characters.
Message Typing Demo
Message typing is paused while menu is open
Longer delays are automatically added after punctuation,
unless overridden with an intrastring delay command, and a
message is automatically cleared from the screen shortly after
typing has finished, unless another message replaces it
sooner.
If the β···β menu is opened while a message is
on screen, the message will fade out, and typing and audio
will pause. The message will fade in and typing and audio will
resume once the β···β menu is closed.
A message's status (untyped, typing, typing (paused), typed)
is updated as necessary, and the promise returned by the
submitMessage method resolves once the message has finished
typing.
Want to speed up the typing? You can! Just tap and hold the
message. This is a behavior commonly implemented in video
games with lots of dialogue.
Dark and Light Modes — On first visit, the site's appearance
is determined by the device's mode, light or dark, if
available. If not, the appearance defaults to light. The
appearance may be toggled between light and dark, and the
preferred setting is remembered for future visits.
Keyboard Accessibility — All interactive elements are keyboard
accessible. Simply tab to an element and press the "Enter" key
to interact with it.
Elements intuitively gain or lose focus when necessary
depending on site circumstances.
Cancellable Actions — If the "Escape" key is pressed while
holding the "Enter" key or while holding the pointer on an
interactive element, the behavior that would have been
triggered is canceled.
Additionally, if you tap and hold on an interactive element,
and, while holding, move the pointer outside of the target and
let go, the action will be canceled.
Except for selectors, actions are only executed if the
"pointerdown" and "pointerup" targets match. Selectors are
cancelled only if, while the selector is being held, the
"Escape" key is pressed or the pointer goes beyond the edge of
a touch-screen.
Independent
Charm's Jobs Sardine theme background varies with the time of
day
Watch the full video demo of the Charm's Jobs app
Watch the full video demo of the Charm's Jobs app
This is Charm's Jobs. It and
my portfolio
are my first web projects.
The Charm's Jobs app is a progressive web app. It will be the
home for the upcoming book series βCharm's Jobs.β
As with my portfolio, everything in Charm's Jobs is made from
scratch with basic tools — JavaScript, HTML,
and CSS. No libraries. No frameworks.
The Charm's Jobs logo and the artwork for the Sardine theme are
by Charlotte Ma. The font,
Comic Neue
, is by Craig Rozynski, and the icons,
Material Icons
, are by Google.
Charm's Jobs is fully compatible with the Blink rendering
engine. Compatibility with the Gecko rendering engine is nearly
100%, but because
Gecko does not support CSS backdrop-filter
, parts of the Sardine theme do not look as intended. Work on
WebKit compatibility is ongoing.
Features
Responsive Design — The book displays one or two pages
depending on the book container's aspect
ratio — not simply the device's
orientation.
Although the book container's aspect ratio is influenced by
the device's orientation, it is also influenced by the height
of the bottom navigation bar, which may be in a raised or
lowered position.
Works Offline — Books can be read offline as long as
they have been downloaded. The Charm's Jobs app leverages the
service worker API and cache storage API, along with a custom
file versioning system, to efficiently manage cache.
Every time a book is loaded, the app checks that all required
resources are downloaded and current. Outdated resources are
automatically purged. Just one HTTP request is required to
check the freshness of all resources.
Touch Gestures and Keyboard Shortcuts — Navigating Charm's Jobs is intuitive.
Custom touch gestures and keyboard shortcuts are available to
turn pages, control audio playback, and adjust volume.
Saved Preferences — User preferences, such as the
last-used theme (including time of day and weather for the
Sardine theme), volume level, auto-play, and bottom navigation
bar position, are saved for future visits.
Auto Resume — The last page that a user viewed is
remembered so that the book will automatically scroll to that
page the next time the book is opened.
Real Progress Bars — Progress bars are shown when book
resources are being loaded or downloaded and they reflect the
actual progress of the data being downloaded to the device.
Sardine Theme Features
Dynamic Backgrounds — When the time of day is set to
current, the background will become a dynamic one, updating
itself every minute. When a specific time of
day — dawn, sunrise, morning, afternoon,
sunset, dusk, or night — is chosen, the
background will become static after transitioning to that time
of day.
Twinkling stars and a cat-themed-constellation start to appear
around sunset. They reach their greatest visibility at night,
and then gradually fade away as sunrise approaches.
Weather — When the weather is set to calm, the
sky becomes clear and the waves gently sway back and forth.
When set to storm, the sky gradually darkens, lightning begins
to flash, and the waves become choppier.
Animated Buoy — The brightness of the buoy's lights
varies with the time of day. The position of the reflections
off the surface of the sea take into account the position of
the lights.