bratik
Tiny, p5.js-like, typed toolkit to cakewalk through 2d canvas context.
see Demo page.
Installation
by npm:
npm i bratik
and then
import { getcanvas, ... } from "bratik"
or if you don't use npm you can import module from unpkg:
import { getcanvas, ... } from "https://unpkg.com/bratik@latest/dist/bratik.min.js"
or by script tag in your html page (this will create the global variable bratik
):
<script src="https://unpkg.com/bratik@latest/dist/iife/bratik.min.js"></script>
Usage
bratik
exports:
import
{
getcanvas, pxratio,
shape, vertex, arc, curve,
line, circle, ellipse, rect,
mask, clip,
font, settext, text,
LINEAR, CONIC, RADIAL, gradient,
fill, stroke, clear, bg,
frame, loop, stop, looping,
CLOSE, PI, TAU
}
from "bratik"
Canvas creation
it takes two optional args: width, height
getcanvas(100, 100)
// OR
const {
canvas, // DOM element with width and height of window
ctx, // CanvasRenderingContext2D
width, // width of window
height // height of window
} = getcanvas()
Pixel ratio, fill, stroke, clear, bg
get/set, default: client device pixel ratio
// get
const pr = pxratio()
// set
pxratio(2)
provide null as a color to fill/stroke to remove it.
stroke(
color: string | CanvasGradient | null,
width?: number,
lineCap?: string,
lineLoin?: string
)
fill(color: string | CanvasGradient | null)
clear()
cleans canvas, bg(color)
fill canvas with provided color.
Gradient
gradient()
takes type of gradient (LINEAR | CONIC | RADIAL)
tag as a first parameter, then other parameters need depending on type.
For the radial gradient you can pass only 3 parameters (x, y, r), 4 parameters (x, y, r1, r2) or 6 like shown below.
const sunset =
gradient(LINEAR, x1: number, y1: number, x2: number, y2: number) ||
gradient(CONIC, a: number, x: number, y: number) ||
gradient(RADIAL, x1: number, y1: number, r1: number, x2?: number, y2?: number, r2?: number)
returns an object
{
image: CanvasGradient,
// gradient itself which you can provide into fill,
// stroke or bg functions, like fill(sunset.image)
reset: (...options?: number[]) => void
// reset method to redefine gradient with options provided,
// as those passed in gradient function after the type tag
// or just reset it without options
add: (offset: number, color: string) => void
// after gradient initiation by type and options
// add colors by this method, offset should be from 0 to 1
}
Example
const sunrise = gradient(LINEAR, 0, 0, 0, height)
sunrise.add(0, "deepskyblue")
sunrise.add(0.75, "lightpink")
sunrise.add(1, "lightgoldenrodyellow")
bg(sunrise.image)
Looper
loop takes a callback to run every animation frame. Call stop to stop the loop.
let x = 20
const play = () => {
clear()
stroke(null)
fill("red")
circle(x++, height / 2, 10)
if (frame === 180) stop()
}
loop(play)
stop()
Animate
animate function takes an object of options:
{
dur: number, // in ms (default: 1000)
ease: string, // ease tag (default: "linear"),
loop: boolean, // default: false
// and callbacks:
onstart, ontick, onpause, onend
}
returns a dinamicly mutable on every animation frame object:
{
dur: number,
ease: string,
started: false,
paused: false,
ended: false,
frame: number, // frame number
time: number, // timestamp
t: number, // float number from 0 to 1 changing by ease function
// callbacks
onstart, ontick, onpause, onend
// methods
pause, play, on
}
Example of how to handle entire animation with ontick callback only:
const particle = { x: width / 2, y: height / 2 }
stroke(null)
animate({
ontick: () => {
const {t} = move, R = (1 - t) * 255, G = 0, B = t * 255
fill(`rgb(${R},${G},${B})`)
particle.y = height / 2 + Math.sin(t * TAU) * (height / 2 - 10)
particle.x = width / 2 + Math.cos(t * TAU) * (width / 2 - 10)
circle(particle.x, particle.y, 10)
}
}).play()
Example of animation with .on()
method combined with loop()
:
.on(target, props)
takes a target object (or array of objects) with props to animate "from" and an object (or array of objects) with props to animate "to" (gsap-like)
const
particle = { x: 10, y: 10 },
move = animate({ dur: 900 })
stroke(null)
fill("black")
move.on(particle, { x: width - 10, y: height - 10 })
loop(() => {
circle(particle.x, particle.y, 10)
if (
particle.x === width - 10 &&
particle.y === height - 10
) stop()
})
Shape, circle, ellipse, rect, line
first call shape to initiate it, then you may call vertex(x, y), arc(x1, y1, x2, y2, r) or curve(x1, y1, x2, y2, x3?, y3?), once shape is finished call it again, provide CLOSE tag as a parameter to close it if needed.
const
radius = 50,
sidesnum = 6,
angle = TAU / sidesnum,
offset = PI / 2,
vx = (i) => width / 2 + Math.cos(angle * i + offset) * radius,
vy = (i) => height / 2 + Math.sin(angle * i + offset) * radius
shape()
for (let i = 0; i < sidesnum; i++) {
i % 2 === 0
? vertex(vx(i), vy(i))
: arc(vx(i), vy(i), vx(i + 1), vy(i + 1), radius)
}
curve(width / 2 + 50, height / 2 + 75, width / 2, height / 2 + 50)
shape(CLOSE)
cirlce
takes: x, y of center and radius.
ellipse
takes: x, y of center, rx, ry radii; and optionaly: rotation, 'from' and 'to' of an arc in radians, and boolean if direction is counterclockwise.
rect
takes: x, y, width, height and optionally radius if you want rounded corners.
line
takes: x1, y1, x2, y2.
const
size = 20,
cx = width / 2,
cy = height / 2,
vx = (x, len) => x + Math.cos(frame * 0.01) * len,
vy = (y, len) => y + Math.sin(frame * 0.01) * len
stroke("black", 2)
fill("white")
loop(() => {
clear()
ellipse(cx, cy, size * 2, size * 1.333, Math.PI / 4)
rect(cx - size, cy - size, size * 2, size * 2, 5)
circle(cx, cy, size)
line(cx, cy, vx(cx, size), vy(cy, size))
})
Mask
You need to define mask shape in between mask()
and mask(CLOSE)
calls, it could be any number of — shape (and vertex, arc, curve), circle, ellipse or rect — calls. Then draw anything you like to clip by mask, after you done just call clip()
const hex = [ "FF", "00" ], { round, random } = Math
getcanvas(200)
const sun = gradient(RADIAL, 100, 100, 100 * Math.SQRT2)
const getcolor = () => Array(4).fill(0).map((_,i)=>i?hex[round(random())]:"#").join("")
// define mask
mask()
rect(50, 0, 100, 200, 40)
rect(0, 50, 200, 100, 40)
mask(CLOSE)
// drawing
stroke(null)
for (let i = 0; i < 4; i++) {
const x = i % 2 ? 100 : 0, y = i > 1 ? 100 : 0
sun.reset()
sun.add(0, getcolor())
sun.add(1, getcolor())
fill(sun.image)
rect(x, y, 100, 100, 10)
}
// clipping
clip()
you will get something like this:
Text
font takes size (number or string, to set line height provide it after slash like this "16/20"), family, and options like "bold". settext takes CanvasTextAlign, optionally CanvasTextBaseline and width. text takes string of content, x and y of its pivot and optionally width.
stroke(null)
fill("blue")
font(21, "monospace", "italic")
settext("center", "middle")
text("Hello world!", width / 2, height / 2)