A Svelte utility class for managing intervals with reactive durations. This package provides a convenient way to create and control setInterval
operations, allowing the interval's duration to be a static number or dynamically tied to Svelte's reactivity system.
Features:
$state
.Interval
class for managing setInterval
.clearInterval
when durations change or components unmountcurrent
or tickCount
is first accessed, optimizing resource usage (unless immediate: true
).The Interval
class allows you to create an interval that can have either a fixed duration or a duration that reacts to Svelte's $state
changes.
You can initialize Interval
with a static number for its duration:
<script>
import { Interval } from 'svelte-interval-rune';
const myInterval = new Interval(1000); // Interval runs every 1000ms (1 second)
</script>
<p>Current Time: {myInterval.current.toLocaleTimeString()}</p>
By default, intervals use lazy initialization and only start when current
or tickCount
is first accessed. However, you can create the interval immediately upon construction using the immediate
option:
<script>
import { Interval } from 'svelte-interval-rune';
// Lazy initialization (default) - interval starts when .current is accessed
const lazyTimer = new Interval(1000);
// Immediate initialization - interval starts right away
const immediateTimer = new Interval(1000, { immediate: true });
// The immediate timer is already running, even without accessing .current
console.log(immediateTimer.paused); // false - already running
console.log(lazyTimer.paused); // false - but not started yet
</script>
<p>Immediate Timer: {immediateTimer.current.toLocaleTimeString()}</p>
<p>Lazy Timer: {lazyTimer.current.toLocaleTimeString()}</p>
When the Interval
instance is destroyed, it will automatically clean up the underlying setInterval
and prevent memory leaks. In addition to this, this works quite well with using
keyword too:
<script>
import { Interval } from 'svelte-interval-rune';
using myInterval = new Interval(1000);
</script>
One of the key features of svelte-interval-rune
is its ability to react to changes in Svelte's $state
. You can pass a function to the Interval
constructor that returns a reactive value.
<script>
import { Interval } from 'svelte-interval-rune';
let multiplier = $state(1);
const reactiveInterval = new Interval(() => multiplier * 500);
reactiveInterval.current // Returns Date
</script>
<p>Current Time: {reactiveInterval.current.toLocaleTimeString()}</p>
<button onclick={() => multiplier++}>Increase Speed</button>
<p>Current Interval Duration: {reactiveInterval.duration}ms</p>
In this example, as multiplier
changes, the reactiveInterval
's duration automatically updates.
The Interval
class provides pause and resume functionality that allows you to temporarily stop the interval without losing track of how many times it has fired.
<script>
import { Interval } from 'svelte-interval-rune';
const timer = new Interval(1000);
function toggleActive() {
if (timer.isActive) {
timer.pause();
} else {
timer.resume();
}
}
function resumeImmediate() {
timer.resume(true); // Immediately triggers a tick and resets timing
}
</script>
<p>Current Time: {timer.current.toLocaleTimeString()}</p>
<p>Tick Count: {timer.tickCount}</p>
<p>Status: {timer.isActive ? 'Running' : 'Paused'}</p>
<button onclick={toggleActive}>
{timer.isActive ? 'Pause' : 'Resume'}
</button>
<button onclick={resumeImmediate}>Resume Immediately</button>
Important: When paused, the interval continues running in the background but stops executing callbacks and incrementing the tick count. When resumed, it picks up with the current interval cycle timing.
The tickCount
property tracks how many times the interval has fired. This count persists across pause/resume cycles and duration changes. Accessing tickCount
will automatically start the interval if it hasn't been started yet.
<script>
import { Interval } from 'svelte-interval-rune';
const counter = new Interval(500);
// Accessing tickCount starts the interval automatically
const tickInfo = $derived({
count: counter.tickCount, // This starts the interval
time: counter.current,
duration: counter.duration
});
</script>
<p>Ticks: {tickInfo.count}</p>
<p>Time: {tickInfo.time.toLocaleTimeString()}</p>
<p>Interval: {tickInfo.duration}ms</p>
current
and tickCount
GettersBoth the current
and tickCount
getters will automatically start the interval if it hasn't been created yet. This means you can access either property to begin interval execution:
current
getter:
setInterval
if it hasn't been created yet.Date
object representing the current time.tickCount
getter:
setInterval
if it hasn't been created yet.<script>
import { Interval } from 'svelte-interval-rune';
const clock = new Interval(1000);
// Either of these will start the interval:
const time = $derived(clock.current); // Starts interval and gets current time
// OR
const ticks = $derived(clock.tickCount); // Starts interval and gets tick count
</script>
The time is: {time.toLocaleTimeString()}
isActive
GetterThe isActive
getter returns the current active state of the interval:
<script>
import { Interval } from 'svelte-interval-rune';
const timer = new Interval(1000);
timer.current; // Start the interval
// Use in reactive context
const status = $derived(timer.isActive ? 'Running' : 'Paused');
</script>
<p>Timer Status: {status}</p>
<button onclick={() => timer.isActive ? timer.pause() : timer.resume()}>
Toggle
</button>
duration
Getter and SetterThe duration
getter returns the current effective duration of the interval. If the interval was initialized with a function, this getter will reflect the latest value returned by that function.
<script>
import { Interval } from 'svelte-interval-rune';
let dynamicDuration = $state(2000);
const myInterval = new Interval(() => dynamicDuration);
console.log(myInterval.duration); // 2000
dynamicDuration = 500;
console.log(myInterval.duration); // 500
</script>
You can also use the duration
setter to change the interval's duration after it has been created:
<script>
import { Interval } from 'svelte-interval-rune';
const myInterval = new Interval(1000); // Starts with 1000ms duration
// Change to a new static duration
myInterval.duration = 500; // Interval now runs every 500ms
</script>
duration
Setter and ReactivityWhen you initialize an Interval
with a function (e.g., new Interval(() => reactive_value)
), it creates a reactive link to that function's return value. However, if you later set the duration
using a number (e.g., myInterval.duration = 500
), this explicitly overwrites the previous reactive function. The interval will then operate with the new static number, and its connection to the original reactive state variable will be unattached.
To re-establish a reactive connection after setting a static duration, you must set the duration
again with a function:
<script>
import { Interval } from 'svelte-interval-rune';
let reactiveValue = $state(1000);
const myInterval = new Interval(() => reactiveValue);
// Interval is currently reactive to reactiveValue
console.log(myInterval.duration); // 1000
// Unattaches from reactiveValue and sets a static duration
myInterval.duration = 500;
console.log(myInterval.duration); // 500
reactiveValue = 2000;
console.log(myInterval.duration); // Still 500, not reactive to reactiveValue anymore
// Re-connects to reactiveValue
myInterval.duration = () => reactiveValue;
console.log(myInterval.duration); // 2000
</script>
<script>
import { Interval } from 'svelte-interval-rune';
let speed = $state(1000);
// Background timer that starts immediately
const backgroundClock = new Interval(() => speed, { immediate: true });
// User-controlled timer that starts on demand
const userClock = new Interval(100);
let userStarted = $state(false);
const timeData = $derived({
background: backgroundClock.current,
user: userStarted ? userClock.current : null,
backgroundTicks: backgroundClock.tickCount,
userTicks: userClock.tickCount
});
function startUserTimer() {
userStarted = true;
}
</script>
<div>
<h3>Background Clock (immediate): {timeData.background.toLocaleTimeString()}</h3>
<p>Ticks: {timeData.backgroundTicks} | Speed: {speed}ms</p>
<h3>User Clock: {timeData.user?.toLocaleTimeString() ?? 'Not started'}</h3>
<p>Ticks: {timeData.userTicks}</p>
{#if !userStarted}
<button onclick={startUserTimer}>Start User Timer</button>
{/if}
<button onclick={() => speed = speed === 1000 ? 100 : 1000}>
Toggle Background Speed
</button>
<button onclick={() => backgroundClock.isActive ? backgroundClock.pause() : backgroundClock.resume()}>
{backgroundClock.isActive ? 'Pause' : 'Resume'} Background
</button>
</div>
<script>
import { Interval } from 'svelte-interval-rune';
let duration = $state(1000);
using timer = new Interval(() => duration);
function reset() {
// Pause and recreate to reset tick count
timer.pause();
timer.duration = duration; // This recreates the interval
timer.resume(true); // Resume with immediate tick
}
</script>
<div>
<p>Time: {timer.current.toLocaleTimeString()}</p>
<p>Ticks: {timer.tickCount}</p>
<p>Duration: {duration}ms</p>
<input type="range" min="100" max="2000" step="100" bind:value={duration} />
<button onclick={() => timer.isActive ? timer.pause() : timer.resume()}>
{timer.isActive ? 'Pause' : 'Resume'}
</button>
<button onclick={reset}>Reset Timer</button>
</div>
new Interval(duration: number | (() => number), options?: IntervalOptions)
- Creates a new interval with the specified duration and optionsimmediate?: boolean
- If true
, creates and starts the interval immediately upon construction. Default: false
current: Date
- Gets current time and starts/subscribes to the interval (auto-starts interval)duration: number
- Gets or sets the interval duration in millisecondsisActive: boolean
- Gets the current active state of the intervaltickCount: number
- Gets the number of times the interval has fired (auto-starts interval)pause(): void
- Pauses the intervalresume(immediate?: boolean): void
- Resumes the interval, optionally with immediate tick[Symbol.dispose](): void
- Cleans up the interval (automatic with using
)Note: Both current
and tickCount
getters will automatically start the interval when first accessed (if not already started with immediate: true
), making initialization lazy and efficient by default.