Skip to content

Commit

Permalink
Bug fix for #928.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjerleke committed Jul 6, 2024
1 parent 387a3fa commit 6ed3a51
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {
const directionSign = options.direction === 'forward' ? -1 : 1
const noop = (): ScrollBodyType => self

// TODO: Make it work with interpolation

let bodyVelocity = 0
let scrollDirection = 0
let rawLocation = location.get()
Expand Down
4 changes: 3 additions & 1 deletion packages/embla-carousel-fade/src/components/Fade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ function Fade(userOptions: FadeOptionsType = {}): FadeType {
const fade = (emblaApi: EmblaCarouselType): void => {
const { dragHandler, scrollBody } = emblaApi.internalEngine()
const pointerDown = dragHandler.pointerDown()
const velocity = scrollBody.velocity()
// TODO: There's probably a better way to solve velocity below
const timeStep = 1000 / 60
const velocity = (scrollBody.velocity() * timeStep) / 1000
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
}
79 changes: 58 additions & 21 deletions packages/embla-carousel/src/components/Engine.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Alignment } from './Alignment'
import { Animations, AnimationsType, AnimationsUpdateType } from './Animations'
import {
Animations,
AnimationsType,
AnimationsUpdateType,
AnimationsRenderType
} from './Animations'
import { Axis, AxisType } from './Axis'
import { Counter, CounterType } from './Counter'
import { DragHandler, DragHandlerType } from './DragHandler'
Expand Down Expand Up @@ -44,6 +49,7 @@ export type EngineType = {
indexPrevious: CounterType
limit: LimitType
location: Vector1DType
offsetLocation: Vector1DType
options: OptionsType
percentOfView: PercentOfViewType
scrollBody: ScrollBodyType
Expand Down Expand Up @@ -149,21 +155,30 @@ export function Engine(
const slideIndexes = arrayKeys(slides)

// Animation
const update: AnimationsUpdateType = ({
dragHandler,
eventHandler,
scrollBody,
scrollBounds,
scrollLooper,
slideLooper,
translate,
location,
animation,
options: { loop }
}) => {
const update: AnimationsUpdateType = (
{ dragHandler, scrollBody, scrollBounds, options: { loop } },
timeStep
) => {
if (!loop) scrollBounds.constrain(dragHandler.pointerDown())
scrollBody.seek()
scrollBody.seek(timeStep)
}

const render: AnimationsRenderType = (
{
scrollBody,
translate,
location,
offsetLocation,
scrollLooper,
slideLooper,
dragHandler,
animation,
eventHandler,
scrollBounds,
options: { loop }
},
lagOffset
) => {
const shouldSettle = scrollBody.settled()
const withinBounds = !scrollBounds.shouldConstrain()
const hasSettled = loop ? shouldSettle : shouldSettle && withinBounds
Expand All @@ -174,22 +189,40 @@ export function Engine(
}
if (!hasSettled) eventHandler.emit('scroll')

const interpolatedLocation =
location.get() * lagOffset + prevLocation.get() * (1 - lagOffset)

offsetLocation.set(interpolatedLocation)

if (loop) {
scrollLooper.loop(scrollBody.direction())
slideLooper.loop()
}

translate.to(location.get())
translate.to(offsetLocation.get())
}

const animation = Animations(ownerDocument, ownerWindow, () => update(engine))
const animation = Animations(
ownerDocument,
ownerWindow,
(timeStep) => update(engine, timeStep),
(lagOffset: number) => render(engine, lagOffset)
)

// Shared
const friction = 0.68
const startLocation = scrollSnaps[index.get()]
const location = Vector1D(startLocation)
const prevLocation = Vector1D(startLocation)
const offsetLocation = Vector1D(startLocation)
const target = Vector1D(startLocation)
const scrollBody = ScrollBody(location, target, duration, friction)
const scrollBody = ScrollBody(
location,
offsetLocation,
prevLocation,
target,
duration,
friction
)
const scrollTarget = ScrollTarget(
loop,
scrollSnaps,
Expand All @@ -201,6 +234,7 @@ export function Engine(
animation,
index,
indexPrevious,
scrollBody,
scrollTarget,
target,
eventHandler
Expand Down Expand Up @@ -267,6 +301,7 @@ export function Engine(
indexPrevious,
limit,
location,
offsetLocation,
options,
resizeHandler: ResizeHandler(
container,
Expand All @@ -280,13 +315,15 @@ export function Engine(
scrollBody,
scrollBounds: ScrollBounds(
limit,
location,
offsetLocation,
target,
scrollBody,
percentOfView
),
scrollLooper: ScrollLooper(contentSize, limit, location, [
scrollLooper: ScrollLooper(contentSize, limit, offsetLocation, [
location,
offsetLocation,
prevLocation,
target
]),
scrollProgress,
Expand All @@ -302,7 +339,7 @@ export function Engine(
slideSizesWithGaps,
snaps,
scrollSnaps,
location,
offsetLocation,
slides
),
slideFocus,
Expand Down
17 changes: 12 additions & 5 deletions packages/embla-carousel/src/components/ScrollBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type ScrollBodyType = {
direction: () => number
duration: () => number
velocity: () => number
seek: () => ScrollBodyType
seek: (timeStep: number) => ScrollBodyType
settled: () => boolean
useBaseFriction: () => ScrollBodyType
useBaseDuration: () => ScrollBodyType
Expand All @@ -15,6 +15,8 @@ export type ScrollBodyType = {

export function ScrollBody(
location: Vector1DType,
offsetLocation: Vector1DType,
prevLocation: Vector1DType,
target: Vector1DType,
baseDuration: number,
baseFriction: number
Expand All @@ -26,21 +28,26 @@ export function ScrollBody(
let rawLocation = location.get()
let rawLocationPrevious = 0

function seek(): ScrollBodyType {
function seek(timeStep: number): ScrollBodyType {
const fixedDeltaTimeSeconds = timeStep / 1000
const duration = scrollDuration * 0.016
const diff = target.get() - location.get()
const isInstant = !scrollDuration
let directionDiff = 0

if (isInstant) {
bodyVelocity = 0
prevLocation.set(target)
location.set(target)

directionDiff = diff
} else {
bodyVelocity += diff / scrollDuration
prevLocation.set(location)

bodyVelocity += diff / duration
bodyVelocity *= scrollFriction
rawLocation += bodyVelocity
location.add(bodyVelocity)
location.add(bodyVelocity * fixedDeltaTimeSeconds)

directionDiff = rawLocation - rawLocationPrevious
}
Expand All @@ -51,7 +58,7 @@ export function ScrollBody(
}

function settled(): boolean {
const diff = target.get() - location.get()
const diff = target.get() - offsetLocation.get()
return mathAbs(diff) < 0.001
}

Expand Down
Loading

0 comments on commit 6ed3a51

Please sign in to comment.