Skip to content

jasonsturges/storybook-for-pixi.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

74 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Storybook for Pixi.js

Using Storybook for Pixi.js graphics development

storybook-for-pixi

This project includes:

Live example at: https://jasonsturges.github.io/storybook-for-pixi.js

Background

Often conventions involve developing components in isolation and leveraging GUI frameworks such as dat.GUI for controls.

For projects of greater complexity there may be a need to classify numerous scenarios, which is the catalyst for this project.

Getting Started

Begin via any of the following:

  • Press the "Use this template" button

  • Use degit to execute:

    degit github:jasonsturges/storybook-for-pixi.js
    
  • Use GitHub CLI to execute:

    gh repo create <name> --template="https://github.com/jasonsturges/storybook-for-pixi.js"
    
  • Simply git clone, delete the existing .git folder, and then:

    git init
    git add -A
    git commit -m "Initial commit"
    

Install and run via npm (or yarn) by executing the storybook script:

npm install
npm run storybook

Configuration

Storybook supports the following configurations.

Story Location

If you prefer to keep stories out of the root or src/ folders, you can optionally store them within the .storybook/ folder. This isolates all aspects of Storybook within a single folder.

Move your stories/ folder inside .storybook/ and update Storybook's main.js configuration file to the new path:

"stories": [
  "./stories/**/*.stories.mdx",
  "./stories/**/*.stories.@(js|jsx|ts|tsx)"
],

Public Static Assets

Static assets can be served and referenced via stories by configuring the staticDirs property in the main.js configuration file.

staticDirs: ["../public"],

In this template, public assets are served from the public/ folder.

Global Variables

Global variables can be defined via the globals object in the main.js configuration file.

For example, define a myKey property as follows:

// Place custom global values here
export const globals = {
  myKey: "My value",
};

Inside every story, the globals object may be accessed and derefereced as follows:

export const Template = (args, { globals: { myKey } }) => {
  console.log(myKey); // => "My value"
};

Loaders

If your stories depend on textures loaded before the story executes, use Storybook's experimental loaders feature, defined in the preview.js under the .storybook/ folder.

This may be necessary if your sprites pivot or inverse scsale to flip and mirror textures, as width and height of the texture must be known at instantiation.

import { loadTextures } from "../stories/Scene";

export const loaders = [
  async () => ({
    loader: await loadTextures(),
  }),
];

Loaders feature async await for synchronous blocking - an indeterminate spinner will appear upon the first load of Storybook while all assets are loaded. Once complete, stories will display as normal. This happens only once per load of the Storybook webpage.

As an example, the Scene.js loads the Pixi.js svg logo.

export const loadTextures = async () => {
  return new Promise((resolve, reject) => {
    const loader = new PIXI.Loader();
    loader //
      .add("images/logo.svg")
      .load();

    loader.onComplete.add(() => {
      resolve();
    });

    loader.onError.add(() => {
      reject();
    });
  });
};

Decorators

Decorators run every time a story changes. To insert code, or expand the template, edit the decorators found within the preview.js under the .storybook/ folder.

export const decorators = [
  (story) => {
    createScene({
      width: window.innerWidth - 32,
      height: window.innerHeight - 36,
    });
    return story();
  },
];

TypeScript

To add support for TypeScript, just add the typescript package to your package.json dev dependencies:

"devDependencies": {
  "tslib": "^2.3.1",
  "typescript": "^4.5.5"
}

Usage

To create a story, add a new file under the stories/ folder named <story-name>.stories.js.

Each story has a default export for navigation grouping and argument types:

export default {
  title: "Example/My Story",
};

The above story will be located under "Example" as "My Story".

Basic Usage

In its most simplistic form, a story simply exports a function. The name of the function is the story name.

import * as PIXI from "pixi.js";
import { canvas, viewport } from "../Scene";

export default {
  title: "Example/Logos",
};

/**
 * Pixi logo story
 */
export const PixiLogo = () => {
  const logo = PIXI.Sprite.from("images/logo.svg");
  logo.anchor.set(0.5, 0.5);
  logo.x = viewport.screenWidth / 2;
  logo.y = viewport.screenHeight / 2;
  viewport.addChild(logo);

  return canvas;
};

