QA@IT

セレクトで、時計の文字盤を切り換えるコードの書き方について

6857 PV

下記のコードは、『(カレンダー機能付き)アナログ時計』のもので、1分おきに英語と日本語に切り替わります。
このコードの"FACE(文字盤)の数字を、セレクトボックスで切り換えるようにしたいのですが、どのように
書けばいいでしょうか。類似のコードでは、うまく行きましたが、こちらのコードでは、うまく行きません。
よろしくお願いします。

    <!DOCTYPE html>
<html>
<head>
    <style>
        body {
            font-family: Helvetica, Arial, sans-serif;
        }

        .Face {
            position: absolute;
            height: 10;
            widdth: 10;
            text-align: center;
            font-size: 1em;
            color: #0000ff;
        }

        .Hours {
            position: absolute;
            width: 16px;
            height: 16px;
            font-family: Arial;
            font-size: 16px;
            color: #000000;
            text-align: center;
            font-weight: bold;
        }

        .Minutes {
            position: absolute;
            width: 16px;
            height: 16px;
            font-family: Arial;
            font-size: 16px;
            color: #000000;
            text-align: center;
            font-weight: bold;
        }

        .Seconds {
            position: absolute;
            width: 16px;
            height: 16px;
            font-family: Arial;
            font-size: 16px;
            color: #ff0000;
            text-align: center;
            font-weight: bold;
        }

        .Date {
            position: absolute;
            height: 10;
            width: 10;
            font-size: 1em;
            color: #00ff00;
        }
    </style>
    <script src="/scripts/snippet-javascript-console.min.js?v=1"></script>
</head>
<body>

<div id="clock">
    <div id="Od" style="position:absolute;top:0px;left:0px">
        <div style="position:relative">
        </div>
    </div>
    <div id="Of" style="position:absolute;top:0px;left:0px">
        <div style="position:relative">
        </div>
    </div>
    <div id="Oh" style="position:absolute;top:0px;left:0px">
        <div style="position:relative">
        </div>
    </div>
    <div id="Om" style="position:absolute;top:0px;left:0px">
        <div style="position:relative">
        </div>
    </div>
    <div id="Os" style="position:absolute;top:0px;left:0px">
        <div style="position:relative">
        </div>
    </div>
</div>

