input要素に対して針を動かして時刻入力するピッカーを作成

長針と短針どちらでも直接回せて、長針と短針それぞれ連動して動作する時刻入力ピッカーが欲しかったので自作してみました。

![timepicker_.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618708/8880ad2b-3c93-ca3c-d452-a5677dd7f7d2.gif)
####[実動サンプル](https://akebi.jp/temp/timepicker/)####

スマホでも操作可能です。
PCではマウスホイールにも対応していますが、ブラウザごとに異なる感度をまだ吸収しきれていませんのでこちらは補助的なものとお考えください。
キーボードでは方向キー左右で1分単位、上下で15分単位の送り戻し、SPACEでAM/PM切替、数値キーでの直接入力ができます。
文字盤の空白部分右半分、左半分をそれぞれクリックすることで1分単位の送り戻しも可能です。

###スクリプト###

~~~js:timepicker.js
"use strict";
{
    const
        timePickerParam = {
            // 主要パーツ デフォルトスタイル
            styles: {
                // サイズ
                width: 200,
                height: 200,
                // 外枠
                frame: {
                    color:  '#f0f0f0',      // 背景色
                },
                // 目盛り
                dial: {
                    color1:  '#c0c0c0',     // 1分 色
                    color5:  '#808080',     // 5分 色
                    color15: '#808080',     // 15分 色
                },
                // 時針(短針)
                hourHand: {
                    color:  '#666666',      // 色
                    length: '0.6',          // 長さ(ピッカー半径を1とした相対値)
                    width:  '0.1',          // 幅(ピッカー半径を1とした相対値)
                    offset: '0.05',         // 中心からのオフセット
                },
                // 分針(長針)
                minuteHand: {
                    color:  '#888888',      // 色
                    length: '1.0',          // 長さ
                    width:  '0.08',         // 幅
                    offset: '0.1',          // 中心からのオフセット
                },
                // closeボタン
                closeButton: {
                    color: '#a0a0a0',
                    background: '#e0e0e0',
                },
                // AM/PMボタン
                ampmButton: {
                    display:  '',           // noneで非表示
                    onColor:  '#000000',
                    offColor: '#c0c0c0',
                },
                // 時刻プレビュー
                preview: {
                    display: '',            // noneで非表示
                    color:   '#000000',
                },
            },
            // 操作開始後に無操作で非表示となる時間(ms)
            timeout: 5000,
            // 選択間隔 1～30(分)
            step: 1,
            // イベント制御ID用オブジェクト
            ids: {},
        },

        tpHandler = (function() {
            const events = {};
            let key = 1;
            return {
                add: function(target, type, listener, capture) {
                    target.addEventListener(type, listener, capture);
                    events[key] = {
                        target: target,
                        type: type,
                        listener: listener,
                        capture: capture
                    };
                    return key++;
                },
                remove: function(key) {
                    if(key in events) {
                        const e = events[key];
                        e.target.removeEventListener(e.type, e.listener, e.capture);
                        delete(events[key]);
                    }
                }
            };
        }());

    function tpSetStyle(width, height, styles) {
        if(styles === undefined) styles = {};
        const stylesDefault = JSON.parse(JSON.stringify(timePickerParam.styles));
        if(width === undefined) width = timePickerParam.styles.width;
        if(height === undefined) height = timePickerParam.styles.height;
        const sizeMin = Math.min(width, height);

        // ピッカー外枠
        let d = document.querySelector('#timepicker').style;
        let st = stylesDefault.frame;
        overRide(st, styles.frame);
        d.width = width + 'px';
        d.height = height + 'px';
        d.background = st.color;
        d.position = 'absolute';
        d.left = '50px';
        d.top = '50px';
        d.overflow = 'hidden';

        // 目盛り
        const cv = document.querySelector('#timepicker #tp_canvas');
        cv.width = width;
        cv.height = height;

        const ctx = cv.getContext('2d');
        d = cv.style;
        d.position = 'absolute';

        st = stylesDefault.dial;
        overRide(st, styles.dial);
        for(let i = 0; i < 60; i++) {
            ctx.beginPath();
            ctx.strokeStyle = i % 5 === 0 ?
                (i % 15 === 0 ? st.color15 : st.color5) : st.color1;
            const r = (Math.PI * 2) / 60 * i;
            const c = Math.cos(r);
            const s = Math.sin(r);
            const sr = (i % 15 === 0) ?
                sizeMin / 2 * .8 : (i % 5 === 0) ? sizeMin / 2 * .9 : sizeMin / 2 * .95;
            ctx.moveTo(width / 2 + c * sr, height / 2 + s * sr);
            ctx.lineTo(width / 2 + c * (sizeMin / 2 * .98), height / 2 + s * (sizeMin / 2 * .98));
            ctx.stroke();
        }

        st = stylesDefault.closeButton;
        overRide(st, styles.closeButton);

        // クローズボタン X記号
        ctx.beginPath();
        ctx.fillStyle = st.background;
        ctx.fillRect(width - width * 0.12, 0, width * 0.12, width * 0.12);
        ctx.strokeStyle = st.color;
        ctx.moveTo(width - width * 0.02, width * 0.02);
        ctx.lineTo(width - width * 0.1, width * 0.1);
        ctx.moveTo(width - width * 0.1, width * 0.02);
        ctx.lineTo(width - width * 0.02, width * 0.1);
        ctx.stroke();

        // closeボタン
        d = document.querySelector('#timepicker #tp_close').style;
        d.position = 'absolute';
        d.right = '0px';
        d.top = '0px';
        d.width = (width * 0.12) + 'px';
        d.height = (width * 0.12) + 'px';
        d.cursor = 'pointer';

        // 時刻プレビュー
        d = document.querySelector('#timepicker #tp_view').style;
        st = stylesDefault.preview;
        overRide(st, styles.preview);
        d.display = st.display;
        d.position = 'absolute';
        d.left = '2px';
        d.bottom = '2px';
        d.border = '0';
        d.padding = '0';
        d.background = 'rgba(0, 0, 0, 0)';
        d.color = st.color;

        // フォーカス制御用ダミー
        d = document.querySelector('#timepicker #tp_dummy').style;
        d.position = 'fixed';
        d.top = '-100px';
        if(timePickerParam.touchFlg) document.querySelector('#timepicker #tp_dummy').type = 'checkbox';

        // 時針
        d = document.querySelector('#timepicker #tp_hour').style;
        st = stylesDefault.hourHand;
        overRide(st, styles.hourHand);
        d.background = st.color;
        d.width = (sizeMin / 2 * st.length) + 'px';
        d.height = (sizeMin / 2 * st.width) + 'px';
        d.cursor = 'pointer';
        d.position = 'absolute';
        d.left = -(sizeMin / 2 * st.offset) + 'px';
        d.top = -(sizeMin / 2 * st.width / 2) + 'px';

        // 時針クリック補助
        d = document.querySelector('#timepicker #tp_hourMargin').style;
        d.width = (sizeMin / 2 * .5) + 'px';
        d.height = (sizeMin / 2 * .4) + 'px';
        d.cursor = 'pointer';
        d.position = 'absolute';
        d.left = (sizeMin / 2 * .15) + 'px';
        d.top = -(sizeMin / 2 * .2) + 'px';

        // 時針回転制御用親要素
        d = document.querySelector('#timepicker #tp_hourParent').style;
        d.position = 'absolute';
        d.width = '0px';
        d.height = '0px';
        d.left = '50%';
        d.top = '50%';
        d.transform = 'rotate(270deg)';
        d.zIndex = '10';

        // 分針
        d = document.querySelector('#timepicker #tp_minute').style;
        st = stylesDefault.minuteHand;
        overRide(st, styles.minuteHand);
        d.background = st.color;
        d.width = (sizeMin / 2 * st.length) + 'px';
        d.height = (sizeMin / 2 * st.width) + 'px';
        d.cursor = 'pointer';
        d.position = 'absolute';
        d.left = -(sizeMin / 2 * st.offset) + 'px';
        d.top = -(sizeMin / 2 * st.width / 2) + 'px';

        // 分針クリック補助
        d = document.querySelector('#timepicker #tp_minuteMargin').style;
        d.width = (sizeMin / 2 * 0.8) + 'px';
        d.height = (sizeMin / 2 * .4) + 'px';
        d.cursor = 'pointer';
        d.position = 'absolute';
        d.left = (sizeMin / 2 * .2) + 'px';
        d.top = -(sizeMin / 2 * .2) + 'px';

        // 分針回転制御用親要素
        d = document.querySelector('#timepicker #tp_minuteParent').style;
        d.position = 'absolute';
        d.width = '0px';
        d.height= '0px';
        d.left = '50%';
        d.top = '50%';
        d.transform = 'rotate(270deg)';
        d.zIndex = '11';

        // AM/PMボタン親
        d = document.querySelector('#timepicker #tp_ampmParent').style;
        st = stylesDefault.ampmButton;
        overRide(st, styles.ampmButton);
        d.display = st.display;
        d.position = 'absolute';
        d.right = '0px';
        d.bottom = '0px';
        // AM/PMボタン
        for(let i = 0; i < 2; i++) {
            d = document.querySelector('#timepicker #tp_' + ['am', 'pm'][i]).style;
            d.position = 'relative';
            d.padding = '0';
            d.border = '0';
            d.cursor = 'pointer';
            d.background = 'rgba(0, 0, 0, 0)';
        }
        timePickerParam.ampmOnColor = st.onColor;
        timePickerParam.ampmOffColor = st.offColor;

        // ステップ送り戻し(文字盤空白部分 左右半分ずつ)
        for(let i = 0; i < 2; i++) {
            d = document.querySelector('#timepicker #tp_' + ['left', 'right'][i] + 'Adjust').style;
            d.position = 'absolute';
            d.width = '50%';
            d.height = '100%';
            d[['left', 'right'][i]] = '0px';
        }

        timePickerParam.timeout_ = timePickerParam.timeout;
        if(styles.timeout !== undefined) {
            let t = parseInt(styles.timeout);
            if(t <= 0) t = 36e5;
            timePickerParam.timeout_ = t;
        }

        timePickerParam.step_ = timePickerParam.step;
        if(styles.step !== undefined) {
            timePickerParam.step_ = styles.step;
        }
        if(timePickerParam.step_ < 1) timePickerParam.step_ = 1;
        else if(timePickerParam.step_ > 30) timePickerParam.step_ = 30;
        timePickerParam.step_ = Math.floor(timePickerParam.step_);

        function overRide(t, f) {
            if(f === undefined) return;
            for(let i in f) t[i] = f[i];
        }
    }

    window.addEventListener('load', function() {
        const html =
            "<div id='timepicker'>" +
            "    <canvas id='tp_canvas' width='" + timePickerParam.styles.width +
            "' height='" + timePickerParam.styles.height + "'></canvas>" +
            "    <input type='button' id='tp_view' value='00:00'>" +
            "    <input type='text' id='tp_dummy'>" +
            "    <div id='tp_leftAdjust'></div>" +
            "    <div id='tp_rightAdjust'></div>" +
            "    <div id='tp_minuteParent'>" +
            "        <div id='tp_minuteMargin'></div>" +
            "        <div id='tp_minute'></div>" +
            "    </div>" +
            "    <div id='tp_hourParent'>" +
            "        <div id='tp_hourMargin'></div>" +
            "        <div id='tp_hour'></div>" +
            "    </div>" +
            "    <div id='tp_close'></div>" +
            "    <div id='tp_ampmParent'>" +
            "        <input type='button' id='tp_am' class='tp_ampm' value='AM'>" +
            "        <input type='button' id='tp_pm' class='tp_ampm' value='PM'>" +
            "    </div>" +
            "</div>";

        document.querySelector('body').insertAdjacentHTML('beforeend', html);

        tpSetStyle();
        document.querySelector('#timepicker').style.display = 'none';

        timePickerParam.touchFlg = window.ontouchstart !== undefined ? true : false;

        let x, y;
        let r, r_;

        timePickerParam.n = 0;
        timePickerParam.pm = 0;

        let hourFlg = false;
        let minuteFlg = false;

        // 時針クリック
        document.querySelector('#timepicker #tp_hour').addEventListener(timePickerParam.touchFlg ? 'touchstart' : 'mousedown', function(e) {
            selectHour();
        });
        document.querySelector('#timepicker #tp_hourMargin').addEventListener(timePickerParam.touchFlg ? 'touchstart' : 'mousedown', function(e) {
            selectHour();
        });
        function selectHour() {
            if(!hourFlg) {
                hourFlg = true;
                if(timePickerParam.touchFlg) tpAddHandler();
                const parent = document.querySelector('#timepicker #tp_hourParent');
                r_ = r = (parseInt(parent.style.transform.replace(/^[^\d-]+/g, '')) + 450 ) % 360;
                setTimeout(function(){ document.querySelector('#timepicker #tp_dummy').focus();}, 10);
            }
        }

        // 分針クリック
        document.querySelector('#timepicker #tp_minute').addEventListener(timePickerParam.touchFlg ? 'touchstart' : 'mousedown', function(e) {
            selectMinute();
        });
        document.querySelector('#timepicker #tp_minuteMargin').addEventListener(timePickerParam.touchFlg ? 'touchstart' : 'mousedown', function(e) {
            selectMinute();
        });
        function selectMinute() {
            if(!minuteFlg) {
                minuteFlg = true;
                if(timePickerParam.touchFlg) tpAddHandler();
                const parent = document.querySelector('#timepicker #tp_minuteParent');
                r_ = r = (parseInt(parent.style.transform.replace(/^[^\d-]+/g, '')) + 450 ) % 360;
                setTimeout(function(){ document.querySelector('#timepicker #tp_dummy').focus();}, 10);
            }
        }

        // 針ドラッグ
        window.addEventListener(timePickerParam.touchFlg ? 'touchmove' : 'mousemove', function(e) {
            let n = timePickerParam.n;
            let pm = n >= 720 ? 1 : 0;
            const tp = document.querySelector('#timepicker');

            if(hourFlg || minuteFlg) {
                const parent = document.querySelector(hourFlg ? '#timepicker #tp_hourParent' : '#timepicker #tp_minuteParent');

                const ex = timePickerParam.touchFlg ? e.touches[0].clientX : e.clientX;
                const ey = timePickerParam.touchFlg ? e.touches[0].clientY : e.clientY;
                x = ex - (tp.offsetLeft - window.pageXOffset) - (tp.clientWidth / 2);
                y = ey - (tp.offsetTop - window.pageYOffset) - (tp.clientHeight / 2);

                const at = Math.atan2(y, x);
                const k = minuteFlg ?
                    Math.floor(((at * (360 / (Math.PI * 2))) % 360) / 6) * 6:
                    (at * (360 / (Math.PI * 2))) % 360;

                parent.style.transform = 'rotate(' + k + 'deg)';

                if(minuteFlg) {
                    let p = 6 * timePickerParam.step_;
                    if(p >= 180) p = 90;
                    r = (Math.round(Math.floor((k + 450) % 360) / p) * p) % 360;
                    n = Math.floor(n / 60) * 60 + (r / 6);
                    if(Math.abs(r - r_) > 180) n -= (360 * (r - r_ > 0 ? 1 : -1)) / 6;
                    r_ = r;
                }

                if(hourFlg) {
                    r = (k + 450) % 360;
                    if(Math.abs(r - r_) > 300) pm ^= 1;
                    n = r * 2 + pm * 720;
                    r_ = r;
                    n = Math.floor((n + 1440) % 1440);
                }

                timePickerParam.n = n;
                timePickerParam.pm = pm;

                tpUpdatePicker();
                timeoutUpdate();
            }
        });

        // マウスホイール
        window.addEventListener('wheel', function(e) {
            if(!timePickerParam.ids['wheel']) return;

            const delta = e.wheelDelta !== undefined ? e.deltaY / 40 : e.deltaY / 20;
            let n = timePickerParam.n + delta / 4;
            timePickerParam.n = Math.round(n);

            tpUpdatePicker();
            timeoutUpdate();
        });

        function timeoutUpdate() {
            if(timePickerParam.ids['timeout']) {
                clearTimeout(timePickerParam.ids['timeout']);
                timePickerParam.ids['timeout'] = 0;
            }
            if(!timePickerParam.ids['timeout']) {
                timePickerParam.ids['timeout'] = setTimeout(function() {
                    document.querySelector('#timepicker').style.display = 'none';
                    tpRemoveHandler();
                }, timePickerParam.timeout_);
            }
        }

        // 針リリース
        window.addEventListener(timePickerParam.touchFlg ? 'touchend' : 'mouseup', function() {
            if(hourFlg) {
                hourFlg = false;
            }
            if(minuteFlg) {
                minuteFlg = false;
            }
            if(document.querySelector('#timepicker').style.display !== 'none')
                document.querySelector('#timepicker #tp_dummy').focus();

            if(timePickerParam.touchFlg) tpRemoveHandler();
        });

        // closeボタン
        document.querySelector('#tp_close').addEventListener('click', function() {
            document.querySelector('#timepicker').style.display = 'none';
            tpRemoveHandler();
        });

        // AM/PMボタン
        document.querySelector('#tp_am').addEventListener('click', function() {
            toggleAmpm();
        });
        document.querySelector('#tp_pm').addEventListener('click', function() {
            toggleAmpm();
        });
        function toggleAmpm() {
            timePickerParam.n = (timePickerParam.n + 720) % 1440;
            timePickerParam.pm = timePickerParam.n >= 720 ? 1 : 0;
            tpPrintTime(timePickerParam.n);
            tpSetAmpmAttribute(timePickerParam.pm);
            tpUpdatePicker();
            timeoutUpdate();
        }

        // キーボード
        window.addEventListener('keydown', function() {
            if(document.querySelector('#timepicker').style.display === 'none') return;
            const kcode = event.keyCode;
            let incremental = 0;
            // 左
            if      (kcode === 37) incremental = -timePickerParam.step_;
            // 右
            else if (kcode === 39) incremental = timePickerParam.step_;
            // 上
            else if (kcode === 38) incremental = timePickerParam.step_ < 15 ? 15 : timePickerParam.step_;
            // 下
            else if (kcode === 40) incremental = timePickerParam.step_ < 15 ? -15 : -timePickerParam.step_;
            // space
            else if (kcode === 32) incremental = 720;
            // enter
            else if (kcode === 13) {
                document.querySelector('#timepicker').style.display = 'none';
                tpRemoveHandler();
            }
            // esc
            else if (kcode === 27) {
                document.querySelector('#timepicker').style.display = 'none';
                timePickerParam.o.value = timePickerParam.ev;
                tpRemoveHandler();
            }
            // 0～9
            else if(kcode >= 48 && kcode <= 57 || kcode >= 96 && kcode <= 105) {
                if(timePickerParam.numKeyInTime === undefined || Date.now() - timePickerParam.numKeyInTime > 2000) timePickerParam.time = '0000';
                timePickerParam.numKeyInTime = Date.now();
                const tmp = (timePickerParam.time + String(kcode - (kcode >= 96 ? 96 : 48))).slice(-4);
                let h = Number(tmp.slice(0,2)), m = Number(tmp.slice(-2));
                timePickerParam.time = tmp;
                if(h <= 24 && h >= 0 && m < 60 && m >= 0) {
                    h %= 24;
                    timePickerParam.n = h * 60 + m;
                    tpUpdatePicker();
                }
                timeoutUpdate();
            }
            if(incremental) {
                timePickerParam.n = (timePickerParam.n + 1440 + incremental) % 1440;
                if((kcode === 38 || kcode === 40))
                    timePickerParam.n = Math.floor(timePickerParam.n / incremental) * incremental;
                tpUpdatePicker();
                timeoutUpdate();
            }
        });

        document.querySelector('#timepicker #tp_leftAdjust')
        .addEventListener(timePickerParam.touchFlg ? 'touchstart' : 'mousedown', function() {
            timePickerParam.n -= timePickerParam.step_;
            tpUpdatePicker();
            timeoutUpdate();
        });
        document.querySelector('#timepicker #tp_rightAdjust')
        .addEventListener(timePickerParam.touchFlg ? 'touchstart' : 'mousedown', function() {
            timePickerParam.n += timePickerParam.step_;
            tpUpdatePicker();
            timeoutUpdate();
        });

        // dataset(data-timepicker)指定のinput要素にclickイベントを追加
        const inputElements = document.querySelectorAll('input');
        for(let i = 0; i < inputElements.length; i++) {
            const dataset = inputElements[i].dataset.timepicker;
            if(dataset === undefined) continue;
            const params = dataset.split(';');
            let width = timePickerParam.styles.width;
            let height = timePickerParam.styles.height;
            for(let l = 0; l < params.length; l++) {
                const param = params[l].split(':');
                if(param[0].trim().toLowerCase() === 'width')
                    width = Math.abs(parseInt(param[1]));
                else if(param[0].trim().toLowerCase() === 'height')
                    height = Math.abs(parseInt(param[1]));
            }
            inputElements[i].addEventListener('click', function() {
                timepicker(this, width, height);
            });
        }
    }, false);

    function tpUpdatePicker() {
        timePickerParam.n = (timePickerParam.n + 1440) % 1440;
        timePickerParam.n_ = timePickerParam.n;

        const step = timePickerParam.step_;
        timePickerParam.n_ = Math.round(timePickerParam.n_ / step) * step;
        timePickerParam.n_ = (timePickerParam.n_ + 1440) % 1440;

        timePickerParam.pm = timePickerParam.n_ >= 720 ? 1 : 0;
        tpPrintTime(timePickerParam.n_);
        tpSetAmpmAttribute(timePickerParam.pm);
        document.querySelector('#timepicker #tp_minuteParent').style.transform =
            'rotate(' + (timePickerParam.n_ * 6 - 90) + 'deg)';
        document.querySelector('#timepicker #tp_hourParent').style.transform =
            'rotate(' + (timePickerParam.n_ / 2 - 90) + 'deg)';
    }

    function tpPrintTime(n) {
        const hour = ('0' + Math.floor(n / 60)).slice(-2);
        const minute = ('0' + (n % 60)).slice(-2);
        document.querySelector('#timepicker #tp_view').value = hour + ':' + minute;
        timePickerParam.o.value = hour + ':' + minute;
    }

    function tpRemoveHandler() {
        if(timePickerParam.ids['touchmove'] > 0) {
            tpHandler.remove(timePickerParam.ids['touchmove']);
            timePickerParam.ids['touchmove'] = 0;
        }
        if(timePickerParam.ids['wheel'] > 0) {
            tpHandler.remove(timePickerParam.ids['wheel']);
            timePickerParam.ids['wheel'] = 0;
        }
    }

    function tpAddHandler() {
        timePickerParam.ids['touchmove'] = tpHandler.add(window, 'touchmove', function(e) {
            e.preventDefault();
        }, { passive: false });
        timePickerParam.ids['wheel'] = tpHandler.add(window, 'wheel', function(e) {
            e.preventDefault();
        }, { passive: false });
    }

    function tpSetAmpmAttribute(f) {
        if(f === undefined) f = timePickerParam.pm;
        const am = document.querySelector('#timepicker #tp_am').style;
        const pm = document.querySelector('#timepicker #tp_pm').style;

        am.color = timePickerParam[f ? 'ampmOffColor' : 'ampmOnColor' ];
        pm.color = timePickerParam[f ? 'ampmOnColor'  : 'ampmOffColor'];
    }

    window.timepicker = function(e, width, height) {
        let styles = {};
        if(typeof(width) === 'object') {
            styles = width;
            width = styles.width !== undefined ? styles.width : undefined;
            height = styles.height !== undefined ? styles.height : undefined;
        }
        if(width === undefined) width = timePickerParam.styles.width;
        if(height === undefined) height = timePickerParam.styles.height;
        tpSetStyle(width, height, styles);

        const picker = document.querySelector('#timepicker');
        if(picker.style.display !== 'none' && timePickerParam.o === e) {
            picker.style.display = 'none';
            tpRemoveHandler();
            return;
        }

        picker.style.left = (e.offsetLeft) + 'px';
        picker.style.top = (4 + e.offsetTop + e.clientHeight) + 'px';

        const bw = document.querySelector('body').clientWidth;
        if(e.offsetLeft + width > bw) picker.style.left = (bw - width) + 'px';

        timePickerParam.ev = e.value;
        e.value = e.value.replace(/^(\d{1,2})(\d\d)$/, "$1:$2");
        const d = new Date('Thu Jan 01 1970 ' + (!e.value ? '_' : e.value) + ':00');
        const now = new Date();
        const t = isNaN(d.getHours()) ?
            now.getHours() * 60 + now.getMinutes() : d.getHours() * 60 + d.getMinutes();

        picker.querySelector('#tp_view').value = isNaN(d.getHours()) ?
            ('0' + now.getHours()).slice(-2) + ':' + ('0' + now.getMinutes()).slice(-2) :
            ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2);

        timePickerParam.time = picker.querySelector('#tp_view').value.replace(':', '');

        timePickerParam.n = Math.round(t / timePickerParam.step_) * timePickerParam.step_;
        timePickerParam.o = e;
        tpUpdatePicker();

        picker.style.display = 'block';
        document.querySelector('#timepicker #tp_dummy').focus();

        tpRemoveHandler();
        if(!timePickerParam.touchFlg) tpAddHandler();

        if(timePickerParam.ids['timeout']) {
            clearTimeout(timePickerParam.ids['timeout']);
            timePickerParam.ids['timeout'] = 0;
        }
    }
}
~~~

