import * as scrawl from '../source/scrawl.js'
import { reportSpeed } from './utilities.js';import * as scrawl from '../source/scrawl.js'
import { reportSpeed } from './utilities.js';const canvas = scrawl.findCanvas('mycanvas');Namespacing boilerplate
const namespace = canvas.name;
const name = (n) => `${namespace}-${n}`;Import the initial image used by the Picture entity
scrawl.importDomImage('#bunny');
canvas.setBase({
backgroundColor: '#000040',
});Create entitys that we can use with the particle emitter
noXYZ flags to false in the hope that this will speed up stamp computation timesvisibility to false to prevent the entity appearing in the scene outside of the emitter’s controlfillStyle and strokeStyle colors - we’ll do that via the particle emitter and the world objectconst commonValues = {
handle: ['center', 'center'],
lineWidth: 2,
method: 'fillThenDraw',
visibility: false,
noUserInteraction: true,
noPositionDependencies: true,
noFilters: true,
noDeltaUpdates: true,
};
scrawl.makeWheel({
name: name('wheel'),
radius: 16,
startAngle: 20,
endAngle: -20,
includeCenter: true,
/** @ts-expect-error */
}).set(commonValues);
scrawl.makeBlock({
name: name('block'),
dimensions: [30, 20],
/** @ts-expect-error */
}).set(commonValues);
scrawl.makeStar({
name: name('star'),
radius1: 6,
radius2: 18,
points: 5,
/** @ts-expect-error */
}).set(commonValues);
scrawl.makePicture({
name: name('picture'),
asset: 'bunny',
width: 26,
height: 37,
copyWidth: '100%',
copyHeight: '100%',
/** @ts-expect-error */
}).set(commonValues);
scrawl.makeLabel({
name: name('label'),
text: 'Hello',
fontString: 'bold 40px Garamond, serif',
textIsAccessible: false,
/** @ts-expect-error */
}).set(commonValues);Create a World object which we can then assign to the particle emitter
const myWorld = scrawl.makeWorld({
name: name('world'),
tickMultiplier: 2,
userAttributes: [
{
key: 'strokeStyle',
defaultValue: '#b9b5df',
},
{
key: 'globalAlpha',
defaultValue: 0.2,
},
],
});Create the particle Emitter entity
const myEmitter = scrawl.makeEmitter({
name: name('emitter'),
world: myWorld,
start: ['center', 'center'],
historyLength: 20,
generationRate: 10,
killAfterTime: 5,As well as killing our particles beyond their lifetime limit (as set in killAfterTime) we can also kill particles that move a certain distance beyond their initial position
killRadius: 50,
killRadiusVariation: 0,Assign the artefact that we will be using for the particle animation
artefact: scrawl.findEntity(name('star')),
rangeX: 40,
rangeFromX: -20,
rangeY: 40,
rangeFromY: -20,
rangeZ: -1,
rangeFromZ: -0.2,Define the stampAction function
stampFirst: 'newest',
stampAction: function (artefact, particle, host) {We will use the (semi-)random fill color assigned to the particle when it was generated
const {history, fill} = particle;
let remaining, scale, roll, start, z;For the stroke color, we shall give all particles the same color, as defined in our World object
/** @ts-expect-error */
const {strokeStyle, globalAlpha} = myWorld;We will display each particle on the canvas using the entity currently assigned to our emitter’s artefact attribute
history.toReversed().forEach(p => {Entitys use Scrawl-canvas Coordinate arrays for their positioning data; we can set the start Coordinate using a normal Array containing [x, y] data - which we can easily extract from the particle’s history arrays like so:
[remaining, z, ...start] = p;We change the size of this instance of the particle based on it’s z coordinate value, by scaling the artefact.
scale = 1 + (z / 3);
if (scale > 0) {We can roll the artefact (for added drama), calculating the roll value based on the amount of time left before the particle gets killed.
roll = (remaining / 5) * 720;The artefact’s simpleStamp function bypasses much of the normal Display cycle functionality; instead it forces the artefact to stamp itself onto the host Cell’s <canvas> element immediately
artefact.simpleStamp(host, {
start,
scale,
roll,
globalAlpha,
strokeStyle,
fillStyle: fill,
});
}
});
},
});Function to display frames-per-second data, and other information relevant to the demo
const { particlenames, particle } = scrawl.library;
const report = reportSpeed('#reportmessage', function () {ParticleHistory arrays are not saved in the Scrawl-canvas library; instead we need to count them in each particle
let historyCount = 0;
particlenames.forEach(n => {
const p = particle[n];
if (p) historyCount += p.history.length;
});
const kr = parseFloat(dom.kill_radius.value),
krv = parseFloat(dom.kill_radius_variation.value) / 2;
return `
Particles: ${particlenames.length}, generationRate: ${dom.generationRate.value}, historyLength: ${dom.historyLength.value}
Stamps per display: ${historyCount}
Background color: ${dom.background.value}
Minimum fill color: ${dom.min_fill.value}
Maximum fill color: ${dom.max_fill.value}
Outline color (strokeStyle): ${dom.outline_color.value}
World speed (tickMultiplier): ${dom.world_speed.value}
Opacity (globalAlpha): ${dom.opacity.value}
Kill radius: from ${(kr - krv) > 0 ? kr - krv : 0}px to ${kr + krv}px`;
});
const mouseCheck = function () {
myEmitter.set({
lockTo: (canvas.here.active) ? 'mouse' : 'start',
});
};Create the Display cycle animation
scrawl.makeRender({
name: name('animation'),
target: canvas,
commence: mouseCheck,
afterShow: report,
});const dom = scrawl.initializeDomInputs([
['input', 'world_speed', '2'],
['input', 'max_fill', '#000000'],
['input', 'min_fill', '#ffffff'],
['input', 'background', '#000040'],
['input', 'outline_color', '#b9b5df'],
['input', 'historyLength', '20'],
['input', 'kill_radius', '50'],
['input', 'kill_radius_variation', '0'],
['input', 'opacity', '0.2'],
['input', 'generationRate', '10'],
['select', 'artefact', 0],
['select', 'artefact', 0],
]);For this demo we will suppress touchmove functionality over the canvas
scrawl.addNativeListener('touchmove', (e) => {
e.preventDefault();
e.returnValue = false;
}, canvas.domElement);Setting (restricting) the range of colors assigned to particles when they are generated.
/** @ts-expect-error */
const colorFactory = myEmitter.fillColorFactory;
const setLowColor = function (e) {
if (e && e.target) colorFactory.setMinimumColor(e.target.value);
};
scrawl.addNativeListener(['input', 'change'], setLowColor, dom.min_fill);
const setHighColor = function (e) {
if (e && e.target) colorFactory.setMaximumColor(e.target.value);
};
scrawl.addNativeListener(['input', 'change'], setHighColor, dom.max_fill);Change the artefact to be used in the animation
const useArtefact = function (e) {
if (e && e.target) {
const val = scrawl.findEntity(name(e.target.value));
if (val) myEmitter.set({ artefact: val });
}
};
scrawl.addNativeListener(['input', 'change'], useArtefact, dom.artefact);Setup other form observer functionality
scrawl.makeUpdater({
event: ['input', 'change'],
origin: '.controlItem',
target: myWorld,
useNativeListener: true,
preventDefault: true,
updates: {
'outline_color': ['strokeStyle', 'raw'],
'world_speed': ['tickMultiplier', 'float'],
'opacity': ['globalAlpha', 'float'],
},
});
scrawl.makeUpdater({
event: ['input', 'change'],
origin: '.controlItem',
target: myEmitter,
useNativeListener: true,
preventDefault: true,
updates: {
generationRate: ['generationRate', 'int'],
historyLength: ['historyLength', 'int'],
kill_radius: ['killRadius', 'int'],
kill_radius_variation: ['killRadiusVariation', 'int'],
stampFirst: ['stampFirst', 'raw'],
},
});
scrawl.makeUpdater({
event: ['input', 'change'],
origin: '.controlItem',
target: canvas,
useNativeListener: true,
preventDefault: true,
updates: {
background: ['backgroundColor', 'raw'],
},
});console.log(scrawl.library);