Skip to content

Commit

Permalink
Add all features except pointer interaction and easing.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjerleke committed Dec 22, 2023
1 parent 5b02530 commit b34a3b8
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 52 deletions.
126 changes: 107 additions & 19 deletions packages/embla-carousel-auto-scroll/src/components/AutoScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,28 @@ declare module 'embla-carousel/components/EventHandler' {
}
}

// TODO: Add stopOnFocusIn

export type AutoScrollType = CreatePluginType<{}, OptionsType>
// TODO: Fix pointerdown and up
// TODO: Add Ease in and out?

export type AutoScrollType = CreatePluginType<
{
play: (delay?: number) => void
stop: () => void
isPlaying: () => boolean
},
OptionsType
>

export type AutoScrollOptionsType = AutoScrollType['options']

function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {
let options: OptionsType
let emblaApi: EmblaCarouselType
let active = false
let playing = false
let wasPlaying = false
let timer = 0
let startDelay: number
let defaultScrollBehaviour: ScrollBodyType

function init(
Expand All @@ -41,37 +54,92 @@ function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {
const allOptions = mergeOptions(optionsBase, userOptions)
options = optionsAtMedia(allOptions)

console.log('hi!')
startDelay = options.delay
active = true
defaultScrollBehaviour = emblaApi.internalEngine().scrollBody

const { eventStore, ownerDocument } = emblaApi.internalEngine()
const emblaRoot = emblaApi.rootNode()
const root = (options.rootNode && options.rootNode(emblaRoot)) || emblaRoot

// const root = (options.rootNode && options.rootNode(emblaRoot)) || emblaRoot
emblaApi.on('pointerDown', stopScroll)
// if (!options.stopOnInteraction) emblaApi.on('settle', startScroll)

startScroll()
if (options.stopOnMouseEnter) {
eventStore.add(root, 'mouseenter', stopScroll)

if (!options.stopOnInteraction) {
eventStore.add(root, 'mouseleave', startScroll)
}
}

if (options.stopOnFocusIn) {
eventStore.add(root, 'focusin', stopScroll)

if (!options.stopOnInteraction) {
eventStore.add(root, 'focusout', startScroll)
}
}

eventStore.add(ownerDocument, 'visibilitychange', visibilityChange)

emblaApi.on('pointerDown', clearScroll)
emblaApi.on('scroll', (_, name) => console.log(name))
emblaApi.on('settle', (_, name) => console.log(name))
if (options.playOnInit) {
emblaApi.on('init', startScroll).on('reInit', startScroll)
}
}

function destroy(): void {}
function destroy(): void {
active = false
playing = false
emblaApi.off('init', startScroll).off('reInit', startScroll)
emblaApi.off('pointerDown', stopScroll)
// if (!options.stopOnInteraction) emblaApi.off('pointerUp', startTimer)
stopScroll()
}

function startScroll(): void {
if (!active || playing) return
emblaApi.emit('autoScroll:play')

const engine = emblaApi.internalEngine()
defaultScrollBehaviour = engine.scrollBody
engine.scrollBody = autoScrollBehaviour(engine)
engine.animation.start()
const { ownerWindow } = engine

timer = ownerWindow.setTimeout(() => {
engine.scrollBody = createAutoScrollBehaviour(engine)
engine.animation.start()
}, startDelay)

playing = true
}

function clearScroll(): void {
function stopScroll(): void {
if (!active || !playing) return
emblaApi.emit('autoScroll:stop')

const engine = emblaApi.internalEngine()
engine.animation.stop()
const { ownerWindow } = engine

engine.scrollBody = defaultScrollBehaviour
engine.animation.start()
ownerWindow.clearTimeout(timer)
timer = 0

playing = false
}

function visibilityChange(): void {
const { ownerDocument } = emblaApi.internalEngine()

if (ownerDocument.visibilityState === 'hidden') {
wasPlaying = playing
return stopScroll()
}

if (wasPlaying) startScroll()
}

function autoScrollBehaviour(engine: EngineType): ScrollBodyType {
function createAutoScrollBehaviour(engine: EngineType): ScrollBodyType {
const { location, target, scrollTarget, index, indexPrevious } = engine
const directionSign = options.direction === 'forward' ? -1 : 1
const noop = (): ScrollBodyType => self

let bodyVelocity = 0
Expand All @@ -82,7 +150,7 @@ function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {
function seek(): ScrollBodyType {
let directionDiff = 0

bodyVelocity = -2
bodyVelocity = directionSign
rawLocation += bodyVelocity
location.add(bodyVelocity)
target.set(location)
Expand Down Expand Up @@ -117,11 +185,31 @@ function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {
return self
}

function play(delayOverride?: number): void {
if (typeof delayOverride !== 'undefined') startDelay = delayOverride
startScroll()
}

function stop(): void {
if (playing) stopScroll()
}

// function reset(): void {
// if (playing) play()
// }

function isPlaying(): boolean {
return playing
}

const self: AutoScrollType = {
name: 'autoScroll',
options: userOptions,
init,
destroy
destroy,
play,
stop,
isPlaying
}
return self
}
Expand Down
12 changes: 12 additions & 0 deletions packages/embla-carousel-auto-scroll/src/components/Options.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { CreateOptionsType } from 'embla-carousel/components/Options'

export type OptionsType = CreateOptionsType<{
direction: 'forward' | 'backward'
speed: number
delay: number
playOnInit: boolean
stopOnFocusIn: boolean
stopOnInteraction: boolean
stopOnMouseEnter: boolean
rootNode: ((emblaRoot: HTMLElement) => HTMLElement | null) | null
}>

export const defaultOptions: OptionsType = {
direction: 'forward',
speed: 1,
delay: 1000,
active: true,
breakpoints: {},
playOnInit: true,
stopOnFocusIn: true,
stopOnInteraction: true,
stopOnMouseEnter: false,
rootNode: null
}
74 changes: 41 additions & 33 deletions packages/embla-carousel-autoplay/src/components/Autoplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type AutoplayOptionsType = AutoplayType['options']
function Autoplay(userOptions: AutoplayOptionsType = {}): AutoplayType {
let options: OptionsType
let emblaApi: EmblaCarouselType
let destroyed: boolean
let active = false
let playing = false
let wasPlaying = false
let jump = false
Expand All @@ -51,89 +51,80 @@ function Autoplay(userOptions: AutoplayOptionsType = {}): AutoplayType {
options = optionsAtMedia(allOptions)

jump = options.jump
destroyed = false
active = true

const { eventStore, ownerDocument } = emblaApi.internalEngine()
const emblaRoot = emblaApi.rootNode()
const root = (options.rootNode && options.rootNode(emblaRoot)) || emblaRoot

emblaApi.on('pointerDown', clearTimer)
emblaApi.on('pointerDown', stopTimer)
if (!options.stopOnInteraction) emblaApi.on('pointerUp', startTimer)

if (options.stopOnMouseEnter) {
eventStore.add(root, 'mouseenter', clearTimer)
eventStore.add(root, 'mouseenter', stopTimer)

if (!options.stopOnInteraction) {
eventStore.add(root, 'mouseleave', startTimer)
}
}

if (options.stopOnFocusIn) {
eventStore.add(root, 'focusin', clearTimer)
eventStore.add(root, 'focusin', stopTimer)

if (!options.stopOnInteraction) {
eventStore.add(root, 'focusout', startTimer)
}
}

eventStore.add(ownerDocument, 'visibilitychange', () => {
if (ownerDocument.visibilityState === 'hidden') {
wasPlaying = playing
return clearTimer()
}

if (wasPlaying) startTimer()
})
eventStore.add(ownerDocument, 'visibilitychange', visibilityChange)

if (options.playOnInit) {
emblaApi.on('init', startTimer).on('reInit', startTimer)
}
}

function destroy(): void {
destroyed = true
active = false
playing = false
emblaApi.off('init', startTimer).off('reInit', startTimer)
emblaApi.off('pointerDown', clearTimer)
emblaApi.off('pointerDown', stopTimer)
if (!options.stopOnInteraction) emblaApi.off('pointerUp', startTimer)
clearTimer()
stopTimer()
cancelAnimationFrame(animationFrame)
animationFrame = 0
}

function startTimer(): void {
if (destroyed) return
if (!active) return
if (!playing) emblaApi.emit('autoplay:play')

const { ownerWindow } = emblaApi.internalEngine()
ownerWindow.clearInterval(timer)
timer = ownerWindow.setInterval(next, options.delay)

playing = true
}

function clearTimer(): void {
if (destroyed) return
function stopTimer(): void {
if (!active) return
if (playing) emblaApi.emit('autoplay:stop')

const { ownerWindow } = emblaApi.internalEngine()
ownerWindow.clearInterval(timer)
timer = 0
playing = false
}

function play(jumpOverride?: boolean): void {
if (typeof jumpOverride !== 'undefined') jump = jumpOverride
startTimer()
playing = false
}

function stop(): void {
if (playing) clearTimer()
}
function visibilityChange(): void {
const { ownerDocument } = emblaApi.internalEngine()

function reset(): void {
if (playing) play()
}
if (ownerDocument.visibilityState === 'hidden') {
wasPlaying = playing
return stopTimer()
}

function isPlaying(): boolean {
return playing
if (wasPlaying) startTimer()
}

function next(): void {
Expand All @@ -143,7 +134,7 @@ function Autoplay(userOptions: AutoplayOptionsType = {}): AutoplayType {
const lastIndex = emblaApi.scrollSnapList().length - 1
const kill = options.stopOnLastSnap && nextIndex === lastIndex

if (kill) clearTimer()
if (kill) stopTimer()

if (emblaApi.canScrollNext()) {
emblaApi.scrollNext(jump)
Expand All @@ -153,6 +144,23 @@ function Autoplay(userOptions: AutoplayOptionsType = {}): AutoplayType {
})
}

function play(jumpOverride?: boolean): void {
if (typeof jumpOverride !== 'undefined') jump = jumpOverride
startTimer()
}

function stop(): void {
if (playing) stopTimer()
}

function reset(): void {
if (playing) play()
}

function isPlaying(): boolean {
return playing
}

const self: AutoplayType = {
name: 'autoplay',
options: userOptions,
Expand Down

0 comments on commit b34a3b8

Please sign in to comment.