Back to list
Force
Use d3 force to move particles around. You can change the forces at any point, more info in the d3 docs.
Force.svelte
See on Github
<script>
import { forceSimulation } from "d3-force"
// utility function for translating elements
const move = (x, y) => `transform: translate(${x}px, ${y}px`
// an array of our particles
export let dots = []
// an array of [name, force] pairs
export let forces = []
let usedForceNames = []
let renderedDots = []
let width = 1200
$: height = width
$: simulation = forceSimulation()
.nodes(dots)
.on("tick", () => {
// update the renderedDots reference to trigger an update
renderedDots = [...dots]
})
$: {
// re-initialize forces when they change
forces.forEach(([name, force]) => {
simulation.force(name, force)
})
// remove old forces
const newForceNames = forces.map(([name]) => name)
let oldForceNames = usedForceNames.filter(
force => !newForceNames.includes(force),
)
oldForceNames.forEach(name => {
simulation.force(name, null)
})
usedForceNames = newForceNames
// kick our simulation into high gear
simulation.alpha(1)
simulation.restart()
}
</script>
<figure class="c" bind:clientWidth="{width}">
<svg {width} {height}>
{#each renderedDots as { x, y }, i}
<circle style="{move(x, y)}" r="{6}"></circle>
{/each}
</svg>
</figure>
<style>
figure {
margin: 0;
}
svg {
overflow: visible;
}
circle {
fill: #0b2830;
}
</style>
Usage example:
ForceWrapper.svelte
See on Github
<script>
import { forceX, forceY, forceCollide, forceRadial } from "d3-force"
import Force from "./Force.svelte"
let element
let centerPosition = [200, 200]
let useForceCollide = true
let useForceRadial = true
$: activeForceX = forceX().x(centerPosition[0])
$: activeForceY = forceY().y(centerPosition[1])
$: activeForceCollide = forceCollide()
.radius(10)
.iterations(3)
$: activeForceRadial = forceRadial()
.radius(150)
.x(centerPosition[0])
.y(centerPosition[1])
$: forces = [
["x", activeForceX],
["y", activeForceY],
useForceCollide && ["collide", activeForceCollide],
useForceRadial && ["radial", activeForceRadial],
].filter(d => d)
const numberOfDots = 100
let dots = new Array(numberOfDots).fill(0).map(_ => ({}))
const onClick = e => {
if (!element) return
const bounds = element.getBoundingClientRect()
const x = e.clientX - bounds.left
const y = e.clientY - bounds.top
centerPosition = [x, y]
}
</script>
<div class="controls">
<label>
<input type="checkbox" bind:checked="{useForceCollide}" />
Collide?
</label>
<label>
<input type="checkbox" bind:checked="{useForceRadial}" />
Radial?
</label>
</div>
<div class="note">Click around to update</div>
<div on:click="{onClick}" bind:this="{element}">
<Force {forces} {dots} />
</div>
<style>
.controls {
display: flex;
align-items: center;
position: absolute;
top: 0;
right: 0;
font-style: italic;
color: var(--text-light);
}
label + label {
margin-left: 1em;
}
.note {
position: absolute;
top: 0;
font-style: italic;
color: var(--text-light);
}
</style>
Click around to update