Guide

Introduction to all Pencil.js feature …

In this guide, you will discover most of the features of the Pencil.js library.
It assumes basic knowledge of web technologies (HTML, JavaScript, NPM …).
by the end of the page, you will be able to build your own creations with ease.

Examples

Getting started

First of all, you need to add the library to your page.
You can quickly doing so by using a CDN:


<script src="https://unpkg.com/pencil.js"></script>
<!-- or -->
<script src="https://cdn.jsdelivr.net/npm/pencil.js"></script>
    

Or you can load it with NPM:


$ npm install pencil.js
        

import Pencil from "pencil.js";
    

In both case, you can now use it:


console.log(Pencil.version);
    

Scene

The first and main element of your code should be a {Scene}. A scene is bound to an element or to the whole screen by default.


const myFirstScene = new Pencil.Scene();
// or
const wrapper = document.querySelector("#wrapper");
const myFirstScene = new Pencil.Scene(wrapper);
    

When your {Scene} is set-up, if you only need a still frame, you can render it once.


myFirstScene.render();
    

Or, you can make it alive by refreshing every frames.


myFirstScene.startLoop();
    

Components

{Component} refers to any drawable part of a scene. As an abstract class, it should not be instantiated by itself, but by any sub-class (ex: Rectangle, Star, Image …).


const myRectangle = new Pencil.Rectangle();
    

All {Component} can be instantiated using the following signature:
(position, [...a number of specific properties], options).
To continue on our previous example:


const width = 800;
const height = 600;
const drawingOptions = {
    fill: "red",
};
const myRectangle = new Pencil.Rectangle(aPosition, width, height, drawingOptions);
    

To know what are the specific properties of any {Component}, refer to its documentation.

Here is the list of all {Component} possible options with their default values:


const componentOptions = {
    shown: true,
    opacity: null,
    rotation: 0,
    rotationAnchor: [0, 0],
    scale: [1, 1],
    zIndex: 1,
    clip: null,
    fill: "#000",
    stroke: null,
    strokeWidth: 2,
    cursor: "default",
    join: "miter",
};
    

Containers

{Scene} and {Component} both inherit from {Container}. It means that you can includes element to each other by using the .add function.


myScene.add(myRectangle);

myRectangle.add(new Pencil.Circle(), new Pencil.Star());
    

You can also remove as easily.


myRectangle.delete();
    

Otherwise, {Container} can be hidden and shown without having to remove or add them.


myRectangle.hide();
myRectangle.show();
    

Position

{Position} is an utility class used all across the Pencil.js library.
It serves as a wrapper for a x and y coordinate in 2D space. You can create one using the class constructor.


const x = 100;
const y = 200;
const myPosition = new Pencil.Position(x, y);
    

Alternatively, anywhere a position is needed, you can use this intuitive shorthand.


const myRectangle = new Pencil.Rectangle([100, 200]);
    

The {Position} class also comes with a lot of methods that can be chained together.


console.log(myPosition.x, myPosition.y); // => 100, 200
myPosition.add(10, 20).multiply(2).rotate(0.25);
console.log(myPosition.x, myPosition.y); // => -440, 220
    

These functions don't return a new instance, but instead modify the existing one. If you don't want that behavior, you can call clone first.


console.log(myPosition.x, myPosition.y); // => 100, 200
const clonePosition = myPosition.clone().add(10, 20).multiply(2).rotate(0.25);
console.log(myPosition.x, myPosition.y); // => 100, 200
console.log(clonePosition.x, clonePosition.y); // => -440, 220
    

Vector

Much alike {Position}, {Vector} is an utility wrapper around two {Position}.


const myVector = new Pencil.Vector(fromPosition, toPosition);
// alternatively
const myVector = new Pencil.Vector([startX, startY], [endX, endY]);
    

Of course, each instances carries useful chainable methods.


myVector.add(otherVector).multiply(2);
if (myVector.intersect(someVector)) {
    // myVector intersects someVector
}
    

You can also use the clone method to prevent operations from mutating your instance.

Events

All classes fire events whenever necessary and you can listen to those.


