プログラミング C# - 翔ソフトウェア (Sho's)

C#/.NET/ソフトウェア開発など

Lightningtalks Timer by HTML5

lightningtalkstimer.png

以前作った HTML5 用のライトニングトーク用のタイマーを、TypeScript で書きなおしてみた。

TypeScript を使うことで、よりすっきりと書ける。また、Visual Studio でのインテリセンスや静的な型チェックが行える為、とても楽だ。

lightningtalktimer.ts

TypeScript コンパイラーによって lightningtalktimer.js に変換される。

// for lightningtalktimer.html
// generated from lightningtalktimer.ts
// Ver.1.01 2014-01-17
// Copyright © 2014 Sho's Software by Fujiwo http://www.shos.info

module LightningTalkTimer {
    export class Application {
        private static timer = null;

        private static get canvas(): HTMLCanvasElement {
            return <HTMLCanvasElement>document.querySelector("canvas");
        }

        private static get context(): CanvasRenderingContext2D {
            return <CanvasRenderingContext2D>Application.canvas.getContext("2d");
        }

        static run() {
            Application.sizing();
            Application.timer = new Timer(this.context, this.canvas.width, this.canvas.height);
        }

        static onClick() {
            Application.timer.start();
        }

        private static sizing() {
            var canvas    = Application.canvas;
            canvas.height = window.innerHeight;
            canvas.width  = window.innerWidth ;
        }
    }

    class Utility {
        static getQuerystring(key: string, default_: string = null): string {
            if (default_ == null)
                default_ = "";
            key             = key.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
            var regex       = new RegExp("[\\?&]" + key + "=([^&#]*)");
            var queryString = regex.exec(window.location.href);
            return queryString == null ? default_ : queryString[1];
        }

        static secondsToString(totalSeconds: number): string {
            var minutes     = Math.floor(totalSeconds / 60);
            var seconds     = totalSeconds % 60;
            var secondsText = (seconds >= 10) ? seconds.toString() : "0" + seconds;
            return minutes.toString() + ':' + secondsText;
        }

        static getCurrentMilliSeconds(): number {
            return Date.parse(new Date().toString());
        }
    }

    class Parameter {
        maxSeconds       = 60 * 5;
        dangerousSeconds = 60 * 1;
        fontHeightRate   = 70;

        constructor() {
            this.setMaxSeconds      ();
            this.setDangerousSeconds();
            this.setFontHeight      ();
        }

        private setFontHeight() {
            var fontHeightRateText = Utility.getQuerystring("font");
            if (fontHeightRateText != "") {
                var heightRate = parseInt(fontHeightRateText);
                if (heightRate > 0 && heightRate <= 100)
                    this.fontHeightRate = heightRate;
            }
        }

        private setMaxSeconds() {
            var maxSecondsText = Utility.getQuerystring("max");
            if (maxSecondsText != "") {
                var seconds = parseInt(maxSecondsText);
                if (seconds > 0) {
                    this.maxSeconds = seconds;
                    if (this.dangerousSeconds > this.maxSeconds)
                        this.dangerousSeconds = this.maxSeconds;
                }
            }
        }

        private setDangerousSeconds() {
            var dangerousSecondsText = Utility.getQuerystring("danger");
            if (dangerousSecondsText != "") {
                var seconds = parseInt(dangerousSecondsText);
                if (seconds >= 0 && seconds <= this.maxSeconds)
                    this.dangerousSeconds = seconds;
            }
        }
    }

    class View {
        private static normalDarkColor     = "#100";
        private static normalLightColor    = "#a99";
        private static dangerousDarkColor  = "#e00";
        private static dangerousLightColor = "#f99";
        private static shadowColor         = "gray";

        private context: CanvasRenderingContext2D;

        constructor(context: CanvasRenderingContext2D) {
            this.context = context;
        }

        draw(text: string, isNormal: boolean, width: number, height: number, fontHeightRate: number) {
            this.context.clearRect(0, 0, width, height);
            View.drawText(this.context, text, View.getDarkColor(isNormal), View.getLightColor(isNormal), width, height, fontHeightRate);
        }

        private static drawText(context: CanvasRenderingContext2D, text: string, colorDark: string, colorLight: string, width: number, height: number, fontHeightRate: number) {
            var fontHeight = height * fontHeightRate / 100;
            var y          = fontHeight * (16 + 1) / 16;
            context.font   = fontHeight + "px 'メイリオ',Meiryo,'ヒラギノ角ゴ Pro W3','Hiragino Kaku Gothic Pro',Calibri,sans-serif";
            View.setGradient(context, fontHeight, colorDark, colorLight);
            View.setShadow  (context, fontHeight                       );
            context.fillText(text, fontHeight / 12, y);
        }

        private static getDarkColor(isNormal: boolean): string {
            return isNormal ? View.normalDarkColor : View.dangerousDarkColor;
        }

