Valores de movimiento
Para un resorte retenido que sigue un valor cambiante, consulte springValue().
Un valor de movimiento es un número que se anima de forma nativa y controla todas las propiedades de vista vinculadas a él. Un valor puede mover una hoja, oscurecer un fondo y reducir la página detrás de él, todo en un reloj nativo:
import { animate, bind, motionValue } from '@rafaelaugusto/jolt'
const progress = motionValue(0)
bind(page, { scale: progress.to([0, 1], [1, 0.92]) })
bind(backdrop, { opacity: progress.to([0, 1], [0, 0.45]) })
bind(sheet, { y: progress.to([0, 1], [200, 0]) })
animate(progress, 1, 'snappy')Mapeo
to(input, output) asigna un valor a través de rangos de entrada y salida, como interpolate de React Native: lineal por partes, con las salidas de los puntos finales mantenidas fuera del rango de entrada. Pase { clamp: false } para extender los segmentos del punto final, de modo que el exceso del resorte se lleve a cabo. Las entradas pueden aumentar o disminuir, pero no pueden cambiar de dirección. Deben ser finitos, con al menos dos entradas y una salida por entrada.
const y = progress.to([0, 0.4, 1], [600, 120, 0])
const tint = progress.to([0, 1], ['#18181B', '#FACC15']) // colors mix in OKLab
const bounce = progress.to([0, 1], [0, 300], { clamp: false })Para una hoja cuyo desplazamiento va de closed a 0, asigne esa dirección directamente. Cada salida permanece emparejada con su entrada, incluidos los colores:
const closed = 380
const offset = motionValue(closed)
const open = offset.to([closed, 0], [0, 1])
bind(sheet, { y: offset })
bind(page, { scale: open.to([0, 1], [1, 0.9]) })
bind(backdrop, { opacity: open.to([0, 1], [0, 0.5]) })
animate(offset, 0, 'snappy')Esto da el mismo resultado que offset.to([0, closed], [1, 0]), sin cambiar sus matrices. La extrapolación numérica funciona en ambas direcciones; Los colores siempre se sujetan. Rangos no válidos como [0, 1, 0] arrojan cuando se llama a to().
Las entradas repetidas conservan su comportamiento de paso existente después de clasificar los pares en orden ascendente: en una parada interior repetida, la salida del segmento inferior gana; justo encima de él, gana la última salida en esa parada. La sujeción tiene prioridad en los puntos finales. Un rango totalmente igual mantiene su primera salida en o por debajo del tope y la última por encima; con clamp: false, la salida en el tope exacto es la segunda salida.
Un número asignado se puede asignar nuevamente. Las salidas en color se vinculan a backgroundColor, borderColor y color; las salidas numéricas se vinculan a todo lo demás que toma animate(), con scale configurando ambos ejes. Un valor también se puede vincular tal como está: bind(dot, { x: offset }).
Animación y ambientación
animate(value, to, transition) toma un número o fotogramas clave y las mismas transiciones que animate(): tweens, springs, repeat, repeatType, repeatDelay, delay y easeTrack. Devuelve un PlaybackAnimation, incluyendo pausa, reproducción y progreso finito. Jolt muestrea la curva del valor una vez, la asigna a cada enlace y reproduce una pista nativa por propiedad enlazada, todo iniciado al mismo tiempo. Los enlaces nativos no necesitan devoluciones de llamada JavaScript por cuadro.
animate(progress, 1, 'snappy')
animate(progress, [0, 1, 0.6], { duration: 700, times: [0, 0.5, 1] })
animate(pulse, 1, { duration: 900, repeat: Infinity, repeatType: 'reverse' })Una animación que interrumpe otra comienza desde el valor en pantalla, con su velocidad, por lo que cada vista enlazada gira junta. set(value, number) aplica un valor a cada vista enlazada a la vez. stop(value) mantiene el valor y sus vistas donde están. value.get() lee el valor ahora, mientras también anima.
Con movimiento reducido, las transformaciones enlazadas saltan hasta su final mientras la opacidad y los colores aún se animan. El escala de tiempo se aplica una vez, a la curva del valor.
Observando vistas exteriores
value.subscribe((value, time) => ...) sigue un valor de JavaScript, incluidas derivaciones numéricas y de color y valores de desplazamiento. Entrega el valor actual inmediatamente y devuelve una función de cancelación de suscripción idempotente. Pase { immediate: false } para recibir solo cambios posteriores.
const release = progress.subscribe((value, time) => {
// For example, update a SceneKit pose or a custom native renderer.
stage.setProgress(value)
})
const releaseColor = progress.to([0, 1], ['black', 'white']).subscribe((color) => {
stage.setTint(color)
})
// Ownership, including scope suspension/resume:
scope.use(() => progress.subscribe((value) => stage.setProgress(value)))
release()
releaseColor()Las actualizaciones comparten una fuente de fotogramas respaldada por la programación CADisplayLink/Choreographer de NativeScript. Cada lote de notificaciones captura todas las fuentes observadas antes de invocar a los oyentes y proporciona la misma marca de tiempo del reloj nativo en milisegundos. Llamadas set() repetidas antes de que un marco se fusione a su último valor; las salidas asignadas sin cambios no notifican. La finalización y la parada entregan el valor final en el siguiente cuadro si cambió. Una escritura realizada dentro de una devolución de llamada pertenece al siguiente lote.
Las devoluciones de llamada se ejecutan en JavaScript. Pueden controlar objetos fuera de las vistas NativeScript, pero no pueden ejecutarse mientras el subproceso JavaScript esté bloqueado. Esto es independiente del adaptador de propiedad nativa planificado. Los enlaces de vistas nativas continúan reproduciéndose independientemente de estas devoluciones de llamada.
La cancelación de la suscripción tiene efecto incluso durante la entrega y no detiene la animación. La fuente del marco queda inactiva después de que las fuentes observadas se estabilizan y se entregan las escrituras pendientes. Mientras la aplicación está en segundo plano, la entrega se detiene; reanudar muestras del valor actual sin reproducir fotogramas perdidos. Los errores de los oyentes van al seguimiento de errores de NativeScript y no detienen a otros oyentes. Utilice scope.use() o llame a la función devuelta cuando se destruya el consumidor.
El movimiento reducido mantiene la sincronización de la pista escalar; Las transformaciones vinculadas ya pueden estar al final mientras los suscriptores y los colores vinculados aún siguen el escalar. Los consumidores externos deben respetar ellos mismos el reducedMotion() al aplicar el movimiento espacial. La escala de duración del animador del sistema Android todavía se aplica a los relojes nativos, incluso cuando los animadores están deshabilitados.
Capas en un valor
Una capa con nombre agrega un desplazamiento escalar a un valor de movimiento antes en sus asignaciones. La animación base mantiene su propio tiempo y velocidad mientras un pulso de carga cambia a cada consumidor al mismo tiempo:
const charge = motionValue(0)
bind(car, {
y: charge.to([0, 1], [0, -20]),
scale: charge.to([0, 1], [1, 1.08]),
backgroundColor: charge.to([0, 1], ['#14b8a6', '#6366f1']),
})
animate(charge, 1, { duration: 4000 })
animate(charge, [0, 0.12, -0.12, 0], {
duration: 1200,
repeat: Infinity,
layer: 'pulse',
})Las capas de valores siempre se agregan, incluso cuando un enlace asigna el resultado a escala, opacidad o color. Su valor neutro es 0. Esto se diferencia del capas de propiedades de vista, que multiplica la escala y la opacidad. La sujeción, las asignaciones por partes y la mezcla de colores ven el escalar combinado; get() y subscribe() ven esa misma composición.
Cada nombre posee su animación y velocidad. Reorientar ese nombre reemplaza solo esa capa; animar sin layer reemplaza solo la base. Una capa completa mantiene su contribución final hasta que se elimina. El PlaybackAnimation devuelto también admite pausa, reproducción y progreso finito para ese canal; la pausa mantiene su contribución mientras otros canales continúan. Detener el identificador de una capa activa elimina su contribución; Detener el mango de la base sostiene la base y deja las capas en funcionamiento. Un mango asentado no hace nada.
stop(charge, { layer: 'pulse' }) // removes even a completed contribution
// Or ease the contribution back to zero first:
await animate(charge, 0, { duration: 250, layer: 'pulse' })
stop(charge, { layer: 'pulse' })
stop(charge) // freezes the combined value as the new base, clears all layers
set(charge, 0) // cancels all channels and replaces the valueArrastrar un valor de movimiento cambia solo su base. Los límites, los puntos de ajuste, la posición del controlador y los eventos de arrastre utilizan unidades base; las capas aditivas pueden mover el resultado combinado más allá de esos límites. Una capa sigue reproduciéndose mientras arrastra y suelta. Los valores de desplazamiento siguen siendo entradas de solo lectura y no exponen las capas de animación.
Los enlaces reproducen una composición nativa compilada. Se reconstruye según los cambios de canal y la entrada de arrastre, no en cada cuadro. Los períodos interminables de milisegundos enteros utilizan un bucle nativo común de hasta 30 segundos; Las combinaciones más largas o fraccionarias y las composiciones finitas de más de cuatro segundos utilizan ventanas reconstruidas en sus límites. Los relojes nativos mantienen la fase de cada canal. Optar por subscribe() también programa las notificaciones de JavaScript.
Encuadernación
bind(views, bindings) proporciona a las vistas el estado actual de los valores a la vez y los mantiene siguiendo. Devuelve una función de limpieza: después, las vistas permanecen donde están. motionScope().use(() => bind(...)) vincula un enlace a un alcance.
Un valor sobrevive a sus vistas. Una vista que aún no está cargada toma su valor cuando se carga y se une a una animación en progreso en el punto alcanzado. Un animate() o set() normal en una propiedad vinculada toma esa propiedad a su valor y velocidad actuales; el valor sigue impulsando sus otras vinculaciones. stop(view) detiene el movimiento en la pantalla pero mantiene el enlace.
Vue
useMotionValue() crea un valor que se detiene cuando el componente se desmonta. useBinding() devuelve una referencia de plantilla que se vincula al montar y se libera al desmontar, por lo que un elemento recreado por v-if recupera el valor nuevamente:
<script setup lang="ts">
import { animate } from '@rafaelaugusto/jolt'
import { useBinding, useMotionValue } from '@rafaelaugusto/jolt/vue'
const progress = useMotionValue(0)
const sheet = useBinding({ y: progress.to([0, 1], [200, 0]) })
const toggle = (open: boolean) => animate(progress, open ? 1 : 0, 'snappy')
</script>
<template>
<GridLayout :ref="sheet" />
</template>Desplazamientos de desplazamiento
scrollValue(scrollView) es un desplazamiento de ScrollView como valor, en DIPs. Vincúlelo como cualquier valor de movimiento; cada evento de desplazamiento escribe las propiedades enlazadas directamente, sin animación intermedia:
import { bind, scrollValue } from '@rafaelaugusto/jolt'
const offset = scrollValue(scroll)
bind(header, { y: offset.to([0, 90], [0, -64]) })
bind(title, { scale: offset.to([0, 90], [1, 0.72]), y: offset.to([0, 90], [0, 30]) })
bind(subtitle, { opacity: offset.to([0, 50], [1, 0]) })
bind(progress, { scaleX: offset.to([0, 700], [0, 1]) }){ axis: 'x' } sigue el desplazamiento horizontal. El valor comienza desde el desplazamiento actual y offset.ref(scrollView) sigue a otro ScrollView, o ninguno con null. offset.dispose() deja de seguir y deja las vistas enlazadas donde están. En Vue, useScrollValue() devuelve un valor cuyo ref va en la referencia de plantilla de ScrollView y termina con el componente:
<script setup lang="ts">
const offset = useScrollValue()
const header = useBinding({ y: offset.to([0, 90], [0, -64]) })
</script>
<template>
<ScrollView :ref="offset.ref">...</ScrollView>
<GridLayout :ref="header" />
</template>Los eventos de desplazamiento llegan a JavaScript, al igual que a scrollLinked(). Con cinco propiedades enlazadas, un evento cuesta entre 0,25 y 0,3 ms en el simulador iOS y el emulador Android, aproximadamente la mitad de la configuración equivalente de scrollLinked() en Android; ver el mediciones.
Arrastrando
drag(view, { axis, value }) escribe la posición del dedo en un valor en lugar de mover la vista, luego reproduce la liberación del valor. Todo lo que está ligado a él sigue al dedo y al lanzamiento juntos. El gesto se lee en la vista que pasas; vincule esa vista también si se mueve:
import { bind, drag, motionValue } from '@rafaelaugusto/jolt'
const closed = 380
const y = motionValue(closed)
bind(sheet, { y })
bind(page, { scale: y.to([0, closed], [0.9, 1]) })
bind(backdrop, { opacity: y.to([0, closed], [0.5, 0]) })
bind(hint, { opacity: y.to([closed / 2, closed], [0, 1]) })
drag(sheet, {
axis: 'y',
value: y,
bounds: { top: 0, bottom: closed },
snap: [0, closed / 2, closed],
rubberband: true,
})axis es 'x' o 'y'. Los límites, los puntos de ajuste y el eje arrastrado en los eventos están en las unidades del valor; el otro eje dice 0. Todo lo demás funciona como en drag(): goma elástica, fricción, devoluciones de llamada y el controlador. El lanzamiento es la misma curva, muestreada una vez y reproducida de forma nativa por cada propiedad vinculada, sin nada en JavaScript hasta que finaliza. onRelease recibe un Animation en lugar de un PlaybackAnimation. Al tocar la vista durante un lanzamiento, o durante animate(value, ...), se detiene el valor donde está y el gesto continúa desde allí.
Cada evento pan escribe las propiedades enlazadas directamente. Con seis propiedades enlazadas, un evento cuesta aproximadamente lo mismo que escribirlos a mano en onMove: alrededor de 0,8 ms en el simulador iOS y de 0,25 a 0,35 ms en el emulador Android; ver el mediciones. En Vue, inicie el arrastre desde useMotionScope() en onMounted, como lo hace la pantalla Arrastrar valores de la demostración.
¿Qué viene después?
Las capas de valores y capas de propiedades de vista se pueden combinar. La evaluación nativa, cuando las mediciones lo requieran, está prevista en el valores de movimiento RFC.