###設定例###

~~~html:index.html
<!DOCTYPE html>
<html lang='ja'>
    <head>
        <meta charset='utf-8'>
        <meta name='viewport' content='user-scalable=no,width=device-width,initial-scale=1'>
        <script src='./timepicker.js'></script>
    </head>
    <body>
        <div style='height:80px;'>&nbsp;</div>
        <!-- onclick='timepicker(this)'で指定 -->
        <input type='text' value='10:10' onclick='timepicker(this)'>
        <input type='text' value='18:00' onclick='timepicker(this)'>
        <input type='text' onclick='timepicker(this)'>
        <!-- type='text' 以外でも可 -->
        <input type='time' onclick='timepicker(this, 150, 150)'>
        <input type='button' value='00:00' onclick='timepicker(this, 150, 150)'>

        <div style='height:20px;'>&nbsp;</div>

        <!-- dataset(data-timepicker)で指定 -->
        <input type='text' data-timepicker=''><!-- 空文字の場合はデフォルトサイズ -->
        <input type='text' data-timepicker='width:300; height:300'><!-- widthとheightが指定可能 -->
        <input type='text' data-timepicker='width:300'><!-- どちらか片方でも可 -->
        <input type='text' data-timepicker='height:300'>

        <div style='height:20px;'>&nbsp;</div>

        <p>
            オブジェクトで装飾パラメータを渡すサンプル<br>
            <script>
                const options = {
                    width: 150,
                    height: 150,
                    frame: {
                        color: 'rgba(240, 255, 255, 0.5)',
                    },
                    dial: {
                        color1: 'rgba(0,0,0,0)',
                        color5: '#c0c0ff',
                        color15: '#8080ff',
                    },
                    hourHand: {
                        color:  '#8888cc',
                        length: '0.3',
                        width:  '0.05',
                        offset: '-0.1',
                    },
                    minuteHand: {
                        color:  '#8888cc',
                        length: '0.5',
                        width:  '0.015',
                        offset: '-0.4',
                    },
                    closeButton: {
                        color: '#8080ff',
                        background: 'transparent',
                    },
                    ampmButton: {
                        onColor: '#8080ff',
                        offColor: '#e0e0ff',
                    },
                    preview: {
                        color: '#8080ff',
                    },
                };
            </script>
            <input type='text' onclick='timepicker(this, options)'>

            <input type='text' onclick='timepicker(this, {frame:{color:"#aca"}, minuteHand:{color:"white"}, hourHand:{color:"white"}, ampmButton:{display:"none"}, preview:{display:"none"}, closeButton:{color:"white", background:"#bdb"} })'>
            <input type='text' onclick='timepicker(this, {frame:{color:"black"}, dial:{color1:"#0f0",color5:"#0f0", color15:"#0f0"}, minuteHand:{color:"white", length:"0.9", width:"0.02", offset:"0"}, hourHand:{color:"white", width:"0.02", offset:"0"}, ampmButton:{display:"none"}, preview:{display:"none"}, closeButton:{color:"white", background:"black"} })'>
        </p>

        <div style='height:800px;'>&nbsp;</div>
    </body>