        private static getLightColor(isNormal: boolean): string {
            return isNormal ? View.normalLightColor : View.dangerousLightColor;
        }

        private static setGradient(context: CanvasRenderingContext2D, fontHeight: number, darkColor: string, lightColor: string) {
            var gradient      = context.createLinearGradient(0, 0, 0, fontHeight);
            gradient.addColorStop(0.5, darkColor);
            gradient.addColorStop(0.6, lightColor);
            gradient.addColorStop(0.7, darkColor);
            context.fillStyle = gradient;
        }

        private static setShadow(context: CanvasRenderingContext2D, fontHeight: number) {
            context.shadowColor   = View.shadowColor;
            context.shadowOffsetX = fontHeight / 36;
            context.shadowOffsetY = fontHeight / 36;
            context.shadowBlur    = fontHeight / 48;
        }
    }

    class Timer {
        private width    : number;
        private height   : number;
        private parameter: Parameter;
        private view     : View;
        private timerID =  0;

        constructor(context: CanvasRenderingContext2D, width: number, height: number) {
            this.width     = width  ;
            this.height    = height ;
            this.parameter = new Parameter();
            this.view      = new View(context);
            this.draw(this.parameter.maxSeconds, this.isNormal(this.parameter.maxSeconds));
        }

        private start() {
            this.reset();
            var start    = Utility.getCurrentMilliSeconds();
            this.timerID = window.setInterval*1;
            if (remainingSeconds == 0)
                window.clearInterval(this.timerID);
        }

        private reset() {
            window.clearInterval(this.timerID);
            this.draw(this.parameter.maxSeconds, this.isNormal(this.parameter.maxSeconds));
        }

        private draw(seconds: number, isNormal: boolean) {
            this.view.draw(Utility.secondsToString(seconds), isNormal, this.width, this.height, this.parameter.fontHeightRate);
        }

        private isNormal(remainingSeconds: number): boolean {
            return remainingSeconds > this.parameter.dangerousSeconds;
        }
    }
}

window.onload = () => {
    LightningTalkTimer.Application.run();
};

lightningtalktimer.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,minimum-scale=1, maximum-scale=1, initial-scale=1">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <title>Sho's Lightning Talks Timer by HTML5</title>
    <style>
        html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, menu, nav, section, menu, time, mark, audio, video {
            margin: 0;
            padding: 0;
            border: 0;
            outline: 0;
            font-size: 100%;
            vertical-align: baseline;
            background: transparent;
        }

        article, aside, dialog, figure, footer, header, hgroup, nav, section {
            display: block;
        }

        body {
            width: 100%;
            overflow: hidden;
        }
    </style>
    <script src="lightningtalktimer.js"></script>
</head>
<body>
    <canvas width="500" height="500" onclick="LightningTalkTimer.Application.onClick()"></canvas>
</body>
</html>

<!--
Usage:
    Click: reset and start counting down.

    lightningtalktimer.html    	               : max = 300 seconds, dangerous =  60 seconds, font height 70%

    lightningtalktimer.html?max=100&danger=50  : max = 100 seconds, dangerous =  50 seconds
    lightningtalktimer.html?max=100&danger=100 : max = 100 seconds, dangerous = 100 seconds
    lightningtalktimer.html?max=100	           : max = 100 seconds, dangerous =  60 seconds
    lightningtalktimer.html?max=10	           : max =	10 seconds, dangerous =  10 seconds
    lightningtalktimer.html?danger=0	       : max = 300 seconds, dangerous =   0 seconds

    lightningtalktimer.html?font=50	           : font height 50%

    - This HTML can work with lightningtalktimer.js online or offline.

Ver.1.01 2014-01-17
Copyright © 2014 Sho's Software by Fujiwo http://www.shos.info
-->

実際に動くもの

使い方

  • クリックするたびカウントダウン (デフォルト 5分)
  • 残り時間が少なくなると赤くなる (デフォルト 1分)
  • クエリー文字列:
    • max で、時間 (秒) の設定 (lightningtalktimer.html?max=100 で 100秒)
    • danger で、赤くなる時間 (秒) の設定 (lightningtalktimer.html?danger=10 で 10秒、lightningtalktimer.html?danger=0 だと最後だけ赤くなる)
    • font で、文字サイズ (%) の設定 (lightningtalktimer.html?font=90 で文字の高さが最大値の90%)
  • Internet ExplorerFirefoxSafariGoogle ChromeOpera の現時点での最新版で動作
  • iPhoneAndroid フォン等スマートフォンで動作

関連記事

*1:) => this.update(start), 1000); } private update(start: number) { var now = Utility.getCurrentMilliSeconds(); var remainingSeconds = this.parameter.maxSeconds - (now - start) / 1000; if (remainingSeconds < 0) remainingSeconds = 0; this.draw(remainingSeconds, this.isNormal(remainingSeconds