q-import { wiki-common.qhtml }
wiki-shell {
sectionKey: "particle-emitter"
pageTitle: "Particle Emitter"
pageIntro: "particle-emitter is a native custom element registered by qhtml6.js. q-particle-emitter is an alias for the same emitter system. It creates a transparent canvas layer inside its parent and simulates particles without creating one DOM node per particle."
main {
wiki-example-card {
title: "1. Mental model"
summary: "Use particle-emitter when you want a visual particle effect from declarative QHTML attributes."
note: "The element is not a q-component. It is a framework-registered custom element, so it works in plain HTML and in QHTML-rendered markup. Use particle-emitter or q-particle-emitter interchangeably. Each emitter owns one canvas, one particle array, one seeded random generator, and one animation loop."
details {
html { <q-html>
div#field {
style {
position: relative;
width: 420px;
height: 220px;
overflow: hidden;
background: #07111f;
}
particle-emitter#energy {
running: "false"
src: "particle.png"
}
}
</q-html> }
}
}
wiki-example-card {
title: "2. Complete rising energy example"
summary: "This example starts particles at the bottom of the container, spreads them horizontally, and moves them upward with a long fade."
note: "The parent must have real dimensions. If the parent has static positioning, particle-emitter changes it to relative so the canvas can fill it."
details {
html { <q-html>
div#energy-field {
style {
position: relative;
width: 420px;
height: 220px;
overflow: hidden;
background: #07111f;
}
particle-emitter#energy-emitter {
emitRate: "84"
lifetime: "3600"
lifetimeVariation: "900"
x: "210"
y: "214"
xVariation: "145"
yVariation: "8"
xVelocity: "0.35"
yVelocity: "-1.25"
xVelocityVariation: "0.45"
yVelocityVariation: "0.4"
xAcceleration: "0.015"
yAcceleration: "-0.018"
xAccelerationVariation: "0.08"
yAccelerationVariation: "0.05"
startSize: "10"
endSize: "30"
startSizeVariation: "7"
endSizeVariation: "10"
startOpacity: "0.35"
endOpacity: "0.02"
startOpacityVariation: "0.16"
endOpacityVariation: "0.02"
maxActiveParticles: "96"
maxActiveParticlesVariation: "18"
running: "false"
interval: "18"
src: "particle.png"
}
}
button {
text { Start energy }
onclick { document.querySelector("#energy-emitter").running = true; }
}
button {
text { Stop energy }
onclick { document.querySelector("#energy-emitter").running = false; }
}
</q-html> }
}
}
wiki-example-card {
title: "3. Lifecycle and controls"
summary: "The emitter starts an internal requestAnimationFrame loop when connected. The loop keeps rendering, but it only creates new particles while running is true."
note: "Setting running to false or calling stop() stops emission but existing particles continue to age out. clear() immediately removes all current particles and resets total-created counters."
details {
html { <q-html>
particle-emitter#fx {
running: "false"
emitRate: "40"
}
button { text { Start } onclick { document.querySelector("#fx").running = true; } }
button { text { Stop } onclick { document.querySelector("#fx").running = false; } }
button { text { Clear } onclick { document.querySelector("#fx").clear(); } }
</q-html> }
}
}
wiki-example-card {
title: "4. Emission attributes"
summary: "These attributes decide how many particles are created and when emission stops."
note: "Attribute names may be written in camelCase in QHTML. The browser lowercases HTML attributes, and the emitter reads both forms."
details {
html { <q-html>
div#emission-demo {
style { position: relative; width: 360px; height: 180px; overflow: hidden; background: #07111f; }
particle-emitter#emission-emitter {
emitRate: "70"
lifetime: "1800"
lifetimeVariation: "250"
maxActiveParticles: "48"
maxActiveParticlesVariation: "6"
maxParticles: "0"
stopAfter: "0"
running: "true"
interval: "10"
seed: "1212"
zIndex: "2"
x: "180"
y: "176"
yVelocity: "-1.35"
xVariation: "130"
src: "particle.png"
color: "#67e8f9"
}
}
</q-html> }
}
}
wiki-example-card {
title: "5. Position and motion attributes"
summary: "Particles are born at x/y, then receive velocity and acceleration each frame."
note: "Use xVariation and yVariation for spawn spread. For rising energy, place y near the container bottom and make yVelocity negative."
details {
html { <q-html>
div#motion-demo {
style { position: relative; width: 360px; height: 180px; overflow: hidden; background: #0f172a; }
particle-emitter#motion-emitter {
x: "180"
y: "176"
xVariation: "150"
yVariation: "8"
xVelocity: "0.18"
yVelocity: "-1.45"
xVelocityVariation: "0.35"
yVelocityVariation: "0.16"
xAcceleration: "0.004"
yAcceleration: "-0.002"
xAccelerationVariation: "0.008"
yAccelerationVariation: "0.003"
emitRate: "64"
lifetime: "2200"
maxActiveParticles: "52"
running: "true"
interval: "10"
src: "particle.png"
color: "#a7f3d0"
}
}
</q-html> }
}
}
wiki-example-card {
title: "6. Size and opacity attributes"
summary: "Each particle interpolates from start values to end values over its lifetime."
note: "The renderer computes progress as age / lifetime. Size and opacity are then linearly interpolated from start to end."
details {
html { <q-html>
div#size-demo {
style { position: relative; width: 360px; height: 180px; overflow: hidden; background: #1e1238; }
particle-emitter#size-emitter {
startSize: "28"
endSize: "8"
startSizeVariation: "8"
endSizeVariation: "3"
startOpacity: "0.85"
endOpacity: "0.03"
startOpacityVariation: "0.08"
endOpacityVariation: "0.02"
x: "180"
y: "176"
xVariation: "135"
yVelocity: "-1.25"
emitRate: "72"
lifetime: "2000"
maxActiveParticles: "56"
running: "true"
interval: "10"
src: "particle.png"
color: "#c084fc"
}
}
</q-html> }
}
}
wiki-example-card {
title: "7. Sprite attributes"
summary: "Particles draw either a composed sprite canvas or a fallback colored circle."
note: "The mask is applied to each particle sprite's alpha, not to the whole emitter canvas. If src and mask are both provided, the mask alpha clips the source alpha. If only mask is provided, the mask becomes the particle shape. Changing mask at runtime reloads and recomposes the sprite. Images are loaded with crossOrigin=\"anonymous\"."
details {
html { <q-html>
div#sprite-demo {
style { position: relative; width: 360px; height: 180px; overflow: hidden; background: #330d12; }
particle-emitter#sprite-emitter {
src: "particle.png"
mask: "particle.png"
color: "#fb7185"
x: "180"
y: "176"
xVariation: "150"
yVelocity: "-1.35"
yVelocityVariation: "0.12"
startSize: "26"
endSize: "10"
startOpacity: "0.82"
endOpacity: "0.03"
emitRate: "68"
lifetime: "1900"
maxActiveParticles: "46"
running: "true"
interval: "10"
}
}
</q-html> }
}
}
wiki-example-card {
title: "8. Rendering details"
summary: "The emitter appends a canvas inside itself and sizes that canvas from the parent element."
note: "The canvas backing store is scaled by devicePixelRatio, while drawing coordinates stay in CSS pixels. This keeps the effect crisp on high-DPI screens."
details {
html { <q-html>
div#render-demo {
style { position: relative; width: 360px; height: 180px; overflow: hidden; background: #082915; }
particle-emitter#render-emitter {
emitRate: "60"
lifetime: "2100"
x: "180"
y: "176"
xVariation: "130"
yVelocity: "-1.3"
startSize: "24"
endSize: "9"
startOpacity: "0.8"
endOpacity: "0.03"
maxActiveParticles: "44"
running: "true"
interval: "10"
src: "particle.png"
color: "#86efac"
}
}
</q-html> }
}
}
}
}