本文へ移動

Taisen Brackets対戦

対戦の一覧から、トーナメント表をSVGで描きます。

対戦(たいせん)は、二者が向かい合って勝負することです。 名前について(英語)

目次

やり方

React・Vue・Svelte・Angular で使う

React、Vue、Svelte、Angular でトーナメント表を描きます。SVG の文字列を HTML として入れます。

drawBracket は SVG の文字列を返すので、どのフレームワークでも同じように使います。データから文字列を作り、フレームワークに HTML として渡します。描画は自分のスタイルを持ち、スクリプトを含まず、渡されたマークはすべて無害化されるので、文字列はそのままページに入れて安全です。サーバーでも同じ描画になるので、サーバーでの描画にもトーナメント表が入ります。

React

Bracket.tsx です。React が HTML を受け取る方法は dangerouslySetInnerHTML です。

import { drawBracket, type Matchup } from "@johnmorrisdotca/taisen-brackets";

const matchups: Matchup[] = [
  { sides: ["Aurora", "Harbour"], score: [2, 1] },
  { sides: ["Cascade", "Delta"], score: [0, 1] },
  { sides: ["Aurora", "Delta"], score: [3, 1] },
];

export function Bracket({ theme = "auto" }: { theme?: "auto" | "light" | "dark" }) {
  return <div id="bracket" dangerouslySetInnerHTML={{ __html: drawBracket(matchups, { theme }) }} />;
}

いつもどおりにマウントします。コンポーネントはサーバーでも文字列に描画できます (renderToString)。パッケージのテストで確かめています。ブラウザが何も実行する前から、ページにトーナメント表が入っています。

import { createRoot } from "react-dom/client";
import { Bracket } from "./Bracket";

createRoot(document.getElementById("root")!).render(<Bracket />);

Vue

v-html が文字列を受け取ります。

<script setup lang="ts">
import { drawBracket, type Matchup } from "@johnmorrisdotca/taisen-brackets";

const props = withDefaults(defineProps<{ theme?: "auto" | "light" | "dark" }>(), { theme: "auto" });
const matchups: Matchup[] = [
  { sides: ["Aurora", "Harbour"], score: [2, 1] },
  { sides: ["Cascade", "Delta"], score: [0, 1] },
  { sides: ["Aurora", "Delta"], score: [3, 1] },
];
</script>

<template>
  <div id="bracket" v-html="drawBracket(matchups, { theme: props.theme })"></div>
</template>
import { createApp } from "vue";
import Bracket from "./Bracket.vue";

createApp(Bracket).mount("#app");

Svelte

{@html …} が文字列を受け取ります。

<script lang="ts">
  import { drawBracket, type Matchup } from "@johnmorrisdotca/taisen-brackets";

  let { theme = "auto" }: { theme?: "auto" | "light" | "dark" } = $props();
  const matchups: Matchup[] = [
    { sides: ["Aurora", "Harbour"], score: [2, 1] },
    { sides: ["Cascade", "Delta"], score: [0, 1] },
    { sides: ["Aurora", "Delta"], score: [3, 1] },
  ];
</script>

<div id="bracket">{@html drawBracket(matchups, { theme })}</div>
import { mount } from "svelte";
import Bracket from "./Bracket.svelte";

mount(Bracket, { target: document.getElementById("app")! });

Angular

Angular の [innerHTML] は描画の <style> を取り除いてしまうので、文字列は bypassSecurityTrustHtml を通します。

import { Component, inject, Input } from "@angular/core";
import { DomSanitizer, type SafeHtml } from "@angular/platform-browser";
import { drawBracket, type Matchup } from "@johnmorrisdotca/taisen-brackets";

const matchups: Matchup[] = [
  { sides: ["Aurora", "Harbour"], score: [2, 1] },
  { sides: ["Cascade", "Delta"], score: [0, 1] },
  { sides: ["Aurora", "Delta"], score: [3, 1] },
];

@Component({
  selector: "app-bracket",
  standalone: true,
  template: `<div id="bracket" [innerHTML]="svg"></div>`,
})
export class BracketComponent {
  @Input() theme: "auto" | "light" | "dark" = "auto";

  private readonly sanitizer = inject(DomSanitizer);

  // The SVG is made here from your own data, and the package cleans every mark it is given: so it is trusted as it is.
  get svg(): SafeHtml {
    return this.sanitizer.bypassSecurityTrustHtml(drawBracket(matchups, { theme: this.theme }));
  }
}
import "@angular/compiler";
import { bootstrapApplication } from "@angular/platform-browser";
import { provideZonelessChangeDetection } from "@angular/core";
import { BracketComponent } from "./bracket.component";

void bootstrapApplication(BracketComponent, { providers: [provideZonelessChangeDetection()] });

Next.js・Nuxt・SvelteKit

どれも先にサーバーで描画しますが、drawBracket はサーバーでも同じ描画をするので、コンポーネントはそのまま動きます。Next.js の App Router では、描くだけのコンポーネントに "use client" は要りません。

ソースを見るGitHub で編集@johnmorrisdotca/taisen-brackets 0.20.0 のソースから作成しました。

この日本語は、まだ日本語を母語とする方の確認を受けていません。訂正を歓迎します。