<script type="text/javascript">
    "use strict";

    function $(sel) {
        return document.getElementById(sel);
    }

    function $$(sel) {
        return document.getElementsByClassName(sel);
    }

    function setPosition(element, y, x) {
        element.style.top = y + 'px';
        element.style.left = x + 'px';
    }

    var CLOCK_HEIGHT = 40,
        CLOCK_WIDTH = 40,
        CLOCK_FROM_MOUSE_Y = 0,
        CLOCK_FROM_MOUSE_X = 100;

    var H = '✴✴✴',
          H = H.split('');
    var M = '✴✴✴✴',
          M = M.split('');
    var S = '・・・・・',
        S = S.split('');
    var FACES =  '✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴', //23字で調整(*空白を入れる)
        SPEED = 0.2,
        FACES = FACES.split(' ');
    var HAND_HEIGHT = CLOCK_HEIGHT / 4.5;
    var HAND_WIDTH = CLOCK_WIDTH / 4.5;
    var HAND_Y = -7,
        HAND_X = -2.5,
        STEP = 0.06;

    var ymouse = 0,
        xmouse = 0;
    var currStep = 0;
    var lastBasePositions = [];

    function initialize() {
        for (var i = 0; i < FACES.length; ++i) {
            lastBasePositions[i] = {x:0, y:0};
        }

        var html = '';
        // Face wrapper
        html = '';
        for (var i = 0; i < FACES.length; ++i) {
            html += '<div class="Face">' + FACES[i] + '</div>';
        }
        $('Of').children[0].innerHTML = html;

        // Hours wrapper
        html = '';
        for (var i = 0; i < H.length; ++i) {
            html += '<div class="Hours">' + H[i] + '</div>';
        }
        $('Oh').children[0].innerHTML = html;

        // Minute wrapper
        html = '';
        for (var i = 0; i < M.length; ++i) {
            html += '<div class="Minutes">' + M[i] + '</div>';
        }
        $('Om').children[0].innerHTML = html;

        // Seconds wrapper
        html = '';
        for (var i = 0; i < S.length; ++i) {
            html += '<div class="Seconds">' + S[i] + '</div>';
        }
        $('Os').children[0].innerHTML = html;

        // Mouse move event handler
        document.onmousemove = function(evnt) {
            ymouse = evnt.clientY + CLOCK_FROM_MOUSE_Y;
            xmouse = evnt.clientX + CLOCK_FROM_MOUSE_X;
        };

        requestAnimationFrame(ClockAndAssign);
    }

    var lastYearPositions = [{x:0, y:0}];
    var lastYearString = ' ';
    var lastYearLocale = '';
    function updateYear(currentDate, scrll) {
        var yearString = lastYearString;
        if (currentDate.getMinutes() % 2 == 0) {
            if (lastYearLocale != 'ja')
                yearString = currentDate.toLocaleDateString("ja-JP-u-ca-japanese", { era: "long", year: "numeric" }).
                replace(/\u200e/g, "").replace(" ", "");
            lastYearLocale = 'ja';
        } else {
            if (lastYearLocale != 'en')
                yearString = currentDate.toLocaleDateString("en-US", { era: "long", year: "numeric" }).
                replace(/\u200e/g, "") + "年";
            lastYearLocale = 'en';
        }

        var yearLength = lastYearPositions.length;
        if (yearString != lastYearString) {
            lastYearString = yearString;
            var yearCharacters = yearString.split('');
            yearLength = yearCharacters.length;

            // Date wrapper
            var html = '';
            for (var i = 0; i < yearLength; ++i) {
                html += '<div class="Date">' + yearCharacters[i] + '</div>';
            }
            $('Od').children[0].innerHTML = html;
        }
        var positions = [{}];
        var lastPosition = lastYearPositions[0];
        positions[0].y = lastPosition.y + ((ymouse) - lastPosition.y) * SPEED;
        positions[0].x = lastPosition.x + ((xmouse) - lastPosition.x) * SPEED;
        for (var i = 1; i < yearLength; ++i) {
            lastPosition = i < lastYearPositions.length ?
                lastYearPositions[i] :
                lastYearPositions[lastYearPositions.length - 1];
            positions[i] = {};
            positions[i].y = lastPosition.y + (positions[i-1].y - lastPosition.y) * SPEED;
            positions[i].x = lastPosition.x + (positions[i-1].x - lastPosition.x) * SPEED;
        }
        for (var i = 0; i < yearLength; ++i) {
            var radian = currStep + i * (360 / yearLength) * Math.PI / 180;
            setPosition($$('Date')[i],
                Math.round(positions[i].y) + CLOCK_HEIGHT * 1.5 * Math.sin(radian) + scrll,
                Math.round(positions[i].x) + CLOCK_WIDTH * 1.5 * Math.cos(radian));
        }
        lastYearPositions = positions;
        currStep -= STEP;
    }


    function ClockAndAssign() {
        var date = new Date();
        var secs = date.getSeconds();
        var sec = -1.57 + Math.PI * secs / 30;
        var mins = date.getMinutes();
        var min = -1.57 + Math.PI * mins / 30;
        var hr = date.getHours();
        var hrs = -1.575 + Math.PI * hr / 6 + Math.PI * parseInt(date.getMinutes(), 10) / 360;
        $('Od').style.top = window.document.body.scrollTop;
        $('Of').style.top = window.document.body.scrollTop;
        $('Oh').style.top = window.document.body.scrollTop;
        $('Om').style.top = window.document.body.scrollTop;
        $('Os').style.top = window.document.body.scrollTop;
        var scrll = 0;

        var positions = [{}];
        var lastPosition = lastBasePositions[0];
        positions[0].y = (lastPosition.y + (ymouse - lastPosition.y) * SPEED);
        positions[0].x = (lastPosition.x + (xmouse - lastPosition.x) * SPEED);
        for (var i = 1; i < FACES.length; ++i) {
            lastPosition = lastBasePositions[i];
            positions[i] = {};
            positions[i].y = (lastPosition.y + (positions[i - 1].y - lastPosition.y) * SPEED);
            positions[i].x = (lastPosition.x + (positions[i - 1].x - lastPosition.x) * SPEED);
        }
        lastBasePositions = positions;

        var split = 360 / FACES.length;
        for (var i = 0; i < FACES.length; ++i) {
            var radian = -1.0471 + i * split * Math.PI / 180;
            setPosition($$('Face')[i],
                positions[i].y + CLOCK_HEIGHT * Math.sin(radian) + scrll,
                positions[i].x + CLOCK_WIDTH * Math.cos(radian));
        }
        for (var i = 0; i < H.length; ++i) {
            setPosition($$('Hours')[i],
                positions[i].y + HAND_Y + (i * HAND_HEIGHT) * Math.sin(hrs) + scrll,
                positions[i].x + HAND_X + (i * HAND_WIDTH) * Math.cos(hrs));
        }
        for (var i = 0; i < M.length; ++i) {
            setPosition($$('Minutes')[i],
                positions[i].y + HAND_Y + (i * HAND_HEIGHT) * Math.sin(min) + scrll,
                positions[i].x + HAND_X + (i * HAND_WIDTH) * Math.cos(min));
        }
        for (var i = 0; i < S.length; ++i) {
            setPosition($$('Seconds')[i],
                positions[i].y + HAND_Y + (i * HAND_HEIGHT) * Math.sin(sec) + scrll,
                positions[i].x + HAND_X + (i * HAND_WIDTH) * Math.cos(sec));
        }
        updateYear(date, scrll);
        requestAnimationFrame(ClockAndAssign);
    }

    initialize();

 function changeFace(){

   if(document.DateFace.changeFace.selectedIndex==0){

      FACES = '✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴';
       }
    else if(document.DateFace.changeFace.selectedIndex==1){

      FACES = '1 2 3 4 5 6 7 8 9 10 11 12';
       }
    else{

      FACES = 'Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ Ⅶ Ⅷ Ⅸ Ⅹ Ⅺ Ⅻ';
       }

      FACES = FACES.split(' ');


  // Face wrapper
  html = '';
  for (var i = 0; i < FACES.length; ++i) {
    html += '<div class="Face">' + FACES[i] + '</div>';
  }
  $('Of').children[0].innerHTML = html;
}
document.DateFace.changeFace.onchange=changeFace;

