此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/444119/1044798/FlowComments.js
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
(我已經安裝了使用者樣式管理器,讓我安裝!)
- // ==UserScript==
- // @name FlowComments
- // @namespace midra.me
- // @version 0.0.1
- // @description コメントをニコニコ風に流すやつ
- // @author Midra
- // @license MIT
- // @compatible chrome >=84
- // @grant none
- // ==/UserScript==
-
- /* jshint esversion: 6 */
-
- 'use strict'
-
- /****************************************
- * 型定義
- * @typedef {{
- * resolution: number,
- * opacity: number,
- * limit: number,
- * }} FlowCommentItemOption
- *
- * @typedef {{
- * resolution: number,
- * opacity: number,
- * limit: number,
- * }} FlowCommentOption
- */
-
- /****************************************
- * @classdesc 流すコメント
- * @example
- * // idを指定する場合
- * const fcItem1 = new FlowCommentItem('1518633760656605184', 'ウルトラソウッ')
- * // idを指定しない場合
- * const fcItem2 = new FlowCommentItem(Symbol(), 'うんち!')
- */
- class FlowCommentItem {
- /**
- * コメントID
- * @type {string | number | symbol}
- */
- #id
- /**
- * コメント本文
- * @type {string}
- */
- #text
- /**
- * X座標
- * @type {number}
- */
- x = 0
- /**
- * X座標(割合)
- * @type {number}
- */
- xp = 0
- /**
- * Y座標
- * @type {number}
- */
- y = 0
- /**
- * コメントの幅
- * @type {number}
- */
- width = 0
- /**
- * コメントの高さ
- * @type {number}
- */
- height = 0
- /**
- * 実際に流すときの距離
- * @type {number}
- */
- scrollWidth = 0
- /**
- * 行番号
- * @type {number}
- */
- line = 0
- /**
- * 表示時間(期間)
- * @type {number}
- */
- lifetime = 6000
- /**
- * コメントを流し始めた時間
- * @type {number}
- */
- startTime
-
- /****************************************
- * コンストラクタ
- * @param {string | number | symbol} id コメントID
- * @param {string} text コメント本文
- * @param {?FlowCommentItemOption} option オプション
- */
- constructor(id, text, option) {
- this.#id = id
- this.#text = text
- }
-
- get id() { return this.#id }
- get text() { return this.#text }
-
- get top() { return this.y }
- get bottom() { return this.y + this.height }
- get left() { return this.x }
- get right() { return this.x + this.width }
- }
-
- /****************************************
- * @classdesc コメントを流すやつ
- * @example
- * // 準備
- * const fc = new FlowComments()
- * document.body.appendChild(fc.canvas)
- * fc.start()
- *
- * // コメントを流す(追加する)
- * fc.pushComment(new FlowCommentItem(Symbol(), 'Hello, world!'))
- */
- class FlowComments {
- /**
- * インスタンスに割り当てられるIDのカウント用
- * @type {number}
- */
- static #id_cnt = 0
- /**
- * インスタンスに割り当てられるID
- * @type {number}
- */
- #id
- /**
- * オプション
- * @type {FlowCommentOption}
- */
- #options = {
- resolution: 720,
- opacity: 1,
- limit: undefined,
- }
- /**
- * Canvas
- * @type {HTMLCanvasElement}
- */
- #canvas
- /**
- * CanvasRenderingContext2D
- * @type {CanvasRenderingContext2D}
- */
- #context2d
- /**
- * 現在表示中のコメント
- * @type {Array<FlowCommentItem>}
- */
- #comments
- /**
- * `AnimationFrame`の`requestID`
- * @type {number}
- */
- #_animReqId
-
- /****************************************
- * コンストラクタ
- * @param {FlowCommentOption} options オプション
- */
- constructor(options) {
- this.#id = ++FlowComments.#id_cnt
- this.#canvas = document.createElement('canvas')
- this.#canvas.classList.add('mid-FlowComment')
- this.#canvas.dataset.fcid = this.#id
- this.#context2d = this.#canvas.getContext('2d')
- this.initialize(options)
- }
-
- get id() { return this.#id }
- get options() { return this.#options }
- get canvas() { return this.#canvas }
- get context2d() { return this.#context2d }
- get comments() { return this.#comments }
-
- get lineHeight() { return this.#canvas.height / 11.4 }
- get lineSpace() { return this.lineHeight * 0.4 }
- get fontSize() { return this.lineHeight - this.lineSpace * 0.5 }
- get fontFamily() {
- return 'Arial,"MS Pゴシック","MS PGothic",MSPGothic,MS-PGothic,Gulim,"黑体",SimHei'
- }
-
- /****************************************
- * 初期化(インスタンス生成時には不要)
- * @param {FlowCommentOption} options オプション
- */
- initialize(options) {
- // オプションを設定
- if (options !== undefined) {
- if (options.resolution !== undefined) {
- this.#options.resolution = options.resolution
- }
- if (options.opacity !== undefined) {
- this.#options.opacity = options.opacity
- }
- if (options.limit !== undefined) {
- this.#options.limit = options.limit
- }
- }
-
- this.stop()
- this.#comments = []
- this.#_animReqId = undefined
- this.initializeCanvas()
- }
-
- /****************************************
- * Canvasの解像度を変更
- * @param {number} resolution 解像度
- */
- changeCanvasResolution(resolution) {
- if (Number.isFinite(resolution)) {
- this.#options.resolution = resolution
- this.initializeCanvas()
- }
- }
-
- /****************************************
- * CanvasRenderingContext2Dを初期化
- */
- initializeCanvas() {
- this.#resizeCanvas()
- this.#context2d.clearRect(0, 0, this.#canvas.width, this.#canvas.height)
- this.#context2d.font = `600 ${this.fontSize}px ${this.fontFamily}`
- this.#context2d.lineJoin = 'round'
- this.#context2d.fillStyle = '#fff'
- this.#context2d.shadowColor = '#000'
- this.#context2d.shadowBlur = this.#options.resolution / 200
- this.#comments.forEach(this.#calcCommentProperty.bind(this))
- }
-
- /****************************************
- * CanvasRenderingContext2Dをリサイズ
- */
- #resizeCanvas() {
- const { width, height } = this.#canvas.getBoundingClientRect()
- const ratio = (width === 0 && height === 0) ? (16 / 9) : (width / height)
- this.#canvas.width = ratio * this.#options.resolution
- this.#canvas.height = this.#options.resolution
- }
-
- /****************************************
- * コメントの各プロパティを計算する
- * @param {FlowCommentItem} comment コメント
- */
- #calcCommentProperty(comment) {
- comment.width = this.#context2d.measureText(comment.text).width
- comment.scrollWidth = this.#canvas.width + comment.width
- comment.x = this.#canvas.width - comment.scrollWidth * comment.xp
- comment.y = this.lineHeight * comment.line
- }
-
- /****************************************
- * コメントを追加(流す)
- * @param {FlowCommentItem} comment コメント
- */
- pushComment(comment) {
- if (this.#_animReqId === undefined) return
-
- //----------------------------------------
- // 画面内に表示するコメントを制限
- //----------------------------------------
- if (this.#options.limit <= this.#comments.length) {
- this.#comments.splice(0, 1)
- }
-
- //----------------------------------------
- // コメントの各プロパティを計算
- //----------------------------------------
- this.#calcCommentProperty(comment)
-
- //----------------------------------------
- // コメント表示行を計算
- //----------------------------------------
- const spd_pushCmt = comment.scrollWidth / comment.lifetime
-
- // [[1, 2], [2, 1], ~ , [11, 1]] ([line, cnt])
- const lines_over = [...Array(11)].map((_, i) => [i + 1, 0])
-
- this.#comments.forEach(val => {
- // 残り表示時間
- const leftTime = val.lifetime * (1 - val.xp)
- // コメント追加時に重なる or 重なる予定かどうか
- const isOver =
- comment.left - spd_pushCmt * leftTime <= 0 ||
- comment.left <= val.right
- if (isOver) {
- lines_over[val.line - 1][1]++
- }
- })
-
- // 重なった頻度を元に昇順で並べ替える
- const lines_sort = lines_over.sort(([, cntA], [, cntB]) => cntA - cntB)
-
- comment.line = lines_sort[0][0]
- comment.y = this.lineHeight * comment.line
-
- //----------------------------------------
- // コメントを追加
- //----------------------------------------
- this.#comments.push(comment)
- }
-
- /****************************************
- * ループ中に実行される処理(描画)
- * @param {number} now 時間
- */
- #render(now) {
- // Canvasをリセット
- this.#context2d.clearRect(0, 0, this.#canvas.width, this.#canvas.height)
-
- const deleteIdx = []
- this.#comments.forEach((comment, idx) => {
- // コメントを流し始めた時間
- if (comment.startTime === undefined) {
- comment.startTime = now
- }
-
- // コメントを流し始めて経過した時間
- const diffTime = now - comment.startTime
-
- if (diffTime <= comment.lifetime * 1.5) {
- // コメントの座標を更新
- comment.xp = diffTime / comment.lifetime
- comment.x = this.#canvas.width - comment.scrollWidth * comment.xp
- // コメントを描画
- this.#context2d.fillText(comment.text, comment.x, comment.y)
- } else {
- // 表示時間を超えたら消す
- deleteIdx.push(idx)
- }
- })
- // 上のループが終わってから消さないと変な挙動になる
- deleteIdx.forEach(v => this.#comments.splice(v, 1))
- }
-
- /****************************************
- * ループ処理
- * @param {number} time 時間
- */
- #loop() {
- this.#render(window.performance.now())
- if (this.#_animReqId !== undefined) {
- this.#_animReqId = window.requestAnimationFrame(this.#loop.bind(this))
- }
- }
-
- /****************************************
- * コメント流しを開始
- */
- start() {
- if (this.#_animReqId === undefined) {
- this.#_animReqId = window.requestAnimationFrame(this.#loop.bind(this))
- }
- }
-
- /****************************************
- * コメント流しを停止
- */
- stop() {
- if (this.#_animReqId !== undefined) {
- window.cancelAnimationFrame(this.#_animReqId)
- this.#_animReqId = undefined
- }
- }
- }