Giraffe.js - HTML5 Canvas Graphics Library Documentation
Comprehensive documentation for the OpenForum Giraffe.js HTML5 canvas graphics library - a Javascript library for building animated interactive graphics with JSON schema support.
Overview
Giraffe.js is a lightweight HTML5 canvas graphics library designed for creating animated and interactive graphics applications. Built by Nik Cross, it provides an object-oriented approach to canvas programming with support for graphics primitives, composites, animations, user interaction, and JSON schema-based scene definition.
Core Architecture
Canvas Management
The library centers around the `Canvas` class, which represents an HTML5 canvas element and manages all graphics objects drawn on it.
// Create a canvas instance
var myCanvas = new Canvas("canvasElementId");
// Add graphics objects
myCanvas.add(new Circle(100, 100, 50));
myCanvas.add(new Rectangle(200, 200, 100, 75));
// Repaint the canvas
myCanvas.repaint();
Graphics Objects Hierarchy
All drawable objects inherit from the `GraphicsObject` base class, providing consistent behavior for positioning, styling, interaction, and animation.
JSON Schema Support
Giraffe.js supports defining graphics scenes using JSON schemas, allowing for data-driven graphics creation and easy serialization/deserialization of complex scenes. For the complete JSON schema definition, see the
GiraffeJSSchema page.
JSON Scene Definition
var sceneData = {
"canvas": {
"id": "myCanvas",
"width": 800,
"height": 600,
"backgroundColor": "#f0f0f0"
},
"objects": [
{
"type": "Circle",
"id": "ball1",
"x": 100,
"y": 100,
"radius": 30,
"fillColor": "red",
"color": "darkred",
"animation": {
"type": "bounce",
"velocityX": 3,
"velocityY": 2
}
},
{
"type": "Rectangle",
"id": "paddle",
"x": 350,
"y": 550,
"width": 100,
"height": 20,
"fillColor": "brown",
"draggable": true
},
{
"type": "Text",
"id": "score",
"x": 10,
"y": 30,
"text": "Score: 0",
"fontSize": 24,
"font": "Arial",
"color": "black"
}
]
}
JSON Schema Loader
function loadSceneFromJSON(sceneData, canvas) {
var objects = {};
// Process each object in the scene
sceneData.objects.forEach(function(objData) {
var obj = createObjectFromJSON(objData);
if (obj) {
objects[objData.id] = obj;
canvas.add(obj);
}
});
return objects;
}
function createObjectFromJSON(objData) {
var obj = null;
switch(objData.type) {
case "Circle":
obj = new Circle(objData.x, objData.y, objData.radius);
break;
case "Rectangle":
obj = new Rectangle(objData.x, objData.y, objData.width, objData.height);
break;
case "Text":
obj = new Text(objData.x, objData.y, objData.text, objData.fontSize, objData.font);
break;
// Add more types as needed
}
if (obj) {
// Apply common properties
if (objData.fillColor) obj.setFillColor(objData.fillColor);
if (objData.color) obj.setColor(objData.color);
if (objData.visible !== undefined) obj.visible = objData.visible;
if (objData.rotation) obj.setRotation(objData.rotation);
// Apply animation if specified
if (objData.animation) {
applyAnimation(obj, objData.animation);
}
// Apply interactivity
if (objData.draggable) {
canvas.makeDraggable(obj);
}
}
return obj;
}
JSON Export/Serialization
function exportSceneToJSON(canvas) {
var sceneData = {
canvas: {
width: canvas.width,
height: canvas.height
},
objects: []
};
canvas.graphicsObjects.forEach(function(obj, index) {
var objData = serializeObject(obj, "object_" + index);
sceneData.objects.push(objData);
});
return sceneData;
}
function serializeObject(obj) {
var data = {
x: obj.x,
y: obj.y,
color: obj.color,
fillColor: obj.fillColor,
visible: obj.visible,
rotation: obj.rotation
};
// Type-specific serialization
if (obj instanceof Circle) {
data.type = "Circle";
data.radius = obj.radius;
} else if (obj instanceof Rectangle) {
data.type = "Rectangle";
data.width = obj.width;
data.height = obj.height;
} else if (obj instanceof Text) {
data.type = "Text";
data.text = obj.text;
data.fontSize = obj.textSize;
data.font = obj.font;
}
return data;
}
Core Classes
Canvas
The main container for all graphics operations.
- Constructor**: `Canvas(canvasElementId)`
- `canvasElementId`: String - DOM element ID of the canvas
- `add(graphicsObject)` - Adds a graphics object to the canvas
- `remove(graphicsObject)` - Removes a graphics object from the canvas
- `repaint()` - Clears and redraws all visible graphics objects
- `scale(scaleX, scaleY)` - Sets scaling factors for all graphics
- `stretchToFitWindow()` - Automatically scales canvas to fit window while maintaining aspect ratio
- `width`, `height` - Canvas dimensions
- `scaleX`, `scaleY` - Current scaling factors
- `graphicsObjects` - Array of all graphics objects
GraphicsObject (Base Class)
The foundation class for all drawable objects.
- Constructor**: `GraphicsObject(x, y)`
- `x`, `y`: Number - Position coordinates
- `x`, `y` - Position coordinates
- `rotation` - Rotation in radians
- `color` - Border/stroke color
- `fillColor` - Fill color
- `scaleX`, `scaleY` - Object scaling factors
- `visible` - Boolean visibility flag
- `shadow` - Shadow configuration object
- `lineStyle` - Line style configuration
- `setColor(color)` - Sets stroke color
- `setFillColor(fillColor)` - Sets fill color
- `setRotation(rotation)` - Sets rotation in radians
- `setCSSClass(className)` - Applies CSS styles
- `setShadow(shadow)` - Applies shadow effects
- `setLineStyle(lineStyle)` - Sets line styling
- Event Handlers** (override these):
- `onClick(x, y)` - Mouse click handler
- `onMouseOver(x, y)` - Mouse enter handler
- `onMouseOut(x, y)` - Mouse leave handler
- `onMousePressed(x, y)` - Mouse press handler
- `onMouseReleased(x, y)` - Mouse release handler
- `animate(frame)` - Animation frame handler
- `isInside(x, y)` - Hit testing method
- `draw()` - Rendering method
Graphics Primitives
Circle
Draws circular shapes with customizable radius.
- Constructor**: `Circle(x, y, radius)`
- `radius`: Number - Circle radius (must be > 0)
var circle = new Circle(100, 100, 50)
.setColor("blue")
.setFillColor("lightblue");
canvas.add(circle);
{
"type": "Circle",
"x": 100,
"y": 100,
"radius": 50,
"fillColor": "lightblue",
"color": "blue"
}
Rectangle
Draws rectangular shapes with specified dimensions.- Constructor**: `Rectangle(x, y, width, height)`
var rect = new Rectangle(50, 50, 200, 100)
.setColor("red")
.setFillColor("pink");
canvas.add(rect);
{
"type": "Rectangle",
"x": 50,
"y": 50,
"width": 200,
"height": 100,
"fillColor": "pink",
"color": "red"
}
RoundedRectangle
Draws rectangles with rounded corners.- Constructor**: `RoundedRectangle(x, y, width, height, radius)`
- `radius`: Number - Corner radius
var roundedRect = new RoundedRectangle(10, 10, 150, 80, 15)
.setFillColor("orange");
canvas.add(roundedRect);
{
"type": "RoundedRectangle",
"x": 10,
"y": 10,
"width": 150,
"height": 80,
"cornerRadius": 15,
"fillColor": "orange"
}
Line
Draws straight lines between two points.- Constructor**: `Line(x, y, x2, y2)`
- `x2`, `y2`: Number - End point coordinates
var line = new Line(0, 0, 200, 150)
.setColor("green");
canvas.add(line);
{
"type": "Line",
"x": 0,
"y": 0,
"x2": 200,
"y2": 150,
"color": "green"
}
Arc
Draws circular arcs and pie segments.- Constructor**: `Arc(x, y, startAngle, sweepAngle, radius)`
- `startAngle`: Number - Starting angle in degrees
- `sweepAngle`: Number - Arc sweep in degrees
- `radius`: Number - Arc radius
- `setClosed()` - Creates a pie segment (closed arc)
- `setOpen()` - Creates an open arc
var arc = new Arc(100, 100, 0, 90, 50)
.setClosed()
.setFillColor("yellow");
canvas.add(arc);
{
"type": "Arc",
"x": 100,
"y": 100,
"startAngle": 0,
"sweepAngle": 90,
"radius": 50,
"closed": true,
"fillColor": "yellow"
}
Text
Renders text with specified font and size.- Constructor**: `Text(x, y, text, textSize, font)`
- `text`: String - Text to display
- `textSize`: Number - Font size in pixels
- `font`: String - Font family name
var text = new Text(50, 100, "Hello Giraffe!", 24, "Arial")
.setColor("blue");
canvas.add(text);
{
"type": "Text",
"x": 50,
"y": 100,
"text": "Hello Giraffe!",
"fontSize": 24,
"font": "Arial",
"color": "blue"
}
Picture
Displays images from various sources.- Constructor**: `Picture(x, y, src)`
- `src`: String - Image URL or data URI
var image = new Picture(10, 10, "/images/logo.png");
canvas.add(image);
{
"type": "Picture",
"x": 10,
"y": 10,
"src": "/images/logo.png",
"width": 100,
"height": 100
}
Polygon
Creates custom shapes from a series of connected points.- Constructor**: `Polygon(x, y)`
- `smooth` - Boolean: Enable curved connections
- `closed` - Boolean: Close the polygon path
- `addPoint(px, py)` - Adds a point to the polygon
var triangle = new Polygon(100, 100)
.addPoint(0, -50)
.addPoint(-50, 50)
.addPoint(50, 50)
.setFillColor("green");
canvas.add(triangle);
{
"type": "Polygon",
"x": 100,
"y": 100,
"points": [
[0, -50],
[-50, 50],
[50, 50]
],
"smooth": false,
"closed": true,
"fillColor": "green"
}
PixelCanvas
Provides pixel-level drawing capabilities for detailed graphics.- Constructor**: `PixelCanvas(x, y, width, height)`
- `setPixel(x, y, r, g, b, a)` - Sets individual pixel color
- Alternative: `setPixel(x, y, colorObject)` - Sets pixel using color object
var pixelCanvas = new PixelCanvas(0, 0, 100, 100);
for (var x = 0; x < 100; x++) {
for (var y = 0; y < 100; y++) {
pixelCanvas.setPixel(x, y, x * 2.55, y * 2.55, 128, 255);
}
}
canvas.add(pixelCanvas);
{
"type": "PixelCanvas",
"x": 0,
"y": 0,
"width": 100,
"height": 100,
"pixelData": "base64EncodedImageData"
}
Advanced Features
Composite Objects
Group multiple graphics objects for coordinated manipulation.- Constructor**: `Composite(x, y, rotation)`
- `masked` - Boolean: Use first object as clipping mask
- `add(part)` - Adds a child object
- `remove(part)` - Removes a child object
- `deconstruct()` - Moves all parts to parent canvas
var group = new Composite(200, 200, 0);
group.add(new Circle(0, 0, 30).setFillColor("red"));
group.add(new Circle(20, 0, 20).setFillColor("blue"));
group.add(new Text(0, 50, "Group", 16, "Arial"));
canvas.add(group);
// Handle clicks on the entire group
group.onClick = function(x, y) {
alert("Clicked on composite!");
};
{
"type": "Composite",
"id": "myGroup",
"x": 200,
"y": 200,
"rotation": 0,
"masked": false,
"parts": [
{
"type": "Circle",
"x": 0,
"y": 0,
"radius": 30,
"fillColor": "red"
},
{
"type": "Circle",
"x": 20,
"y": 0,
"radius": 20,
"fillColor": "blue"
}
]
}
Styling and Effects
Shadow Effects
var shadow = new Shadow();
shadow.color = "#333";
shadow.blur = 10;
shadow.offsetX = 5;
shadow.offsetY = 5;
var circle = new Circle(100, 100, 50)
.setShadow(shadow)
.setFillColor("yellow");
{
"type": "Circle",
"x": 100,
"y": 100,
"radius": 50,
"fillColor": "yellow",
"shadow": {
"color": "#333",
"blur": 10,
"offsetX": 5,
"offsetY": 5
}
}
Line Styles
var lineStyle = new LineStyle()
.setThickness(3)
.setEndCap("round");
var line = new Line(0, 0, 200, 100)
.setLineStyle(lineStyle)
.setColor("blue");
{
"type": "Line",
"x": 0,
"y": 0,
"x2": 200,
"y2": 100,
"color": "blue",
"lineStyle": {
"thickness": 3,
"endCap": "round"
}
}
Gradient Colors
var radialGrad = new RadialColor(canvas, "white", "blue", 0, 0, 50);
var circle = new Circle(100, 100, 50)
.setFillColor(radialGrad.getColor());
{
"type": "Circle",
"x": 100,
"y": 100,
"radius": 50,
"fillColor": {
"type": "radialGradient",
"colors": ["white", "blue"],
"centerX": 0,
"centerY": 0,
"radius": 50
}
}
var linearGrad = new GradientColor(canvas, "red", "yellow", 0, 0, 100, 0);
var rect = new Rectangle(50, 50, 100, 50)
.setFillColor(linearGrad.getColor());
{
"type": "Rectangle",
"x": 50,
"y": 50,
"width": 100,
"height": 50,
"fillColor": {
"type": "linearGradient",
"colors": ["red", "yellow"],
"x1": 0,
"y1": 0,
"x2": 100,
"y2": 0
}
}
Animation System
Enable smooth animations with frame-based updates.
Basic Animation Setup
// Enable animation capabilities
Giraffe.setAnimated(myCanvas);
// Start animation: 30 FPS, infinite frames, looped
myCanvas.startAnimation(30, 1000, true);
// Stop animation
myCanvas.stopAnimation();
Animation Listeners
var animationController = {
processFrame: function(frameNumber) {
// Custom animation logic
console.log("Frame: " + frameNumber);
}
};
myCanvas.addAnimationListener(animationController);
Object Animation
Override the `animate` method on any graphics object:
var bouncingBall = new Circle(50, 50, 25)
.setFillColor("red");
bouncingBall.vx = 3; // Velocity X
bouncingBall.vy = 2; // Velocity Y
bouncingBall.animate = function(frame) {
this.x += this.vx;
this.y += this.vy;
// Bounce off edges
if (this.x < 25 || this.x > canvas.width - 25) this.vx *= -1;
if (this.y < 25 || this.y > canvas.height - 25) this.vy *= -1;
};
canvas.add(bouncingBall);
- JSON Schema for Animation**:
{
"type": "Circle",
"id": "bouncingBall",
"x": 50,
"y": 50,
"radius": 25,
"fillColor": "red",
"animation": {
"type": "custom",
"velocityX": 3,
"velocityY": 2,
"bounceEdges": true
}
}
Transition Effects
Pre-built animation sequences for common effects.
// Flip out on X axis over 30 frames
var flipOut = new Giraffe.FlipOutX(myObject, 30);
flipOut.start();
// Chain animations
flipOut.doNext = function() {
var flipIn = new Giraffe.FlipInX(myObject, 30);
flipIn.start();
};
- JSON Schema for Transitions**:
{
"type": "Circle",
"id": "animatedCircle",
"x": 100,
"y": 100,
"radius": 30,
"transitions": [
{
"type": "flipOutX",
"frames": 30,
"delay": 0
},
{
"type": "flipInX",
"frames": 30,
"delay": 30
}
]
}
// Move object following velocity vectors
myObject.vx = 5;
myObject.vy = -2;
var moveSeq = new Giraffe.MoveSequence(myObject, 60);
moveSeq.start();
// Rotate 10 degrees per frame for 36 frames (full rotation)
var rotateSeq = new Giraffe.RotationSequence(myObject, 36, 10);
rotateSeq.start();
// Explode a composite into its parts
var explode = new Giraffe.ExplodeSequence(myComposite, 120);
explode.start();
Interactive Features
Mouse Interaction Setup
// Enable interactivity
Giraffe.Interactive.setInteractive(myCanvas);
// Canvas-level event handlers
myCanvas.onMousePressed = function(x, y) {
console.log("Canvas clicked at: " + x + "," + y);
};
myCanvas.onMouseReleased = function(x, y) {
console.log("Mouse released at: " + x + "," + y);
};
myCanvas.onMouseOver = function(x, y) {
// Mouse movement over canvas
};
Object Event Handling
var button = new RoundedRectangle(50, 50, 100, 40, 5)
.setFillColor("lightblue")
.setColor("blue");
button.onClick = function(x, y) {
this.setFillColor("yellow");
canvas.repaint();
setTimeout(() => {
this.setFillColor("lightblue");
canvas.repaint();
}, 200);
};
button.onMouseOver = function(x, y) {
this.setFillColor("lightgreen");
canvas.repaint();
};
button.onMouseOut = function(x, y) {
this.setFillColor("lightblue");
canvas.repaint();
};
canvas.add(button);
- JSON Schema for Interactivity**:
{
"type": "RoundedRectangle",
"id": "interactiveButton",
"x": 50,
"y": 50,
"width": 100,
"height": 40,
"cornerRadius": 5,
"fillColor": "lightblue",
"color": "blue",
"interactive": {
"onClick": {
"action": "changeColor",
"color": "yellow",
"duration": 200
},
"onMouseOver": {
"action": "changeColor",
"color": "lightgreen"
},
"onMouseOut": {
"action": "changeColor",
"color": "lightblue"
}
}
}
Drag and Drop
// Make objects draggable
var draggableBox = new Rectangle(100, 100, 60, 60)
.setFillColor("orange");
canvas.add(draggableBox);
canvas.makeDraggable(draggableBox);
// Create drop target
var dropZone = new Rectangle(300, 300, 100, 100)
.setFillColor("lightgray")
.setColor("black");
dropZone.onCatch = function(droppedObject, x, y) {
alert("Object dropped in zone!");
droppedObject.setFillColor("green");
canvas.repaint();
};
canvas.add(dropZone);
- JSON Schema for Drag and Drop**:
{
"objects": [
{
"type": "Rectangle",
"id": "draggableBox",
"x": 100,
"y": 100,
"width": 60,
"height": 60,
"fillColor": "orange",
"draggable": true
},
{
"type": "Rectangle",
"id": "dropZone",
"x": 300,
"y": 300,
"width": 100,
"height": 100,
"fillColor": "lightgray",
"color": "black",
"dropTarget": true,
"onDrop": {
"action": "changeDroppedObjectColor",
"color": "green"
}
}
]
}
Behavior Utilities
// Reveal on hover behavior
function setReveal(trigger, target) {
trigger.onMouseOver = function(x, y) {
target.visible = true;
};
trigger.onMouseOut = function(x, y) {
target.visible = false;
};
target.visible = false;
}
// Usage
var hoverArea = new Rectangle(50, 50, 100, 50).setFillColor("blue");
var tooltip = new Text(160, 75, "Hidden tooltip!", 14, "Arial");
setReveal(hoverArea, tooltip);
- JSON Schema for Behaviors**:
{
"behaviors": [
{
"type": "reveal",
"trigger": "hoverArea",
"target": "tooltip"
}
],
"objects": [
{
"type": "Rectangle",
"id": "hoverArea",
"x": 50,
"y": 50,
"width": 100,
"height": 50,
"fillColor": "blue"
},
{
"type": "Text",
"id": "tooltip",
"x": 160,
"y": 75,
"text": "Hidden tooltip!",
"fontSize": 14,
"font": "Arial",
"visible": false
}
]
}
JSON Schema Processing Examples
Complete Scene Loader
function GiraffeJSONLoader() {
this.loadScene = function(jsonData, canvas) {
var scene = JSON.parse(jsonData);
var objects = {};
// Set canvas properties
if (scene.canvas) {
if (scene.canvas.backgroundColor) {
canvas.canvasElement.style.backgroundColor = scene.canvas.backgroundColor;
}
}
// Load objects
if (scene.objects) {
scene.objects.forEach(function(objData) {
var obj = this.createObject(objData, canvas);
if (obj && objData.id) {
objects[objData.id] = obj;
canvas.add(obj);
}
}.bind(this));
}
// Apply behaviors
if (scene.behaviors) {
this.applyBehaviors(scene.behaviors, objects);
}
return objects;
};
this.createObject = function(objData, canvas) {
var obj = null;
switch(objData.type) {
case "Circle":
obj = new Circle(objData.x, objData.y, objData.radius);
break;
case "Rectangle":
obj = new Rectangle(objData.x, objData.y, objData.width, objData.height);
break;
case "RoundedRectangle":
obj = new RoundedRectangle(objData.x, objData.y, objData.width, objData.height, objData.cornerRadius);
break;
case "Line":
obj = new Line(objData.x, objData.y, objData.x2, objData.y2);
break;
case "Arc":
obj = new Arc(objData.x, objData.y, objData.startAngle, objData.sweepAngle, objData.radius);
if (objData.closed) obj.setClosed();
break;
case "Text":
obj = new Text(objData.x, objData.y, objData.text, objData.fontSize, objData.font);
break;
case "Picture":
obj = new Picture(objData.x, objData.y, objData.src);
break;
case "Polygon":
obj = new Polygon(objData.x, objData.y);
if (objData.points) {
objData.points.forEach(function(point) {
obj.addPoint(point[0], point[1]);
});
}
if (objData.smooth !== undefined) obj.smooth = objData.smooth;
if (objData.closed !== undefined) obj.closed = objData.closed;
break;
case "Composite":
obj = new Composite(objData.x, objData.y, objData.rotation || 0);
if (objData.masked !== undefined) obj.masked = objData.masked;
if (objData.parts) {
objData.parts.forEach(function(partData) {
var part = this.createObject(partData, canvas);
if (part) obj.add(part);
}.bind(this));
}
break;
}
if (obj) {
this.applyCommonProperties(obj, objData, canvas);
}
return obj;
};
this.applyCommonProperties = function(obj, objData, canvas) {
if (objData.fillColor) {
if (typeof objData.fillColor === 'string') {
obj.setFillColor(objData.fillColor);
} else if (objData.fillColor.type === 'radialGradient') {
var grad = new RadialColor(canvas, objData.fillColor.colors[0], objData.fillColor.colors[1],
objData.fillColor.centerX, objData.fillColor.centerY, objData.fillColor.radius);
obj.setFillColor(grad.getColor());
} else if (objData.fillColor.type === 'linearGradient') {
var grad = new GradientColor(canvas, objData.fillColor.colors[0], objData.fillColor.colors[1],
objData.fillColor.x1, objData.fillColor.y1, objData.fillColor.x2, objData.fillColor.y2);
obj.setFillColor(grad.getColor());
}
}
if (objData.color) obj.setColor(objData.color);
if (objData.visible !== undefined) obj.visible = objData.visible;
if (objData.rotation) obj.setRotation(objData.rotation);
// Apply shadow
if (objData.shadow) {
var shadow = new Shadow();
shadow.color = objData.shadow.color;
shadow.blur = objData.shadow.blur;
shadow.offsetX = objData.shadow.offsetX;
shadow.offsetY = objData.shadow.offsetY;
obj.setShadow(shadow);
}
// Apply line style
if (objData.lineStyle) {
var lineStyle = new LineStyle()
.setThickness(objData.lineStyle.thickness)
.setEndCap(objData.lineStyle.endCap);
obj.setLineStyle(lineStyle);
}
// Apply interactivity
if (objData.interactive) {
this.applyInteractivity(obj, objData.interactive, canvas);
}
// Apply animation
if (objData.animation) {
this.applyAnimation(obj, objData.animation, canvas);
}
// Apply transitions
if (objData.transitions) {
this.applyTransitions(obj, objData.transitions);
}
// Make draggable if specified
if (objData.draggable) {
canvas.makeDraggable(obj);
}
};
this.applyInteractivity = function(obj, interactiveData, canvas) {
if (interactiveData.onClick) {
obj.onClick = function(x, y) {
this.handleInteraction(obj, interactiveData.onClick, canvas);
}.bind(this);
}
if (interactiveData.onMouseOver) {
obj.onMouseOver = function(x, y) {
this.handleInteraction(obj, interactiveData.onMouseOver, canvas);
}.bind(this);
}
if (interactiveData.onMouseOut) {
obj.onMouseOut = function(x, y) {
this.handleInteraction(obj, interactiveData.onMouseOut, canvas);
}.bind(this);
}
};
this.handleInteraction = function(obj, actionData, canvas) {
switch(actionData.action) {
case "changeColor":
var originalColor = obj.fillColor;
obj.setFillColor(actionData.color);
canvas.repaint();
if (actionData.duration) {
setTimeout(function() {
obj.setFillColor(originalColor);
canvas.repaint();
}, actionData.duration);
}
break;
case "toggleVisibility":
obj.visible = !obj.visible;
canvas.repaint();
break;
case "playSound":
if (actionData.soundUrl) {
var audio = new Audio(actionData.soundUrl);
audio.play();
}
break;
}
};
this.applyAnimation = function(obj, animationData, canvas) {
switch(animationData.type) {
case "bounce":
obj.vx = animationData.velocityX || 0;
obj.vy = animationData.velocityY || 0;
obj.animate = function(frame) {
this.x += this.vx;
this.y += this.vy;
if (animationData.bounceEdges) {
if (this.x < 0 || this.x > canvas.width) this.vx *= -1;
if (this.y < 0 || this.y > canvas.height) this.vy *= -1;
}
};
break;
case "rotate":
obj.rotationSpeed = animationData.speed || 0.1;
obj.animate = function(frame) {
this.setRotation(this.rotation + this.rotationSpeed);
};
break;
case "scale":
obj.scaleSpeed = animationData.speed || 0.01;
obj.scaleDirection = 1;
obj.animate = function(frame) {
this.scaleX += this.scaleSpeed * this.scaleDirection;
this.scaleY += this.scaleSpeed * this.scaleDirection;
if (this.scaleX > 2 || this.scaleX < 0.5) {
this.scaleDirection *= -1;
}
};
break;
}
};
this.applyTransitions = function(obj, transitionsData) {
transitionsData.forEach(function(transitionData) {
setTimeout(function() {
switch(transitionData.type) {
case "flipOutX":
var transition = new Giraffe.FlipOutX(obj, transitionData.frames);
transition.start();
break;
case "flipInX":
var transition = new Giraffe.FlipInX(obj, transitionData.frames);
transition.start();
break;
case "flipOutY":
var transition = new Giraffe.FlipOutY(obj, transitionData.frames);
transition.start();
break;
case "flipInY":
var transition = new Giraffe.FlipInY(obj, transitionData.frames);
transition.start();
break;
}
}, transitionData.delay || 0);
});
};
this.applyBehaviors = function(behaviors, objects) {
behaviors.forEach(function(behavior) {
switch(behavior.type) {
case "reveal":
var trigger = objects[behavior.trigger];
var target = objects[behavior.target];
if (trigger && target) {
setReveal(trigger, target);
}
break;
}
});
};
}
// Usage example
var loader = new GiraffeJSONLoader();
var canvas = new Canvas("myCanvas");
Giraffe.Interactive.setInteractive(canvas);
Giraffe.setAnimated(canvas);
var sceneObjects = loader.loadScene(jsonSceneData, canvas);
canvas.startAnimation(30, 1000, true);
Scene Export/Save
function exportSceneToJSON(canvas, sceneObjects) {
var sceneData = {
canvas: {
width: canvas.width,
height: canvas.height,
backgroundColor: canvas.canvasElement.style.backgroundColor || "#ffffff"
},
objects: [],
behaviors: []
};
// Export all objects
for (var id in sceneObjects) {
var objData = this.serializeObject(sceneObjects[id], id);
sceneData.objects.push(objData);
}
return JSON.stringify(sceneData, null, 2);
}
function serializeObject(obj, id) {
var data = {
id: id,
x: obj.x,
y: obj.y,
color: obj.color,
fillColor: obj.fillColor,
visible: obj.visible,
rotation: obj.rotation
};
// Type-specific serialization
if (obj instanceof Circle) {
data.type = "Circle";
data.radius = obj.radius;
} else if (obj instanceof Rectangle) {
data.type = "Rectangle";
data.width = obj.width;
data.height = obj.height;
} else if (obj instanceof RoundedRectangle) {
data.type = "RoundedRectangle";
data.width = obj.width;
data.height = obj.height;
data.cornerRadius = obj.radius;
} else if (obj instanceof Text) {
data.type = "Text";
data.text = obj.text;
data.fontSize = obj.textSize;
data.font = obj.font;
} else if (obj instanceof Line) {
data.type = "Line";
data.x2 = obj.x2;
data.y2 = obj.y2;
} else if (obj instanceof Arc) {
data.type = "Arc";
data.startAngle = obj.startAngle;
data.sweepAngle = obj.sweepAngle;
data.radius = obj.radius;
data.closed = obj.closed;
} else if (obj instanceof Picture) {
data.type = "Picture";
data.src = obj.img.src;
} else if (obj instanceof Polygon) {
data.type = "Polygon";
data.points = obj.points;
data.smooth = obj.smooth;
data.closed = obj.closed;
}
return data;
}
Tablet and Touch Support
Touch Events
// Enable touch support for tablets
Giraffe.Tablet.enableTouch(myCanvas);
- JSON Schema for Touch Configuration**:
{
"canvas": {
"touchEnabled": true,
"multiTouch": false,
"gestureSupport": ["tap", "swipe", "pinch"]
}
}
Device Orientation
// Convert device tilt to mouse movements (experimental)
Giraffe.Tablet.convertRotationToMouse(myCanvas);
Utility Classes and Helpers
Giraffe Core Utilities
- `Giraffe.canvases` - Registry of all canvas instances
- `Giraffe.getCssValue(selector, attribute)` - Extract CSS values
- `Giraffe.X`, `Giraffe.Y` - Array index constants (0, 1)
- `Giraffe.DEG_TO_RAD` - Degree to radian conversion constant
Helper Functions
// Get element position on page
function getPosition(element) {
// Returns [x, y] array of element position
}
Complete Example Applications
Basic Interactive Scene with JSON
Giraffe.js JSON Demo
Game Example with JSON Configuration
var gameSceneJSON = `{
"canvas": {
"width": 800,
"height": 600,
"backgroundColor": "#001122"
},
"objects": [
{
"type": "Composite",
"id": "ship",
"x": 400,
"y": 500,
"parts": [
{
"type": "Polygon",
"x": 0,
"y": 0,
"points": [[0, -20], [-15, 15], [0, 10], [15, 15]],
"fillColor": "white",
"closed": true
}
],
"interactive": {
"onKeyPress": {
"left": {"action": "move", "direction": "left", "speed": 5},
"right": {"action": "move", "direction": "right", "speed": 5},
"space": {"action": "fire"}
}
}
},
{
"type": "Circle",
"id": "enemy1",
"x": 100,
"y": 50,
"radius": 15,
"fillColor": "red",
"animation": {
"type": "patrol",
"path": [[100, 50], [700, 50], [700, 200], [100, 200]],
"speed": 2
}
},
{
"type": "Text",
"id": "score",
"x": 20,
"y": 30,
"text": "Score: 0",
"fontSize": 20,
"font": "Arial",
"color": "white"
}
],
"behaviors": [
{
"type": "collision",
"objects": ["ship", "enemy1"],
"action": "gameOver"
}
]
}`;
Best Practices
Performance
- Use `repaint()` only when necessary - the animation system handles this automatically
- Avoid creating objects in animation loops
- Set `visible=false` for objects that shouldn't be drawn rather than removing them
- Use composites to group related objects for efficient manipulation
- Cache gradient objects rather than recreating them each frame
Code Organization
- Create reusable components by extending GraphicsObject
- Use meaningful names for event handlers and animation methods
- Group related graphics operations in composite objects
- Separate animation logic from rendering logic
- Use JSON schemas for complex scenes that need to be saved/loaded
Memory Management
- Remove event listeners when objects are no longer needed
- Call `canvas.remove(object)` to properly clean up graphics objects
- Stop animations before page unload to prevent memory leaks
- Clear large pixel canvases when not in use
JSON Schema Best Practices
- Use meaningful IDs for all objects that need to be referenced
- Group related objects in composites to reduce complexity
- Define reusable animation and interaction patterns
- Validate JSON schemas before loading to prevent runtime errors
- Use version numbers in your JSON schemas for backward compatibility
Debugging
- Use the browser's developer console for error tracking
- Test hit detection with temporary visual indicators
- Use frame counters to debug animation timing issues
- Check canvas dimensions and scaling factors if objects appear in wrong positions
- Validate JSON structure before attempting to load scenes
Advanced Patterns
Custom Graphics Objects
function CustomShape(x, y, size) {
GraphicsObject.call(this, x, y);
this.size = size;
this.draw = function() {
// Custom drawing code using this.canvas
this.canvas.beginPath();
// ... drawing operations
this.canvas.stroke();
};
this.isInside = function(x, y) {
// Custom hit testing logic
return false; // Implement based on shape
};
this.toJSON = function() {
return {
type: "CustomShape",
x: this.x,
y: this.y,
size: this.size,
color: this.color,
fillColor: this.fillColor
};
};
}
CustomShape.prototype = new GraphicsObject();
// Register custom type with JSON loader
GiraffeJSONLoader.prototype.createCustomShape = function(objData) {
return new CustomShape(objData.x, objData.y, objData.size);
};
Animation Controllers
var GameController = {
score: 0,
gameObjects: [],
processFrame: function(frameNumber) {
// Update game state
this.updatePhysics();
this.checkCollisions();
this.updateUI();
},
updatePhysics: function() {
// Physics simulation
},
checkCollisions: function() {
// Collision detection
},
exportGameState: function() {
return {
score: this.score,
frame: this.currentFrame,
objects: this.gameObjects.map(obj => obj.toJSON())
};
}
};
canvas.addAnimationListener(GameController);
Scene Management System
var SceneManager = {
scenes: {},
currentScene: null,
canvas: null,
loadScene: function(sceneName, jsonData) {
var loader = new GiraffeJSONLoader();
var objects = loader.loadScene(jsonData, this.canvas);
this.scenes[sceneName] = {
objects: objects,
jsonData: jsonData
};
return objects;
},
switchToScene: function(sceneName) {
// Clear current scene
if (this.currentScene) {
this.clearScene(this.currentScene);
}
// Load new scene
if (this.scenes[sceneName]) {
this.currentScene = sceneName;
var scene = this.scenes[sceneName];
// Reload objects from JSON
var loader = new GiraffeJSONLoader();
scene.objects = loader.loadScene(scene.jsonData, this.canvas);
this.canvas.repaint();
}
},
clearScene: function(sceneName) {
if (this.scenes[sceneName]) {
var scene = this.scenes[sceneName];
for (var id in scene.objects) {
this.canvas.remove(scene.objects[id]);
}
}
},
saveCurrentScene: function() {
if (this.currentScene) {
var scene = this.scenes[this.currentScene];
scene.jsonData = exportSceneToJSON(this.canvas, scene.objects);
return scene.jsonData;
}
return null;
}
};
Browser Compatibility
Giraffe.js is designed to work with HTML5 canvas-supporting browsers:
- Chrome 4+
- Firefox 2+
- Safari 3.1+
- Internet Explorer 9+
- Opera 9+
- Mobile browsers with HTML5 support
- Note**: The library includes commented-out IE compatibility code for older versions using VML canvas emulation.
- JSON Support**: All modern browsers support JSON.parse() and JSON.stringify(). For older browsers, include a JSON polyfill.
Troubleshooting
Common Issues
- Objects not appearing**: Check canvas dimensions, object visibility, and position coordinates
- Animation stuttering**: Reduce frame rate or optimize drawing operations
- Mouse events not working**: Ensure `Giraffe.Interactive.setInteractive()` was called
- Performance issues**: Use fewer objects or implement object pooling
- JSON parsing errors**: Validate JSON structure and check for syntax errors
- Objects not loading from JSON**: Verify object types are correctly specified and supported
Debug Techniques
// Check if object is being drawn
myObject.draw = function() {
console.log("Drawing object at: " + this.x + "," + this.y);
// ... original draw code
};
// Monitor animation performance
var frameCount = 0;
var startTime = Date.now();
canvas.addAnimationListener({
processFrame: function(frame) {
frameCount++;
if (frameCount % 60 === 0) {
var fps = frameCount / ((Date.now() - startTime) / 1000);
console.log("Average FPS: " + fps);
}
}
});
// Validate JSON before loading
function validateSceneJSON(jsonString) {
try {
var scene = JSON.parse(jsonString);
if (!scene.objects || !Array.isArray(scene.objects)) {
throw new Error("Invalid scene: objects array missing");
}
scene.objects.forEach(function(obj, index) {
if (!obj.type) {
throw new Error("Object at index " + index + " missing type");
}
if (obj.x === undefined || obj.y === undefined) {
throw new Error("Object at index " + index + " missing position");
}
});
return scene;
} catch (e) {
console.error("JSON validation failed:", e);
return null;
}
}
Class Reference Summary
Core Classes
- `Canvas(canvasElementId)` - Main canvas container
- `GraphicsObject(x, y)` - Base class for all graphics objects
- `GiraffeJSONLoader()` - JSON scene loader and processor
Drawing Primitives
- `Circle(x, y, radius)` - Circular shapes
- `Rectangle(x, y, width, height)` - Rectangular shapes
- `RoundedRectangle(x, y, width, height, radius)` - Rounded rectangles
- `Line(x, y, x2, y2)` - Straight lines
- `Arc(x, y, startAngle, sweepAngle, radius)` - Arcs and pie segments
- `Text(x, y, text, textSize, font)` - Text rendering
- `Picture(x, y, src)` - Image display
- `Polygon(x, y)` - Custom polygonal shapes
- `PixelCanvas(x, y, width, height)` - Pixel-level drawing
Advanced Features
- `Composite(x, y, rotation)` - Object grouping
- `Shadow()` - Shadow effects configuration
- `LineStyle()` - Line styling configuration
- `RadialColor(canvas, color1, color2, x, y, radius)` - Radial gradients
- `GradientColor(canvas, color1, color2, x1, y1, x2, y2)` - Linear gradients
Animation Classes
- `Giraffe.Transition(target, frames)` - Base transition class
- `Giraffe.FlipOutX(target, frames)` - X-axis flip out animation
- `Giraffe.FlipInX(target, frames)` - X-axis flip in animation
- `Giraffe.FlipOutY(target, frames)` - Y-axis flip out animation
- `Giraffe.FlipInY(target, frames)` - Y-axis flip in animation
- `Giraffe.MoveSequence(target, frames, matrix)` - Movement animation
- `Giraffe.RotationSequence(target, frames, steps)` - Rotation animation
- `Giraffe.ExplodeSequence(target, frames)` - Explosion effect
Utility Modules
- `Giraffe.Interactive` - Mouse and keyboard interaction handling
- `Giraffe.Tablet` - Touch and device orientation support
Quick Start Guide
1. Basic Setup
// HTML
// JavaScript
var canvas = new Canvas("myCanvas");
2. Add Graphics (Programmatic)
var circle = new Circle(100, 100, 50)
.setFillColor("blue")
.setColor("darkblue");
canvas.add(circle);
canvas.repaint();
3. Add Graphics (JSON)
var sceneJSON = `{
"objects": [
{
"type": "Circle",
"x": 100,
"y": 100,
"radius": 50,
"fillColor": "blue",
"color": "darkblue"
}
]
}`;
var loader = new GiraffeJSONLoader();
var objects = loader.loadScene(sceneJSON, canvas);
canvas.repaint();
4. Enable Interaction
Giraffe.Interactive.setInteractive(canvas);
circle.onClick = function(x, y) {
alert("Circle clicked!");
};
5. Add Animation
Giraffe.setAnimated(canvas);
circle.animate = function(frame) {
this.x = 100 + Math.sin(frame * 0.1) * 50;
};
canvas.startAnimation(30, 1000, true);
6. Save/Load Scenes
// Save current scene
var sceneJSON = exportSceneToJSON(canvas, objects);
localStorage.setItem("myScene", sceneJSON);
// Load saved scene
var savedScene = localStorage.getItem("myScene");
if (savedScene) {
var objects = loader.loadScene(savedScene, canvas);
}
This comprehensive documentation covers the complete Giraffe.js library functionality, including both programmatic usage and JSON schema-based scene definition. The library provides a solid foundation for building interactive canvas applications with clean object-oriented design patterns, comprehensive animation support, robust user interaction capabilities, and flexible data-driven scene management.