</script>


 <form id ="DateFaceform" name="DateFace" style="text-align:CENTER;">
   <select id="faceSelector" name="changeFace" onChange="changeFace();">
      <option  selected disabled=disabled> 文字盤 </option>
      <option>アラビア数字</option>
      <option>ロマン数字</option>
   </select>
 </form>
</body>
</html>

問題箇所は、下記の部分です。

 function changeFace(){

   if(document.DateFace.changeFace.selectedIndex==0){

      FACES = '✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴';
       }
    else if(document.DateFace.changeFace.selectedIndex==1){

      FACES = '1 2 3 4 5 6 7 8 9 10 11 12';
       }
    else{

      FACES = 'Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ Ⅶ Ⅷ Ⅸ Ⅹ Ⅺ Ⅻ';
       }

      FACES = FACES.split(' ');


  // Face wrapper
  html = '';
  for (var i = 0; i < FACES.length; ++i) {
    html += '<div class="Face">' + FACES[i] + '</div>';
  }
  $('Of').children[0].innerHTML = html;
}
document.DateFace.changeFace.onchange=changeFace;

回答

// 追記

質問者様自身で解決済みでしたね。
更新しておらず気が付きませんでした。失礼しました。

// 追記ここまで

動かしてみてびっくりしました。普通の時計かと思ったらとてもきれいな動きをするんですね。
結構時間がたっていますのでもう解決しているかもしれませんが回答します。

見出し

以下の順序で回答します。

  1. 動作させるための修正案
  2. 質問者様のコードが動作しなかった理由
  3. 私の問題解決手順

1.動作させるための修正案(一例)

一番変更が少ないと思われる方法です。ソースの読みやすさなどはあまり考慮していません。

変更前

JavaScript(修正箇所のみ抜粋)

function changeFace() {
  if (document.DateFace.changeFace.selectedIndex == 0) {
    FACES = '✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴';
  } else if (document.DateFace.changeFace.selectedIndex == 1) {
    FACES = '1 2 3 4 5 6 7 8 9 10 11 12';
  } else {
    FACES = 'Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ Ⅶ Ⅷ Ⅸ Ⅹ Ⅺ Ⅻ';
  }

  FACES = FACES.split(' ');

  // Face wrapper
  html = '';
  for (var i = 0; i < FACES.length; ++i) {
    html += '<div class="Face">' + FACES[i] + '</div>';
  }
  $('Of').children[0].innerHTML = html;
}
document.DateFace.changeFace.onchange = changeFace;

html(修正箇所のみ抜粋)

<form id ="DateFaceform" name="DateFace" style="text-align:CENTER;">
  <select id="faceSelector" name="changeFace" onChange="changeFace();">
    <option selected disabled=disabled> 文字盤 </option>
    <option>アラビア数字</option>
    <option>ロマン数字</option>
  </select>
</form>

変更後

JavaScript(修正箇所のみ抜粋)

