diff --git a/examples/web/index.ts b/examples/web/index.ts
index f1e48f98e1d7ed0f4e670155cbbaed4839e04ed5..f7951ea9aeeef2b1d63ba8c2ed9d72de0d179cfe 100644
--- a/examples/web/index.ts
+++ b/examples/web/index.ts
@@ -1,4 +1,4 @@
-import { Emulator, PixelFormat, startApp } from "./react/app";
+import { Emulator, Observable, PixelFormat, startApp } from "./react/app";
 
 import { default as _wasm, GameBoy, PadKey, PpuMode } from "./lib/boytacean.js";
 import info from "./package.json";
@@ -48,7 +48,7 @@ const ROM_PATH = require("../../res/roms/20y.gb");
  * and "joins" all the elements together to bring input/output
  * of the associated machine.
  */
-class GameboyEmulator implements Emulator {
+class GameboyEmulator extends Observable implements Emulator {
     /**
      * The Game Boy engine (probably coming from WASM) that
      * is going to be used for the emulation.
@@ -218,6 +218,7 @@ class GameboyEmulator implements Emulator {
                     this.gameBoy!.frame_buffer_eager(),
                     PixelFormat.RGB
                 );
+                this.trigger("frame");
                 lastFrame = this.gameBoy!.ppu_frame();
             }
         }
diff --git a/examples/web/react/app.tsx b/examples/web/react/app.tsx
index a70e8960e819fb25b9ed5363a54d798c340a95fc..ab92d0f9dd57079f8f66960ccd0e2439f428a020 100644
--- a/examples/web/react/app.tsx
+++ b/examples/web/react/app.tsx
@@ -22,12 +22,36 @@ import {
 
 import "./app.css";
 
+export type Callback<T> = (owner: T) => void;
+
+/**
+ * Abstract class that implements the basic functionality
+ * part of the definition of the observable pattern.
+ *
+ * @see {@link https://en.wikipedia.org/wiki/Observer_pattern}
+ */
+export class Observable {
+    private events: Record<string, [Callback<this>]> = {};
+
+    bind(event: string, callback: Callback<this>) {
+        const callbacks = this.events[event] ?? [];
+        if (callbacks.includes(callback)) return;
+        callbacks.push(callback);
+        this.events[event] = callbacks;
+    }
+
+    trigger(event: string) {
+        const callbacks = this.events[event] ?? [];
+        callbacks.forEach((c) => c(this));
+    }
+}
+
 /**
  * Top level interface that declares the main abstract
  * interface of an emulator structured entity.
  * Should allow typical hardware operations to be performed.
  */
-export interface Emulator {
+export interface Emulator extends Observable {
     getName(): string;
     getVersion(): string;
     getVersionUrl(): string;
@@ -80,9 +104,10 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
     };
     const onDrawHandler = (handler: DrawHandler) => {
         if (intervalRef.current) return;
-        intervalRef.current = setInterval(() => {
+        intervalRef.current = 1;
+        emulator.bind("frame", () => {
             handler(emulator.getImageBuffer(), PixelFormat.RGB);
-        }, 1000);
+        });
     };
     useEffect(() => {
         document.body.style.backgroundColor = `#${getBackground()}`;