Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Add interpolation to fixed time step to improve animation smoothness #933

Merged
merged 1 commit into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions packages/embla-carousel-auto-scroll/src/components/AutoScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {
function createAutoScrollBehaviour(engine: EngineType): ScrollBodyType {
const {
location,
previousLocation,
offsetLocation,
target,
scrollTarget,
index,
Expand All @@ -164,12 +166,15 @@ function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {
let rawLocationPrevious = 0
let hasSettled = false

function seek(): ScrollBodyType {
function seek(timeStep: number): ScrollBodyType {
const fixedDeltaTimeSeconds = timeStep / 1000
let directionDiff = 0

bodyVelocity = directionSign * options.speed
previousLocation.set(location)

bodyVelocity = directionSign * options.speed * 55
rawLocation += bodyVelocity
location.add(bodyVelocity)
location.add(bodyVelocity * fixedDeltaTimeSeconds)
target.set(location)

directionDiff = rawLocation - rawLocationPrevious
Expand All @@ -186,8 +191,8 @@ function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {

const reachedEnd =
options.direction === 'forward'
? reachedMin(location.get())
: reachedMax(location.get())
? reachedMin(offsetLocation.get())
: reachedMax(offsetLocation.get())

if (!loop && reachedEnd) {
hasSettled = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const SLIDE_NUMBER_STYLES = css`
align-items: center;
justify-content: center;
height: __replace_slide_height__;
user-select: none;
}
`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ const EmblaCarousel = (props) => {

emblaApi.reInit()
const newEngine = emblaApi.internalEngine()
const copyEngineModules = ['location', 'target', 'scrollBody']
const copyEngineModules = [
'scrollBody',
'location',
'offsetLocation',
'previousLocation',
'target'
]
copyEngineModules.forEach((engineModule) => {
Object.assign(newEngine[engineModule], oldEngine[engineModule])
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ const EmblaCarousel: React.FC<PropType> = (props) => {
emblaApi.reInit()
const newEngine = emblaApi.internalEngine()
const copyEngineModules: (keyof EngineType)[] = [
'scrollBody',
'location',
'target',
'scrollBody'
'offsetLocation',
'previousLocation',
'target'
]
copyEngineModules.forEach((engineModule) => {
Object.assign(newEngine[engineModule], oldEngine[engineModule])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ const EmblaCarousel: React.FC<PropType> = (props) => {
emblaApi.reInit()
const newEngine = emblaApi.internalEngine()
const copyEngineModules: (keyof EngineType)[] = [
'scrollBody',
'location',
'target',
'scrollBody'
'offsetLocation',
'previousLocation',
'target'
]
copyEngineModules.forEach((engineModule) => {
Object.assign(newEngine[engineModule], oldEngine[engineModule])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ export const setupInfiniteScroll = (emblaApi, loadMoreCallback) => {

emblaApi.reInit()
const newEngine = emblaApi.internalEngine()
const copyEngineModules = ['scrollBody', 'location', 'target']
const copyEngineModules = [
'scrollBody',
'location',
'offsetLocation',
'previousLocation',
'target'
]
copyEngineModules.forEach((engineModule) =>
Object.assign(newEngine[engineModule], oldEngine[engineModule])
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const setupInfiniteScroll = (
const copyEngineModules: (keyof EngineType)[] = [
'scrollBody',
'location',
'offsetLocation',
'previousLocation',
'target'
]
copyEngineModules.forEach((engineModule) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const setupInfiniteScroll = (
const copyEngineModules: (keyof EngineType)[] = [
'scrollBody',
'location',
'offsetLocation',
'previousLocation',
'target'
]
copyEngineModules.forEach((engineModule) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ export function EmblaCarousel() {
console.log(`Embla just triggered ${eventName}!`)
}

function onInit(event): void {
function onInit(event: CustomEvent<EmblaCarouselType>): void {
emblaApi = event.detail
emblaApi.on('slidesInView', logEmblaEvent)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export function EmblaCarousel() {
console.log(emblaApi.slidesInView())
}

function onInit(event): void {
function onInit(event: CustomEvent<EmblaCarouselType>): void {
emblaApi = event.detail
emblaApi.on('slidesInView', logSlidesInView)
}
Expand Down
7 changes: 5 additions & 2 deletions packages/embla-carousel-fade/src/components/Fade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function Fade(userOptions: FadeOptionsType = {}): FadeType {
const fullOpacity = 1
const noOpacity = 0
const fadeFriction = 0.68
const timeStep = 1000 / 60

let emblaApi: EmblaCarouselType
let opacities: number[] = []
Expand All @@ -28,6 +29,7 @@ function Fade(userOptions: FadeOptionsType = {}): FadeType {
let fadeVelocity = 0
let progress = 0
let shouldFadePair = false

let defaultSettledBehaviour: ScrollBodyType['settled']
let defaultProgressBehaviour: EmblaCarouselType['scrollProgress']

Expand Down Expand Up @@ -214,10 +216,11 @@ function Fade(userOptions: FadeOptionsType = {}): FadeType {
return distanceSign === directionSign ? nextSnap : selectedSnap
}

const fade = (emblaApi: EmblaCarouselType): void => {
function fade(emblaApi: EmblaCarouselType): void {
const { dragHandler, scrollBody } = emblaApi.internalEngine()
const fixedDeltaTimeSeconds = timeStep / 1000
const pointerDown = dragHandler.pointerDown()
const velocity = scrollBody.velocity()
const velocity = scrollBody.velocity() * fixedDeltaTimeSeconds
const duration = scrollBody.duration()
const fadeIndex = getFadeIndex()
const noFadeIndex = !isNumber(fadeIndex)
Expand Down
1 change: 1 addition & 0 deletions packages/embla-carousel/src/__tests__/loop-ltr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const scrollToLocationInstant = (
engine.target.set(location)
engine.scrollBody.useDuration(0)
engine.animation.update()
engine.animation.render(1)
}

describe('➡️ Loop - Horizontal LTR', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const setLocationOutOfBounds = (
engine: EngineType,
outOfBoundsLocation: number
): void => {
engine.offsetLocation.set(outOfBoundsLocation)
engine.location.set(outOfBoundsLocation)
engine.target.set(outOfBoundsLocation)
}
Expand All @@ -21,7 +22,7 @@ describe('➡️ ScrollBounds - Horizontal LTR', () => {
emblaApi.on('settle', settleCallback)

setLocationOutOfBounds(engine, engine.limit.max + 1)
engine.animation.update()
engine.animation.render(1)

expect(settleCallback).toHaveBeenCalledTimes(0)
})
Expand All @@ -46,7 +47,7 @@ describe('➡️ ScrollBounds - Horizontal LTR', () => {
emblaApi.on('settle', settleCallback)

setLocationOutOfBounds(engine, engine.limit.min - 1)
engine.animation.update()
engine.animation.render(1)

expect(settleCallback).toHaveBeenCalledTimes(0)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const setLocationOutOfBounds = (
engine: EngineType,
outOfBoundsLocation: number
): void => {
engine.offsetLocation.set(outOfBoundsLocation)
engine.location.set(outOfBoundsLocation)
engine.target.set(outOfBoundsLocation)
}
Expand All @@ -23,7 +24,7 @@ describe('➡️ ScrollBounds - Horizontal RTL', () => {
emblaApi.on('settle', settleCallback)

setLocationOutOfBounds(engine, engine.limit.max + 1)
engine.animation.update()
engine.animation.render(1)

expect(settleCallback).toHaveBeenCalledTimes(0)
})
Expand Down Expand Up @@ -52,7 +53,7 @@ describe('➡️ ScrollBounds - Horizontal RTL', () => {
emblaApi.on('settle', settleCallback)

setLocationOutOfBounds(engine, engine.limit.min - 1)
engine.animation.update()
engine.animation.render(1)

expect(settleCallback).toHaveBeenCalledTimes(0)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const setLocationOutOfBounds = (
engine: EngineType,
outOfBoundsLocation: number
): void => {
engine.offsetLocation.set(outOfBoundsLocation)
engine.location.set(outOfBoundsLocation)
engine.target.set(outOfBoundsLocation)
}
Expand All @@ -23,7 +24,7 @@ describe('➡️ ScrollBounds - Vertical', () => {
emblaApi.on('settle', settleCallback)

setLocationOutOfBounds(engine, engine.limit.max + 1)
engine.animation.update()
engine.animation.render(1)

expect(settleCallback).toHaveBeenCalledTimes(0)
})
Expand Down Expand Up @@ -52,7 +53,7 @@ describe('➡️ ScrollBounds - Vertical', () => {
emblaApi.on('settle', settleCallback)

setLocationOutOfBounds(engine, engine.limit.min - 1)
engine.animation.update()
engine.animation.render(1)

expect(settleCallback).toHaveBeenCalledTimes(0)
})
Expand Down
27 changes: 20 additions & 7 deletions packages/embla-carousel/src/components/Animations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,35 @@ import { EngineType } from './Engine'
import { EventStore } from './EventStore'
import { WindowType } from './utils'

export type AnimationsUpdateType = (engine: EngineType) => void
export type AnimationsUpdateType = (
engine: EngineType,
timeStep: number
) => void
export type AnimationsRenderType = (
engine: EngineType,
lagOffset: number
) => void

export type AnimationsType = {
init: () => void
destroy: () => void
start: () => void
stop: () => void
update: () => void
render: (lagOffset: number) => void
}

export function Animations(
ownerDocument: Document,
ownerWindow: WindowType,
update: AnimationsType['update']
update: (timeStep: number) => void,
render: (lagOffset: number) => void
): AnimationsType {
const documentVisibleHandler = EventStore()
const timeStep = 1000 / 60
let lastTimeStamp: number | null = null
let animationFrame = 0
let lag = 0
let animationFrame = 0

function init(): void {
documentVisibleHandler.add(ownerDocument, 'visibilitychange', () => {
Expand All @@ -38,15 +47,18 @@ export function Animations(
if (!animationFrame) return
if (!lastTimeStamp) lastTimeStamp = timeStamp

const timeElapsed = timeStamp - lastTimeStamp
const elapsed = timeStamp - lastTimeStamp
lastTimeStamp = timeStamp
lag += timeElapsed
lag += elapsed

while (lag >= timeStep) {
update()
update(timeStep)
lag -= timeStep
}

const lagOffset = lag / timeStep
render(lagOffset)

if (animationFrame) ownerWindow.requestAnimationFrame(animate)
}

Expand All @@ -73,7 +85,8 @@ export function Animations(
destroy,
start,
stop,
update
update: () => update(timeStep),
render
}
return self
}
Loading
Loading