query returns the first element matching sel.
{
return dom.Doc().Query(sel).Value
}
animate drives a requestAnimationFrame loop for the given duration and
invokes step with the current progress (0..1).
{
start := js.Performance().Call("now").Float()
total := float64(duration.Milliseconds())
var cb js.Func
cb = js.FuncOf(func(this js.Value, args []js.Value) any {
now := js.Performance().Call("now").Float()
p := (now - start) / total
if p > 1 {
p = 1
}
step(p)
if p < 1 {
js.RequestAnimationFrame(cb)
} else {
cb.Release()
}
return nil
})
js.RequestAnimationFrame(cb)
}
Keyframes leverages the Web Animations API to animate the selected element
using the provided keyframes and options. The returned value is the
underlying Animation object, allowing further control if needed.
{
el := query(sel)
frames := make([]any, len(keyframes))
for i, f := range keyframes {
frames[i] = f
}
opts := make(map[string]any, len(options))
for k, v := range options {
switch n := v.(type) {
case int:
opts[k] = float64(n)
case int32:
opts[k] = float64(n)
case int64:
opts[k] = float64(n)
default:
opts[k] = v
}
}
return el.Call("animate", frames, opts)
}
Translate moves the element selected by sel from the starting coordinates
to the destination using a translate transform.
{
el := query(sel)
animate(el, duration, func(p float64) {
x := fromX + (toX-fromX)*p
y := fromY + (toY-fromY)*p
el.Get("style").Set("transform", fmt.Sprintf("translate(%fpx,%fpx)", x, y))
})
}
Fade transitions the element's opacity from 'from' to 'to'.
{
el := query(sel)
animate(el, duration, func(p float64) {
val := from + (to-from)*p
el.Get("style").Set("opacity", val)
})
}
Scale scales the element from the starting factor to the ending factor.
{
el := query(sel)
animate(el, duration, func(p float64) {
val := from + (to-from)*p
el.Get("style").Set("transform", fmt.Sprintf("scale(%f)", val))
})
}
ColorCycle iterates the element's background color through the provided
list over the given duration.
{
el := query(sel)
stepDur := duration / time.Duration(len(colors))
go func() {
for _, c := range colors {
el.Get("style").Set("background", c)
<-time.After(stepDur)
}
}()
}
TestKeyFrameMap verifies basic operations on KeyFrameMap.
{
k := NewKeyFrame()
if len(k) != 0 {
t.Fatalf("expected empty keyframe map, got %d entries", len(k))
}
k.Add("opacity", 0.5).Add("transform", "scale(2)")
if v, ok := k["opacity"]; !ok || v != 0.5 {
t.Fatalf("expected opacity 0.5, got %v", v)
}
k.Delete("opacity")
if _, ok := k["opacity"]; ok {
t.Fatalf("expected opacity key removed")
}
}
{
s := &Scene{el: query(sel), options: opts}
c.scenes = append(c.scenes, s)
c.current = s
return c
}
AddKeyFrame appends a keyframe built with KeyFrameMap to the current scene.
For more advanced scenarios a raw map can be supplied using AddKeyFrameMap.
{
if c.current == nil {
c.AddScene("", nil)
}
frame["offset"] = offset
c.current.keyframes = append(c.current.keyframes, map[string]any(frame))
return c
}
AddKeyFrameMap appends a raw map as keyframe to the current scene and is kept
for backwards compatibility and advanced use cases.
{
if c.current == nil {
c.AddScene("", nil)
}
frame["offset"] = offset
c.current.keyframes = append(c.current.keyframes, frame)
return c
}
{
frames := []map[string]any{
{"offset": 0},
{"offset": 1},
}
for k, v := range props {
frames[0][k] = v
frames[1][k] = v
}
c.current.keyframes = append(c.current.keyframes, frames...)
if c.current.options == nil {
c.current.options = map[string]any{}
}
c.current.options["duration"] = duration.Milliseconds()
return c
}
{
fn(c)
return c
}
{
go fn(c)
return c
}
{
c.scripts = append(c.scripts, func() { <-time.After(d) })
return c
}
{
if count <= 0 {
c.loop = 1
} else {
c.loop = count
}
return c
}
{
c.video = query(sel)
return c
}
{
if c.video.Truthy() {
c.video.Call("play")
}
return c
}
{
if c.video.Truthy() {
c.video.Call("pause")
}
return c
}
{
if c.video.Truthy() {
c.video.Call("pause")
c.video.Set("currentTime", 0)
}
return c
}
{
if c.video.Truthy() {
c.video.Set("currentTime", t)
}
return c
}
{
if c.video.Truthy() {
c.video.Set("playbackRate", rate)
}
return c
}
{
if c.video.Truthy() {
c.video.Set("volume", v)
}
return c
}
{
if c.video.Truthy() {
c.video.Set("muted", true)
}
return c
}
{
if c.video.Truthy() {
c.video.Set("muted", false)
}
return c
}
{
if c.video.Truthy() {
track := js.Document().Call("createElement", "track")
track.Set("kind", kind)
track.Set("label", label)
track.Set("srclang", srcLang)
track.Set("src", src)
c.video.Call("appendChild", track)
}
return c
}
{
audio := js.Document().Call("createElement", "audio")
audio.Set("src", src)
audio.Set("controls", true)
c.root.Call("appendChild", audio)
c.audio = audio
return c
}
{
if c.audio.Truthy() {
c.audio.Set("currentTime", 0)
c.audio.Call("play")
}
return c
}
{
if c.audio.Truthy() {
c.audio.Call("pause")
}
return c
}
{
if c.audio.Truthy() {
c.audio.Call("pause")
c.audio.Set("currentTime", 0)
}
return c
}
{
if c.audio.Truthy() {
c.audio.Set("volume", v)
}
return c
}
{
if c.audio.Truthy() {
c.audio.Set("muted", true)
}
return c
}
{
if c.audio.Truthy() {
c.audio.Set("muted", false)
}
return c
}
{
if c.video.Truthy() {
bar := query(sel)
if bar.Truthy() {
bar.Set("max", 100)
update := func(js.Value) {
dur := c.video.Get("duration").Float()
if dur > 0 {
cur := c.video.Get("currentTime").Float()
bar.Set("value", cur/dur*100)
}
}
input := func(js.Value) {
dur := c.video.Get("duration").Float()
if dur > 0 {
valStr := bar.Get("value").String()
if val, err := strconv.ParseFloat(valStr, 64); err == nil {
c.video.Set("currentTime", val/100*dur)
}
}
}
events.OnTimeUpdate(c.video, update)
events.OnInput(bar, input)
}
}
return c
}
{
c.scripts = append(c.scripts, func() { fn(c.root) })
return c
}
{
c.start = fn
return c
}
{
c.end = fn
return c
}
{
for i := 0; i < c.loop; i++ {
if c.start != nil {
c.start()
}
for _, s := range c.scenes {
if len(s.keyframes) > 0 {
KeyframesForScene(s)
}
}
for _, fn := range c.scripts {
fn()
}
if c.end != nil {
c.end()
}
}
}
{
return &CinemaBuilder{root: query(sel), loop: 1}
}
{
// convert frames to []any to ensure proper conversion to JS values
frames := make([]any, len(s.keyframes))
for i, f := range s.keyframes {
frames[i] = f
}
// convert option numeric types to float64 for syscall/js compatibility
opts := make(map[string]any, len(s.options))
for k, v := range s.options {
switch n := v.(type) {
case int:
opts[k] = float64(n)
case int32:
opts[k] = float64(n)
case int64:
opts[k] = float64(n)
default:
opts[k] = v
}
}
return s.el.Call("animate", frames, opts)
}
KeyFrameMap simplifies building keyframe properties without
dealing with raw map structures.
map[string]any
NewKeyFrame returns an empty KeyFrameMap.
{
return KeyFrameMap{}
}
import "fmt"
import "time"
import "github.com/rfwlab/rfw/v1/dom"
dom
import "github.com/rfwlab/rfw/v1/js"
js
import "testing"
import "strconv"
import "time"
import "github.com/rfwlab/rfw/v1/events"
events
import "github.com/rfwlab/rfw/v1/js"
js