Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greatest.deepsurf.us/scripts/444119/1061612/FlowComments.js
- // ==UserScript==
- // @name FlowComments
- // @namespace https://midra.me
- // @version 1.0.6
- // @description コメントを流すやつ
- // @author Midra
- // @license MIT
- // @grant none
- // @compatible chrome >= 84
- // @compatible safari >= 15
- // @compatible firefox >= 90
- // ==/UserScript==
-
- // @ts-check
-
- 'use strict'
-
- /**
- * `FlowComments`のスタイル
- * @typedef {object} FlowCommentsStyle
- * @property {string} [fontFamily] フォント
- * @property {string} [fontWeight] フォントの太さ
- * @property {number} [fontScale] 拡大率
- * @property {string} [color] フォントカラー
- * @property {string} [shadowColor] シャドウの色
- * @property {number} [shadowBlur] シャドウのぼかし
- * @property {number} [opacity] 透明度
- */
-
- /**
- * `FlowCommentsItem`のオプション
- * @typedef {object} FlowCommentsItemOption
- * @property {number} [position] 表示位置
- * @property {number} [duration] 表示時間
- */
-
- /**
- * `FlowComments`のオプション
- * @typedef {object} FlowCommentsOption
- * @property {number} [resolution] 解像度
- * @property {number} [lines] 行数
- * @property {number} [limit] 画面内に表示するコメントの最大数
- * @property {boolean} [autoResize] サイズ(比率)を自動で調整
- * @property {boolean} [autoResolution] 解像度を自動で調整
- * @property {boolean} [smoothRender] カクつきを抑える(負荷高いかも)
- */
-
- /****************************************
- * デフォルト値
- */
- const FLOWCMT_CONFIG = Object.freeze({
- /** フォントファミリー */
- FONT_FAMILY: [
- 'Arial',
- '"ヒラギノ角ゴシック"', '"Hiragino Sans"',
- '"游ゴシック体"', 'YuGothic', '"游ゴシック"', '"Yu Gothic"',
- 'Gulim', '"Malgun Gothic"',
- '"黑体"', 'SimHei',
- 'system-ui', '-apple-system',
- 'sans-serif',
- ].join(),
-
- /** フォントの太さ */
- FONT_WEIGHT: /Android/.test(window.navigator.userAgent) ? '700' : '600',
-
- /** フォントの拡大率 */
- FONT_SCALE: 0.7,
-
- /** フォントのY軸のオフセット */
- FONT_OFFSET_Y: 0.15,
-
- /** テキストの色 */
- TEXT_COLOR: '#fff',
-
- /** テキストシャドウの色 */
- TEXT_SHADOW_COLOR: '#000',
-
- /** テキストシャドウのぼかし */
- TEXT_SHADOW_BLUR: 1,
-
- /** テキスト間の余白(配列形式の場合) */
- TEXT_MARGIN: 0.2,
-
- /** Canvasのクラス名 */
- CANVAS_CLASSNAME: 'mid-FlowComments',
-
- /** Canvasの比率 */
- CANVAS_RATIO: 16 / 9,
-
- /** Canvasの解像度 */
- CANVAS_RESOLUTION: 720,
-
- /** 解像度のリスト */
- RESOLUTION_LIST: [240, 360, 480, 720],
-
- /** コメントの表示時間 */
- CMT_DISPLAY_DURATION: 6000,
-
- /** コメントの最大数(0は無制限) */
- CMT_LIMIT: 0,
-
- /** 行数 */
- LINES: 11,
-
- /** 比率の自動調整 */
- AUTO_RESIZE: true,
-
- /** 解像度の自動調整 */
- AUTO_RESOLUTION: true,
- })
-
- /****************************************
- * コメントの種類
- */
- const FLOWCMT_TYPE = Object.freeze({
- // 流す
- FLOW: 0,
- // 上部に固定
- TOP: 1,
- // 下部に固定
- BOTTOM: 2,
- })
-
- /****************************************
- * @type {FlowCommentsItemOption}
- */
- const FLOWCMTITEM_DEFAULT_OPTION = Object.freeze({
- position: FLOWCMT_TYPE.FLOW,
- duration: FLOWCMT_CONFIG.CMT_DISPLAY_DURATION,
- })
-
- /****************************************
- * @type {FlowCommentsOption}
- */
- const FLOWCMT_DEFAULT_OPTION = Object.freeze({
- resolution: FLOWCMT_CONFIG.CANVAS_RESOLUTION,
- lines: FLOWCMT_CONFIG.LINES,
- limit: FLOWCMT_CONFIG.CMT_LIMIT,
- autoResize: FLOWCMT_CONFIG.AUTO_RESIZE,
- autoResolution: FLOWCMT_CONFIG.AUTO_RESOLUTION,
- smoothRender: false,
- })
-
- /****************************************
- * @type {FlowCommentsStyle}
- */
- const FLOWCMT_DEFAULT_STYLE = Object.freeze({
- fontFamily: FLOWCMT_CONFIG.FONT_FAMILY,
- fontWeight: FLOWCMT_CONFIG.FONT_WEIGHT,
- fontScale: 1,
- color: FLOWCMT_CONFIG.TEXT_COLOR,
- shadowColor: FLOWCMT_CONFIG.TEXT_SHADOW_COLOR,
- shadowBlur: FLOWCMT_CONFIG.TEXT_SHADOW_BLUR,
- opacity: 1,
- })
-
- /****************************************
- * @classdesc ユーティリティ
- */
- class FlowCommentsUtil {
- /****************************************
- * オブジェクトのプロパティからnullとundefinedを除去
- * @param {object} obj オブジェクト
- */
- static filterObject(obj) {
- if (typeof obj === 'object' && !Array.isArray(obj) && obj !== undefined && obj !== null) {
- Object.keys(obj).forEach(key => {
- if (obj[key] === undefined || obj[key] === null) {
- delete obj[key]
- } else {
- this.filterObject(obj[key])
- }
- })
- }
- }
-
- /****************************************
- * Canvasにスタイルを適用
- * @param {CanvasRenderingContext2D} ctx CanvasRenderingContext2D
- * @param {FlowCommentsStyle} style スタイル
- * @param {number} resolution 解像度
- * @param {number} fontSize フォントサイズ
- */
- static setStyleToCanvas(ctx, style, resolution, fontSize) {
- ctx.textBaseline = 'middle'
- ctx.lineJoin = 'round'
- ctx.font = `${style.fontWeight} ${fontSize * style.fontScale}px ${style.fontFamily}`
- ctx.fillStyle = style.color
- ctx.shadowColor = style.shadowColor
- ctx.shadowBlur = resolution / 400 * style.shadowBlur
- ctx.globalAlpha = style.opacity
- }
- }
-
- /****************************************
- * @classdesc 画像キャッシュ管理用
- */
- class FlowCommentsImageCache {
- /**
- * オプション(デフォルト値)
- */
- static #OPTION = {
- maxSize: 50,
- }
- /**
- * キャッシュ
- * @type {{ [url: string]: { img: HTMLImageElement; lastUsed: number; }; }}
- */
- static #cache = {}
-
- /****************************************
- * キャッシュ追加
- * @param {string} url URL
- * @param {HTMLImageElement} img 画像
- */
- static add(url, img) {
- // 削除
- if (this.#OPTION.maxSize < Object.keys(this.#cache).length) {
- let delCacheUrl
- Object.keys(this.#cache).forEach(key => {
- if (
- delCacheUrl === undefined ||
- this.#cache[key].lastUsed < this.#cache[delCacheUrl].lastUsed
- ) {
- delCacheUrl = key
- }
- })
- this.dispose(delCacheUrl)
- }
-
- // 追加
- this.#cache[url] = {
- img: img,
- lastUsed: Date.now(),
- }
- }
-
- /****************************************
- * 画像が存在するか
- * @param {string} url URL
- */
- static has(url) {
- return this.#cache.hasOwnProperty(url)
- }
-
- /****************************************
- * 画像を取得
- * @param {string} url URL
- * @returns {Promise<HTMLImageElement>} 画像
- */
- static async get(url) {
- return new Promise(async (resolve, reject) => {
- if (this.has(url)) {
- this.#cache[url].lastUsed = Date.now()
- resolve(this.#cache[url].img)
- } else {
- try {
- let img = new Image()
- img.addEventListener('load', ({ target }) => {
- if (target instanceof HTMLImageElement) {
- this.add(target.src, target)
- resolve(this.#cache[target.src].img)
- } else {
- reject()
- }
- })
- img.addEventListener('error', reject)
- img.src = url
- img = null
- } catch (e) {
- reject(e)
- }
- }
- })
- }
-
- /****************************************
- * 画像を解放
- * @param {string} url URL
- */
- static dispose(url) {
- if (this.has(url)) {
- this.#cache[url].img.remove()
- delete this.#cache[url]
- }
- }
- }
-
- /****************************************
- * @classdesc `FlowCommentsItem`用の画像クラス
- */
- class FlowCommentsImage {
- /**
- * URL
- * @type {string}
- */
- #url
- /**
- * 代替テキスト
- * @type {string}
- */
- #alt
-
- /****************************************
- * コンストラクタ
- * @param {string} url URL
- * @param {string} [alt] 代替テキスト
- */
- constructor(url, alt) {
- this.#url = url
- this.#alt = alt
- }
-
- get url() { return this.#url }
- get alt() { return this.#alt }
-
- /****************************************
- * 画像を取得
- * @returns {Promise<HTMLImageElement | string>}
- */
- async get() {
- try {
- return (await FlowCommentsImageCache.get(this.#url))
- } catch (e) {
- return this.#alt
- }
- }
- }
-
- /****************************************
- * @classdesc 流すコメント
- * @example
- * // idを指定する場合
- * const fcItem1 = new FlowCommentsItem('1518633760656605184', 'ウルトラソウッ')
- * // idを指定しない場合
- * const fcItem2 = new FlowCommentsItem(Symbol(), 'みどらんかわいい!')
- */
- class FlowCommentsItem {
- /**
- * コメントID
- * @type {string | number | symbol}
- */
- #id
- /**
- * コメント本文
- * @type {Array<string | FlowCommentsImage>}
- */
- #content
- /**
- * オプション
- * @type {FlowCommentsItemOption}
- */
- #option
- /**
- * スタイル
- * @type {FlowCommentsStyle}
- */
- #style
- /**
- * 実際の表示時間
- * @type {number}
- */
- #actualDuration
- /**
- * コメント単体を描画したCanvas
- * @type {HTMLCanvasElement}
- */
- #canvas
-
- /**
- * 座標
- * @type {{ x: number; y: number; xp: number; offsetY: number; }}
- */
- position = {
- x: 0,
- y: 0,
- xp: 0,
- offsetY: 0,
- }
- /**
- * 描画サイズ
- * @type {{ width: number; height: number; }}
- */
- size = {
- width: 0,
- height: 0,
- }
- /**
- * 実際に流すときの距離
- * @type {number}
- */
- scrollWidth = 0
- /**
- * 行番号
- * @type {number}
- */
- line = 0
- /**
- * コメントを流し始めた時間
- * @type {number}
- */
- startTime = null
-
- /****************************************
- * コンストラクタ
- * @param {string | number | symbol} id コメントID
- * @param {Array<string | FlowCommentsImage>} content コメント本文
- * @param {FlowCommentsItemOption} [option] オプション
- * @param {FlowCommentsStyle} [style] スタイル
- */
- constructor(id, content, option, style) {
- FlowCommentsUtil.filterObject(option)
- FlowCommentsUtil.filterObject(style)
- this.#id = id
- this.#content = Array.isArray(content) ? content.filter(v => v) : content
- this.#style = style
- this.#option = { ...FLOWCMTITEM_DEFAULT_OPTION, ...option }
- if (this.#option.position === FLOWCMT_TYPE.FLOW) {
- this.#actualDuration = this.#option.duration * 1.5
- }
- this.#canvas = document.createElement('canvas')
- }
-
- get id() { return this.#id }
- get content() { return this.#content }
- get style() { return this.#style }
- get option() { return this.#option }
- get actualDuration() { return this.#actualDuration }
- get canvas() { return this.#canvas }
-
- get top() { return this.position.y }
- get bottom() { return this.position.y + this.size.height }
- get left() { return this.position.x }
- get right() { return this.position.x + this.size.width }
-
- get rect() {
- return {
- width: this.size.width,
- height: this.size.height,
- top: this.top,
- bottom: this.bottom,
- left: this.left,
- right: this.right,
- }
- }
-
- dispose() {
- this.#canvas.remove()
-
- this.#id = null
- this.#content = null
- this.#style = null
- this.#option = null
- this.#actualDuration = null
- this.#canvas = null
-
- Object.keys(this).forEach(k => delete this[k])
- }
- }
-
- /****************************************
- * @classdesc コメントを流すやつ
- * @example
- * // 準備
- * const fc = new FlowComments()
- * document.body.appendChild(fc.canvas)
- * fc.start()
- *
- * // コメントを流す(追加する)
- * fc.pushComment(new FlowCommentsItem(Symbol(), 'Hello world!'))
- */
- class FlowComments {
- /**
- * インスタンスに割り当てられるIDのカウント用
- * @type {number}
- */
- static #id_cnt = 0
-
- /**
- * インスタンスに割り当てられるID
- * @type {number}
- */
- #id
- /**
- * `requestAnimationFrame`の`requestID`
- * @type {number}
- */
- #animReqId = null
- /**
- * Canvas
- * @type {HTMLCanvasElement}
- */
- #canvas
- /**
- * CanvasRenderingContext2D
- * @type {CanvasRenderingContext2D}
- */
- #context2d
- /**
- * 現在表示中のコメント
- * @type {Array<FlowCommentsItem>}
- */
- #comments
- /**
- * オプション
- * @type {FlowCommentsOption}
- */
- #option
- /**
- * スタイル
- * @type {FlowCommentsStyle}
- */
- #style
- /**
- * @type {ResizeObserver}
- */
- #resizeObs
-
- /****************************************
- * コンストラクタ
- * @param {FlowCommentsOption} [option] オプション
- * @param {FlowCommentsStyle} [style] スタイル
- */
- constructor(option, style) {
- // 初期化
- this.initialize(option, style)
- }
-
- get id() { return this.#id }
- get style() { return { ...FLOWCMT_DEFAULT_STYLE, ...this.#style } }
- get option() { return { ...FLOWCMT_DEFAULT_OPTION, ...this.#option } }
- get canvas() { return this.#canvas }
- get context2d() { return this.#context2d }
- get comments() { return this.#comments }
-
- get lineHeight() { return this.#canvas.height / this.option.lines }
- get fontSize() { return this.lineHeight * FLOWCMT_CONFIG.FONT_SCALE }
-
- get isStarted() { return this.#animReqId !== null }
-
- /****************************************
- * 初期化(インスタンス生成時には不要)
- * @param {FlowCommentsOption} [option] オプション
- * @param {FlowCommentsStyle} [style] スタイル
- */
- initialize(option, style) {
- this.dispose()
-
- // ID割り当て
- this.#id = ++FlowComments.#id_cnt
-
- // Canvas生成
- this.#canvas = document.createElement('canvas')
- this.#canvas.classList.add(FLOWCMT_CONFIG.CANVAS_CLASSNAME)
- this.#canvas.dataset.fcid = this.#id.toString()
-
- // CanvasRenderingContext2D
- this.#context2d = this.#canvas.getContext('2d')
-
- // コメント一覧
- this.#comments = []
-
- // サイズ変更を監視
- this.#resizeObs = new ResizeObserver(entries => {
- entries.forEach(entry => {
- const { width, height } = entry.contentRect
-
- // Canvasのサイズ(比率)を自動で調整
- if (this.option.autoResize) {
- const rect_before = this.#canvas.width / this.#canvas.height
- const rect_resized = width / height
- if (0.01 < Math.abs(rect_before - rect_resized)) {
- this.resizeCanvas()
- }
- }
-
- // Canvasの解像度を自動で調整
- if (this.option.autoResolution) {
- const resolution = FLOWCMT_CONFIG.RESOLUTION_LIST.find(v => height <= v)
- if (Number.isFinite(resolution) && this.option.resolution !== resolution) {
- this.changeOption({ resolution: resolution })
- }
- }
- })
- })
- this.#resizeObs.observe(this.#canvas)
-
- // オプションをセット
- this.changeOption(option)
- // スタイルをセット
- this.changeStyle(style)
- }
-
- /****************************************
- * オプションを変更
- * @param {FlowCommentsOption} option オプション
- */
- changeOption(option) {
- FlowCommentsUtil.filterObject(option)
- this.#option = { ...this.#option, ...option }
- if (option !== undefined && option !== null) {
- this.resizeCanvas()
- }
- }
-
- /****************************************
- * スタイルを変更
- * @param {FlowCommentsStyle} [style] スタイル
- */
- changeStyle(style) {
- FlowCommentsUtil.filterObject(style)
- this.#style = { ...this.#style, ...style }
- if (style !== undefined && style !== null) {
- this.#updateCanvasStyle()
- }
- }
-
- /****************************************
- * Canvasをリサイズ
- */
- resizeCanvas() {
- // Canvasをリサイズ
- const { width, height } = this.#canvas.getBoundingClientRect()
- const { resolution } = this.option
- const ratio = (width === 0 && height === 0) ? FLOWCMT_CONFIG.CANVAS_RATIO : (width / height)
- this.#canvas.width = resolution * ratio
- this.#canvas.height = resolution
-
- // Canvasのスタイルをリセット
- this.#updateCanvasStyle()
- }
-
- /****************************************
- * Canvasのスタイルを更新
- */
- #updateCanvasStyle() {
- // スタイルを適用
- FlowCommentsUtil.setStyleToCanvas(
- this.#context2d, this.style, this.option.resolution, this.fontSize
- )
-
- // Canvasをリセット
- this.#context2d.clearRect(0, 0, this.#canvas.width, this.#canvas.height)
- // コメントの各プロパティを再計算・描画
- this.#comments.forEach(cmt => {
- this.#generateCommentsItemCanvas(cmt)
- this.#renderComment(cmt)
- })
- }
-
- /****************************************
- * Canvasのスタイルをリセット
- */
- resetCanvasStyle() {
- this.changeStyle(FLOWCMT_DEFAULT_STYLE)
- }
-
- /****************************************
- * 端数処理
- * @param {number} num
- */
- #floor(num) {
- return this.#option.smoothRender ? num : (num | 0)
- }
-
- /****************************************
- * コメントの単体のCanvasを生成
- * @param {FlowCommentsItem} comment コメント
- */
- async #generateCommentsItemCanvas(comment) {
- const ctx = comment.canvas.getContext('2d')
- ctx.clearRect(0, 0, comment.canvas.width, comment.canvas.height)
-
- const style = { ...this.style, ...comment.style }
- const drawFontSize = this.fontSize * style.fontScale
- const margin = drawFontSize * FLOWCMT_CONFIG.TEXT_MARGIN
-
- // スタイルを適用
- FlowCommentsUtil.setStyleToCanvas(
- ctx, style, this.option.resolution, this.fontSize
- )
-
- /** @type {Array<number>} */
- const aryWidth = []
-
- //----------------------------------------
- // サイズを計算
- //----------------------------------------
- for (const cont of comment.content) {
- // 文字列
- if (typeof cont === 'string') {
- aryWidth.push(ctx.measureText(cont).width)
- }
- // 画像
- else if (cont instanceof FlowCommentsImage) {
- const img = await cont.get()
- if (img instanceof HTMLImageElement) {
- const ratio = img.width / img.height
- aryWidth.push(drawFontSize * ratio)
- } else if (img !== undefined) {
- aryWidth.push(ctx.measureText(img).width)
- } else {
- aryWidth.push(1)
- }
- }
- }
-
- // コメントの各プロパティを計算
- comment.size.width = aryWidth.reduce((a, b) => a + b)
- comment.size.width += margin * (aryWidth.length - 1)
- comment.size.height = this.lineHeight
- comment.scrollWidth = this.#canvas.width + comment.size.width
- comment.position.x = this.#canvas.width - comment.scrollWidth * comment.position.xp
- comment.position.y = this.lineHeight * comment.line
- comment.position.offsetY = this.lineHeight / 2 * (1 + FLOWCMT_CONFIG.FONT_OFFSET_Y)
-
- // Canvasのサイズを設定
- comment.canvas.width = comment.size.width
- comment.canvas.height = comment.size.height
-
- // スタイルを再適用(上でリセットされる)
- FlowCommentsUtil.setStyleToCanvas(
- ctx, style, this.option.resolution, this.fontSize
- )
-
- //----------------------------------------
- // コメントを描画
- //----------------------------------------
- let dx = 0
- for (let idx = 0; idx < comment.content.length; idx++) {
- if (0 < idx) {
- dx += margin
- }
- const cont = comment.content[idx]
- // 文字列
- if (typeof cont === 'string') {
- ctx.fillText(
- cont,
- this.#floor(dx), this.#floor(comment.position.offsetY)
- )
- }
- // 画像
- else if (cont instanceof FlowCommentsImage) {
- const img = await cont.get()
- if (img instanceof HTMLImageElement) {
- ctx.drawImage(
- img,
- this.#floor(dx), this.#floor((comment.size.height - drawFontSize) / 2),
- this.#floor(aryWidth[idx]), this.#floor(drawFontSize)
- )
- } else if (img !== undefined) {
- ctx.fillText(
- img,
- this.#floor(dx), this.#floor(comment.position.offsetY)
- )
- } else {
- ctx.fillText(
- '',
- this.#floor(dx), this.#floor(comment.position.offsetY)
- )
- }
- }
- dx += aryWidth[idx]
- }
- }
-
- /****************************************
- * コメントを追加(流す)
- * @param {FlowCommentsItem} comment コメント
- */
- async pushComment(comment) {
- if (this.#animReqId === null || document.visibilityState === 'hidden') return
-
- //----------------------------------------
- // 画面内に表示するコメントを制限
- //----------------------------------------
- if (0 < this.option.limit && this.option.limit <= this.#comments.length) {
- this.#comments.splice(0, this.#comments.length - this.option.limit)[0]
- }
-
- //----------------------------------------
- // コメントの各プロパティを計算
- //----------------------------------------
- await this.#generateCommentsItemCanvas(comment)
-
- //----------------------------------------
- // コメント表示行を計算
- //----------------------------------------
- const spd_pushCmt = comment.scrollWidth / comment.option.duration
-
- // [[0, 0], [1, 0], ~ , [10, 0]] ([line, cnt])
- const lines_over = [...Array(this.option.lines)].map((_, i) => [i, 0])
-
- this.#comments.forEach(cmt => {
- // 残り表示時間
- const leftTime = cmt.option.duration * (1 - cmt.position.xp)
- // コメント追加時に重なる or 重なる予定かどうか
- const isOver =
- comment.left - spd_pushCmt * leftTime <= 0 ||
- comment.left <= cmt.right
- if (isOver && cmt.line < this.option.lines) {
- lines_over[cmt.line][1]++
- }
- })
-
- // 重なった頻度を元に昇順で並べ替える
- const lines_sort = lines_over.sort(([, cntA], [, cntB]) => cntA - cntB)
-
- comment.line = lines_sort[0][0]
- comment.position.y = this.lineHeight * comment.line
-
- //----------------------------------------
- // コメントを追加
- //----------------------------------------
- this.#comments.push(comment)
- }
-
- /****************************************
- * テキストを描画
- * @param {FlowCommentsItem} comment コメント
- */
- #renderComment(comment) {
- this.#context2d.drawImage(
- comment.canvas,
- this.#floor(comment.position.x), this.#floor(comment.position.y)
- )
- }
-
- /****************************************
- * ループ中に実行される処理
- * @param {number} time 時間
- */
- #update(time) {
- // Canvasをリセット
- this.#context2d.clearRect(0, 0, this.#canvas.width, this.#canvas.height)
-
- this.#comments.forEach((cmt, idx, ary) => {
- // コメントを流し始めた時間
- if (cmt.startTime === null) {
- cmt.startTime = time
- }
-
- // コメントを流し始めて経過した時間
- const elapsedTime = time - cmt.startTime
-
- if (elapsedTime <= cmt.actualDuration) {
- // コメントの座標を更新(流すコメント)
- if (cmt.option.position === FLOWCMT_TYPE.FLOW) {
- cmt.position.xp = elapsedTime / cmt.option.duration
- cmt.position.x = this.#canvas.width - cmt.scrollWidth * cmt.position.xp
- }
- // コメントを描画
- this.#renderComment(cmt)
- } else {
- // 表示時間を超えたら消す
- cmt.dispose()
- ary.splice(idx, 1)[0]
- }
- })
- }
-
- /****************************************
- * ループ処理
- * @param {number} time 時間
- */
- #loop(time) {
- this.#update(time)
- if (this.#animReqId !== null) {
- this.#animReqId = window.requestAnimationFrame(this.#loop.bind(this))
- }
- }
-
- /****************************************
- * コメント流しを開始
- */
- start() {
- if (this.#animReqId === null) {
- this.#animReqId = window.requestAnimationFrame(this.#loop.bind(this))
- }
- }
-
- /****************************************
- * コメント流しを停止
- */
- stop() {
- if (this.#animReqId !== null) {
- window.cancelAnimationFrame(this.#animReqId)
- this.#animReqId = null
- }
- }
-
- /****************************************
- * 解放(初期化してCanvasを削除)
- */
- dispose() {
- this.stop()
-
- this.#canvas?.remove()
- this.#resizeObs?.disconnect()
-
- this.#id = null
- this.#animReqId = null
- this.#canvas = null
- this.#context2d = null
- this.#comments = null
- this.#style = null
- this.#option = null
- this.#resizeObs = null
-
- Object.keys(this).forEach(k => delete this[k])
- }
- }