Skip to content

Grid Pulse Background

A grid of dots with a pulse of light spreading out from the center.

Grid Pulse Background, playing
Download .swift
Origin
Original work
Found by
background · grid · dots · pulse · radar · ripple · animated · fondo · cuadrícula · puntos · pulso · radar · onda · animado

A grid of dots with a pulse of light spreading from the center.

With Reduce Motion on, it shows one still ring.

struct GridPulseBackgroundDemo: View {
var body: some View {
ZStack {
Color(red: 0.03, green: 0.05, blue: 0.1)
.ignoresSafeArea()
GridPulseBackground()
VStack(spacing: 10) {
Image(systemName: "dot.radiowaves.left.and.right")
.font(.largeTitle)
.accessibilityHidden(true)
Text("Signal")
.font(.system(.largeTitle, design: .rounded, weight: .bold))
}
.foregroundStyle(.white)
.padding(.horizontal, 28)
.padding(.vertical, 20)
.background(.ultraThinMaterial, in: .rect(cornerRadius: 24))
}
}
}
ParameterTypeDefaultDescription
colorColor.cyan
spacingDouble26
periodDouble2.6Seconds for the pulse to travel from the center to the corners.

Copy this code into your project, or download the file.

GridPulseBackground.swift
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
enum GridPulseBackgroundMath {
/// How many dots fit on each side of the center, or `nil` when `spacing` cannot lay out a grid.
static func dotsPerSide(extent: Double, spacing: Double) -> Int? {
guard spacing.isFinite, spacing > 0 else { return nil }
return Int(extent / 2 / spacing) + 1
}
}
/// A grid of dots with a pulse of light spreading from the center.
///
/// With Reduce Motion on, it shows one still ring.
struct GridPulseBackground: View {
var color: Color = .cyan
var spacing: Double = 26
/// Seconds for the pulse to travel from the center to the corners.
var period: Double = 2.6
@Environment(\.accessibilityReduceMotion) private var reduceMotion
private static let dotRadius = 2.0
private static let restingOpacity = 0.32
private static let bandWidth = 80.0
var body: some View {
TimelineView(.animation(paused: reduceMotion)) { timeline in
Canvas { context, size in
let cycle = timeline.date.timeIntervalSinceReferenceDate / period
let progress = reduceMotion ? 0.4 : cycle - floor(cycle)
draw(progress: progress, in: context, size: size)
}
}
.ignoresSafeArea()
.allowsHitTesting(false)
.accessibilityHidden(true)
}
private func draw(progress: Double, in context: GraphicsContext, size: CGSize) {
guard
let columns = GridPulseBackgroundMath.dotsPerSide(extent: size.width, spacing: spacing),
let rows = GridPulseBackgroundMath.dotsPerSide(extent: size.height, spacing: spacing)
else { return }
let center = CGPoint(x: size.width / 2, y: size.height / 2)
let front = progress * (hypot(size.width, size.height) / 2 + Self.bandWidth)
var resting = Path()
for row in -rows...rows {
for column in -columns...columns {
let dot = CGPoint(x: center.x + Double(column) * spacing, y: center.y + Double(row) * spacing)
let gap = (hypot(dot.x - center.x, dot.y - center.y) - front) / Self.bandWidth
let glow = exp(-gap * gap) * (1 - pow(progress, 4))
if glow < 0.02 {
resting.addEllipse(in: circle(at: dot, radius: Self.dotRadius))
} else {
context.fill(
Path(ellipseIn: circle(at: dot, radius: Self.dotRadius * (1 + glow * 1.6))),
with: .color(color.opacity(Self.restingOpacity + glow * (1 - Self.restingOpacity)))
)
}
}
}
context.fill(resting, with: .color(color.opacity(Self.restingOpacity)))
}
private func circle(at center: CGPoint, radius: Double) -> CGRect {
CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2)
}
}
// MARK: - Demo
struct GridPulseBackgroundDemo: View {
var body: some View {
ZStack {
Color(red: 0.03, green: 0.05, blue: 0.1)
.ignoresSafeArea()
GridPulseBackground()
VStack(spacing: 10) {
Image(systemName: "dot.radiowaves.left.and.right")
.font(.largeTitle)
.accessibilityHidden(true)
Text("Signal")
.font(.system(.largeTitle, design: .rounded, weight: .bold))
}
.foregroundStyle(.white)
.padding(.horizontal, 28)
.padding(.vertical, 20)
.background(.ultraThinMaterial, in: .rect(cornerRadius: 24))
}
}
}
#Preview {
GridPulseBackgroundDemo()
}