const eventName = Pencil.MouseEvent.events.hover;
const callback = () => myRectangle.options.fill = "red";
myRectangle.on(eventName, callback);
    

const eventName = Pencil.KeyboardEvent.events.keydown;
myScene.on(eventName, (event) => {
    switch(event.key) {
        case Pencil.KeyboardEvent.keys.enter:
            console.log("Enter key pressed");
            break;
        case Pencil.KeyboardEvent.keys.delete:
            console.log("Delete key pressed");
            break;
        // ...
    }
});
    

const eventName = Pencil.NetworkEvent.events.ready;
const myImage = new Pencil.Image([10, 20], url);
myImage.on(eventName, () => myScene.add(myImage).render());
    

Events bubble to parent container and all his ancestor.
If you want to listen to events only targeting a specific element, you have to set the third parameter to true.


container.on("eventName", callback);       // Listen to all "eventName" triggered by any of container's children
container.on("eventName", callback, true); // Listen only to "eventName" triggered by container
    

Text

You can write on the scene with the {Text} class. This one is a bit different from the other.


const text = new Pencil.Text(aPosition, "Some text", options);
    

{Text} have more options on top of options from Component. Here is the list with default values:


const textOptions = {
    font: "sans-serif",
    fontSize: 20,
    align: "start",
    bold: false,
    italic: false,
    underscore: false,
    lineHeight: 1,
};
    

If the font property of options is an URL, you have to wait for it to be loaded before rendering it.


const fontURL = "https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu4mxK.woff2";
const text = new Pencil.Text(aPosition, "Robot ♥ you", {
    font: fontURL,
});
text.on("ready", () => myScene.add(text).render());
    

Interactions

{Component} can be set {draggable} with a simple call of a function.


myRectangle.draggable();
// or with some options
myRectangle.draggable({
    x: false,
});
// or
myRectangle.draggable({
    constrain: new Pencil.Vector(min, max),
});
    

{Rectangle} can be set {resizable} with a simple call of a function.


myRectangle.resizable();
// or with some options
myRectangle.resizable({
    x: false,
});
// or
myRectangle.resizable({
    constrain: new Pencil.Vector(min, max),
});
    

User inputs

Pencil.js provide ways to let your users interacts with common inputs components ({Button}, {Checkbox}, {Select}, {Slider}).


new Pencil.Slider(aPosition, {
    value: 1,
    min: 1,
    max: 100,
});

new Pencil.Select(anotherPosition, [
    "First option",
    "Second option",
    "Third option",
], {
    value: 1,
});
    

These classes have their own set of options to customize how they look.


new Pencil.Button(yetAnotherPosition, {
    value: "Click me",
    fill: "red",
    background: "#222",
    border: "gold",
    hover: "#444",
});
    

Color

Whenever you have to provide a color (fill or stroke option for example), you can use all CSS color value or the {Color} class.

This utility class has several ways to be created.


new Pencil.Color(0.1, 0.5, 0.3);
new Pencil.Color("indigo");
new Pencil.Color("#123456");
new Pencil.Color("#123");
new Pencil.Color(0x123456);
    

Every one of this definitions can have one more parameter to define it's opacity.


const opacity = 0.7;
new Pencil.Color(0.1, 0.5, 0.3, opacity);
    

Then, you have access to numerous methods to manipulate the color.


const color = new Pencil.Color("olive");
color.saturation(0.9).reverse();
console.log(color.name); // => "mediumslateblue"
    

Pro-tips

1. Keep your code cleaner and lighter by only importing parts of Pencil.js that you need.


import { Star, Image as PencilImage } from "pencil.js";
// same as
import Star from "@pencil.js/star";
import PencilImage from "@pencil.js/image";
    

2. You can make you own class extending other existing ones.


import Circle from "@pencil.js/circle";
class BlueCircle extends Circle {
    static get defaultOptions () {
        return {
            ...super.defaultOptions,
            fill: "blue",
        };
    }
}
    

3. You can create and fire your own events.


const myEventName = "something append";
eventTarget.fire(new Pencil.BaseEvent(myEventName, eventTarget);
// and listen using the same event name.
myScene.on(myEventName, doSomething);