Screen Display Log

ipadやiphone用に、画面にログを出力する。console.log() の代わりに screenLog() で利用する。

当前为 2026-05-10 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/577436/1820905/Screen%20Display%20Log.js

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Screen Display Log
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  ipadやiphone用に、画面にログを出力する。console.log() の代わりに screenLog() で利用する。
// @grant        none
// ==/UserScript==

/**
 * 画面端にデバッグ用UIを表示するクラス
 */
class ScreenLogger {
    constructor() {
        this.id = 'tm-debug-console';
        this.container = null;
        this.logArea = null;
        this._setup();
    }

    /**
     * 初期セットアップ
     */
    _setup() {
        if (document.getElementById(this.id)) return;
        this._injectStyles();
        this._createUI();
    }

    /**
     * CSSを<style>タグとして注入(インラインスタイルの撤廃)
     */
    _injectStyles() {
        const style = document.createElement('style');
        style.textContent = `
            #${this.id} { position: fixed; bottom: 10px; right: 10px; width: 280px; max-height: 250px; background-color: rgba(0, 0, 0, 0.85); color: #00ff00; font-size: 11px; font-family: monospace; padding: 5px; border-radius: 5px; overflow-y: auto; z-index: 999999; pointer-events: auto; border: 1px solid #444; box-shadow: 0 0 10px rgba(0,0,0,0.5); }
            #${this.id} .tm-header { border-bottom: 1px solid #444; margin-bottom: 5px; padding-bottom: 3px; display: flex; justify-content: space-between; align-items: center; }
            #${this.id} .tm-header b { color: #fff; }
            #${this.id} button { font-size: 9px; cursor: pointer; margin-left: 4px; }
            #${this.id} .tm-log-area { display: block; }
            #${this.id} .tm-line { border-bottom: 1px solid #222; padding: 2px 0; white-space: pre-wrap; word-break: break-all; }
            #${this.id}.minimized { height: 25px; overflow: hidden; }
            #${this.id}.minimized .tm-log-area { display: none; }
        `;
        document.head.appendChild(style);
    }

    /**
     * UI要素の構築
     */
    _createUI() {
        this.container = document.createElement('div');
        this.container.id = this.id;

        const header = document.createElement('div');
        header.className = 'tm-header';
        header.innerHTML = `
            <span><b>[DEBUG]</b></span>
            <div>
                <button id="tm-clear-log">Clear</button>
                <button id="tm-toggle-log">Min</button>
            </div>
        `;

        this.logArea = document.createElement('div');
        this.logArea.className = 'tm-log-area';

        this.container.appendChild(header);
        this.container.appendChild(this.logArea);
        document.body.appendChild(this.container);

        // イベントリスナーの登録
        this.container.querySelector('#tm-clear-log').onclick = () => this.clear();
        this.container.querySelector('#tm-toggle-log').onclick = (e) => this._toggle(e.target);
    }

    /**
     * 表示・非表示の切り替え
     */
    _toggle(btn) {
        const isMinimized = this.container.classList.toggle('minimized');
        btn.innerText = isMinimized ? 'Max' : 'Min';
    }

    /**
     * ログの消去
     */
    clear() {
        if (this.logArea) this.logArea.innerHTML = '';
    }

    /**
     * 引数を文字列に整形(HTML要素、オブジェクト対応)
     */
    _format(arg) {
        if (arg === null) return 'null';
        if (arg === undefined) return 'undefined';

        if (arg instanceof HTMLElement) {
            const id = arg.id ? `#${arg.id}` : '';
            const classes = arg.className ? `.${Array.from(arg.classList).join('.')}` : '';
            return `<${arg.tagName.toLowerCase()}${id}${classes}>`;
        }

        if (typeof arg === 'object') {
            try {
                return JSON.stringify(arg);
            } catch (e) {
                return String(arg);
            }
        }

        return String(arg);
    }

    /**
     * 画面にログを出力
     */
    log(...args) {
        const msg = args.map(arg => this._format(arg)).join(' ');
        const line = document.createElement('div');
        line.className = 'tm-line';
        line.innerText = `> ${msg}`;

        this.logArea.appendChild(line);
        this.container.scrollTop = this.container.scrollHeight;
    }
}

/**
 * 外部から呼び出すためのエントリーポイント
 */
function initOnScreenConsole() {
    const logger = new ScreenLogger();
    // 既存の「関数として呼び出す」挙動を維持するため、メソッドをバインドして返す
    return logger.log.bind(logger);
}

// 利用開始
const screenLog = initOnScreenConsole();