</html>
~~~

**timepicker.js**を読み込み、対応させたい**input要素**に`onclick='timepicker(this)'`または`data-timepicker`を追記することで適用できます。

~~~html
<script src='./timepicker.js'></script>

<input type='text' onclick='timepicker(this)'>
<input type='text' data-timepicker>
~~~

タイムピッカーのサイズはスクリプト内で設定されているサイズ(200x200ピクセル)がデフォルトになりますが、`onclick='timepicker(this, 150, 150)'`や`data-timepicker='width:150; height:150'`のように個別にサイズ指定もできます。

~~~html
<input type='text' onclick='timepicker(this, 150, 150)'>
<input type='text' data-timepicker='width:150; height:150'>
~~~

**onclick**で指定する場合はオブジェクトで装飾オプションを渡すこともできます。

~~~html
<input type='text' onclick='timepicker(this, {width:150, height:150, frame:{color:"#000000"}, minuteHand:{color:"#ffffff"}, hourHand:{color:"#ffffff"}, ampmButton:{onColor:"#ffffff", offColor:"#888888"}, closeButton:{color:"#ffffff", background:"transparent"} })'>
~~~

適用させた**input要素**をクリックするとピッカーを表示、対象**input要素**をもう一度クリックするかピッカーのクローズボタンをクリック、または操作後一定時間(デフォルト5秒)無操作で消えます。