<footer><div id=version></div></footer>
html, body {
  height: 100%;
}

body {
  margin: 0;
  padding: 0;
  background: #111 url(https://labs.phaser.io/assets/sprites/phaser3-logo-small.png) no-repeat 0 20px;
  color: #eee;
  font: caption;
}

#version {
  position: absolute;
  left: 0;
  top: 0;
  padding: 0;
  background: rgba(0, 0, 0, 0.5)
}
/* global colors, Phaser */

const { silver, gray, white, black, red, green, yellow } = colors.hexColors;

const { Clamp } = Phaser.Math;

class Window {
  constructor(scene, x, y, width, height) {
    this.container = scene.sys.add.container(x, y);

    this.camera = scene.sys.cameras.add(x, y, width, height)
      .ignore(this.container);

    this.titleBar = new Phaser.GameObjects.Rectangle(scene, 0, -32, width, 32, gray)
      .setOrigin(0, 0)
      .setName("bar");

    this.minButton = new Phaser.GameObjects.Arc(
      scene,
      16,
      -16,
      12,
      undefined,
      undefined,
      undefined,
      yellow
    )
      .setInteractive()
      .on("pointerdown", this.minimize, this);

    this.maxButton = new Phaser.GameObjects.Arc(
      scene,
      48,
      -16,
      12,
      undefined,
      undefined,
      undefined,
      green
    )
      .setInteractive()
      .on("pointerdown", this.maximize, this);

    this.container.setName("window")
      .setAlpha(0.4)
      .setInteractive({
        cursor: "grab",
        draggable: true,
        hitArea: new Phaser.Geom.Rectangle(0, -32, width, 32),
        hitAreaCallback: Phaser.Geom.Rectangle.Contains
      })
      .on("pointerover", this.onPointerOver, this)
      .on("pointerout", this.onPointerOut, this)
      .on("drag", this.onDrag, this)
      .add([this.titleBar, this.minButton, this.maxButton]);
  }

  onDrag(pointer, dragX, dragY) {
    this.camera.setPosition(dragX, dragY);
    this.container.setPosition(dragX, dragY);
  }

  onPointerOver() {
    this.container.setAlpha(0.8);
  }

  onPointerOut() {
    this.container.setAlpha(0.4);
  }

  minimize() {
    this.camera.visible = false;
  }

  maximize() {
    this.camera.visible = true;
  }
}

class BootScene extends Phaser.Scene {
  preload() {
    this.load.image("grid", "assets/pics/debug-grid-1920x1920.png");
    this.load.image("nebula", "assets/tests/space/nebula.jpg");
    this.load.image("eye", "assets/pics/lance-overdose-loader-eye.png");
  }

  create() {
    this.scene.start("play");
    this.scene.remove();
  }
}

// Make sure the drag coordinates come from only one camera viewport.
function onDragStart(pointer) {
  for (const cam of pointer.camera.cameraManager.cameras) {
    cam.inputEnabled = cam === pointer.camera;
  }
}

function onDrag(pointer, dragX, dragY) {
  this.setPosition(dragX, dragY);
}

function onDragEnd(pointer) {
  for (const cam of pointer.camera.cameraManager.cameras) {
    cam.inputEnabled = true;
  }
}

class PlayScene extends Phaser.Scene {
  create() {
    const nebula = this.add.image(0, 0, "nebula").setOrigin(0, 0);

    const grid = this.add.image(0, 0, "grid").setOrigin(0, 0);

    const eye1 = this.add
      .image(320, 320, "eye")
      .setInteractive({ cursor: "grab", draggable: true })
      .on("dragstart", onDragStart)
      .on("drag", onDrag)
      .on("dragend", onDragEnd);

    const eye2 = this.add
      .image(80, 80, "eye")
      .setInteractive({ cursor: "grab", draggable: true })
      .on("dragstart", onDragStart)
      .on("drag", onDrag)
      .on("dragend", onDragEnd);

    const _window = new Window(this, 80, 80, 640, 640);

    console.log(_window);

    const layerInsideWindow = this.add
      .layer([grid, eye2])
      .setName("layerInWindow");

    const layerOutsideWindow = this.add
      .layer([nebula, eye1, _window.container])
      .setName("layerOutsideWindow");

    _window.camera.ignore(layerOutsideWindow);

    this.cameras.main.ignore(layerInsideWindow);
  }
}

document.getElementById("version").textContent = `Phaser v${Phaser.VERSION}`;

new Phaser.Game({
  scene: [new BootScene("boot"), new PlayScene("play")],
  loader: {
    baseURL: "https://labs.phaser.io",
    crossOrigin: "anonymous"
  }
});

Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.js
  2. https://cdn.jsdelivr.net/npm/@samme/colors@1.2.0/dist/colors.umd.js