Auto-zoom from the cursor targets recorded by glide/3, and from the
target boxes of :upload events (upload/4, drop_files/4), so the view
holds on the file input or drop zone while the upload runs.
Glides that happen close together form a cluster. For each cluster the view eases in on the first target, pans between targets, holds after the last one, then eases back out. Each target's zoom is capped so the whole element (with margin) stays in frame.
The result is an ffmpeg zoompan filter, driven by the input timestamp
(it), with smoothstep easing between keyframes.
Summary
Functions
The zoompan filter for keyframes, producing out (%{width:, height:})
frames from input whose CSS-pixel viewport is viewport.
Keyframes {t_ms, zoom, cx, cy} in CSS pixels from a plan's events.
Returns [] when zoom is off or there are no glides.
A piecewise ffmpeg expression over it (seconds) that eases between
keyframe values with smoothstep.
Functions
The zoompan filter for keyframes, producing out (%{width:, height:})
frames from input whose CSS-pixel viewport is viewport.
Keyframes {t_ms, zoom, cx, cy} in CSS pixels from a plan's events.
Returns [] when zoom is off or there are no glides.
A piecewise ffmpeg expression over it (seconds) that eases between
keyframe values with smoothstep.
It's a flat sum, the first value plus each segment's change scaled by its
progress (0 before the segment, 1 after it), rather than nested if()s:
ffmpeg's expression parser gives up past about 100 levels of nesting, which
a few minutes of demo reaches. Segments that don't change the value add
nothing.
iex> PhxDemo.Zoom.piecewise([{0, 1.0, 0, 0}, {1000, 2.0, 0, 0}], fn {_, z, _, _} -> z end)
"1.0+(1.0)*clip((it-0.0)/1.0,0,1)*clip((it-0.0)/1.0,0,1)*(3-2*clip((it-0.0)/1.0,0,1))"
iex> PhxDemo.Zoom.piecewise([{0, 1.5, 0, 0}, {800, 1.5, 0, 0}], fn {_, z, _, _} -> z end)
"1.5"