The Scene.js defines a reusable pixi app, viewport, and canvas element to perform Pixi operations.

story-pixi-logo

Providing Controls as Arguments

To include arguments, first define the argTypes used by the story. These include controls of number, boolean, text, color, date, radio, check, and other advanced types. See Storybook's documentation on controls for more.

export default {
  title: "Example/My Story",
  argTypes: {
    width: { control: "number" },
    height: { control: "number" },
    fill: { control: "color" },
    enabled: { control: "boolean" },
    text: { control: "text" },
    align: {
      options: ["left", "center", "right"],
      control: {
        type: "radio",
      },
    },
    value: {
      control: {
        type: "range",
        min: 0,
        max: 100,
        step: 1,
      },
    },
  },
};

These arguments may be accessed via the args object, passed to the story function:

export const MyStoryExample = (args) => {
  const text = new PIXI.Text(args.text, {
    fontsize: 24,
    fill: 0xffffff,
    align: args.align,
  });
}

Or, simply derefereced:

export const MyStoryExample = ({text, align}) => {
  const text = new PIXI.Text(text, {
    fontsize: 24,
    fill: 0xffffff,
    align: align,
  });
}

To set default values, set the args of your story function immediately following the story:

export const MyStoryExample = ({text, align}) => {
  // ...story implementation
}
MyStoryExample.args = {
  text: "Hello, World\n๐Ÿ˜€",
  align: "center",
};

The following is a full example of using arguments in a story:

import * as PIXI from "pixi.js";
import { canvas, viewport } from "../Scene";
import { drawGear } from "../../src/components/Gear";
import { parseColor } from "../../src/utils/ColorUtils";

export default {
  title: "Example/Shapes",
  argTypes: {
    stroke: { control: "number" },
    color: { control: "color" },
    fill: { control: "color" },
    sides: { control: "number" },
    innerRadius: { control: "number" },
    outerRadius: { control: "number" },
    angle: { control: "number" },
  },
};

export const Gear = ({
  stroke,
  color,
  fill,
  sides,
  innerRadius,
  outerRadius,
  holeSides,
  holeRadius,
  angle,
}) => {
  const graphics = new PIXI.Graphics();
  graphics.lineStyle(stroke, parseColor(color));
  graphics.beginFill(parseColor(fill));

  drawGear(
    graphics,
    viewport.screenWidth / 2,
    viewport.screenWidth / 2,
    sides,
    innerRadius,
    outerRadius,
    angle,
    holeSides,
    holeRadius
  );

  viewport.addChild(graphics);

  return canvas;
};
Gear.args = {
  stroke: 2,
  color: "#cfefff",
  fill: "#036191",
  sides: 8,
  innerRadius: 35,
  outerRadius: 50,
  holeSides: 8,
  holeRadius: 10,
  angle: 0,
};

The above code results in:

story-gear

Template

Instead of defining each individual story as a bespoke function, templates can be defined to reuse common story construction.

For example, create a template to be used by several stories:

const Template = ({ stroke, color, fill, sides, radius, angle }) => {
  const graphics = new PIXI.Graphics();
  graphics.lineStyle(stroke, parseColor(color));
  graphics.beginFill(parseColor(fill));
  drawPolygon(
    graphics,
    viewport.screenWidth / 2,
    viewport.screenHeight / 2,
    sides,
    radius,
    angle
  );

  viewport.addChild(graphics);

  return canvas;
};

Apply the template by passing default arguments:

export const Triangle = Template.bind({});
Triangle.args = {
  stroke: 2,
  color: "#cfefff",
  fill: "#036191",
  sides: 3,
  radius: 50,
  angle: 0,
};

export const Square = Template.bind({});
Square.args = {
  stroke: 2,
  color: "#cfefff",
  fill: "#036191",
  sides: 4,
  radius: 50,
  angle: 0,
};

Result of this story will be:

story-polygon

See more examples: Star, Burst, Gear, Text Style, Logo, Polygon