/* changeFace -> doChangeFaceに変更 */
function doChangeFace() {
  if (document.DateFace.changeFace.selectedIndex == 0) {
    FACES = '✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴ ✴';
  } else if (document.DateFace.changeFace.selectedIndex == 1) {
    FACES = '1 2 3 4 5 6 7 8 9 10 11 12';
  } else {
    FACES = 'Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ Ⅶ Ⅷ Ⅸ Ⅹ Ⅺ Ⅻ';
  }

  FACES = FACES.split(' ');

  // Face wrapper
  /* use strictを使用している場合暗黙の宣言はできない
     明示的に変数として宣言 */
  var html = '';
  for (var i = 0; i < FACES.length; ++i) {
    html += '<div class="Face">' + FACES[i] + '</div>';
  }
  $('Of').children[0].innerHTML = html;
}
/* htmlで指定したonChangeと効果が重複するためコメントアウト */
// document.DateFace.changeFace.onchange=changeFace;

html(修正箇所のみ抜粋)

<form id ="DateFaceform" name="DateFace" style="text-align:CENTER;">
  <!-- changeFace関数の名称を変更したのでonChangeで呼び出す関数名も合わせて 変更 -->
  <select id="faceSelector" name="changeFace" onChange="doChangeFace(); ">
    <option selected disabled=disabled> 文字盤 </option>
    <option>アラビア数字</option>
    <option>ロマン数字</option>
  </select>
</form>

変更箇所

  1. changeFace() 関数の名前を doChangeFace() に変更
  2. 2. 1の変更に合わせて onChange="changeFace();"onChange="doChangeFace();" に変更
  3. 3. 変数 html が宣言されていなかったため宣言

2.質問者様のコードが動作しなかった理由

理由1 : changeFace()関数がnameのchangeFaceと誤認識されていた

JavaScript内部の細かい動きまでは理解できていないのですが、
関数のchangeFace()とselect要素につけたchangeFaceで名称がかぶっており、select要素のchangeFaceを指定したと判断されてしまっていたようです。
changeFaceは関数ではないため実行できない」というエラーが出ていました。
複数の要素、関数に対して同じ名称は用いないほうがいいと思います。

理由2 : 変数htmlが宣言されておらずエラーになっていた

(こちらは質問者様のJavaScriptに対する理解度が分からず、書き漏れなのかuse strictの仕様を勘違いしていたのかinitialize()で宣言済みの為宣言不要と判断したのか分からないため一通り書きます。)

use strictを使用すると厳格モードとなり記述ルールがより厳しくなります。
変数も(var,letなどを用いて)明示的に宣言しなければエラーとなり、使用できなくなります(use strict未指定時は暗黙でグローバル変数として宣言されます)。use strict参考リンク(msdn)

html変数はinitialize()関数内でvar htmlという記載で宣言されていますが、これはinitialize()関数内でのみ有効な変数として宣言されます。全体で利用できるようにするにはH,M,SPEEDなどの変数のように一番外側?で宣言する必要があります。
ただ今回のhtmlは使いまわす必要がある変数ではないと思いますので、関数内の必要なタイミングで宣言する形で問題ないと思います。

3. 私の問題解決手順

(このざっくりとした記載で役立つかは分かりませんが)私が原因を見つけるまでの手順を記載しておきます。

  1. とりあえずplunkerのエディタに提示いただいているソースを全て貼り付け、動かしてみました(ブラウザはChromeを使用)
  2. Chromeのデベロッパーツールにてコンソールを開き、表示されているエラーを確認しながら原因を探しました。(changeFace is not functionhtml is not definedという分かりやすいエラーの出方だったので、比較的原因究明しやすかったです。)

JavaScriptはちゃんとエラーが出ますのでデベロッパーツールなどを用いて確認しながら実装すれば、期待した動作にならない場合の対処もしやすいのではないかと思います。

以上参考になれば幸いです。

編集 履歴 (1)
  • 詳しい回答をありがとうございました。IEでも、動きます。
    理由1のchangeFaceの名称が同じにしたのは、気になってはいました。
    2.Chromeのデベロッパーツールは、使えそうですね。エラーの原因が、分からないことが多いですが。
    -

所々にミスがありました。まずは、"form"タグは、不要です。そして、"onChange="changeFace();"の
()の中に"this"を入れます。
次に、スクリプト内の"// Face wrapper"内の"document.DateFace.changeFace.onchange=changeFace;"
も不要です。また"html = '';"に"var"を付けます。
次に"function changeFace()"の()内に"select"を入れます。
最後に"if(document.DateFace.changeFace.selectedIndex==0)~else if(else if(document.DateFace.changeFace.selectedIndex==1)…)"の"document.DateFace.changeFace"を
"select"に換えます。
以上です。

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。