20201016のJavaScriptに関する記事は21件です。

javascriptのLocalStorage を使ってお気に入り機能を自作してみた ③実装編-基盤作り-

どうも7noteです。プラグインより多機能なお気に入り機能を作成。前回の続き

①準備編がまだの方はこちら
②実装編-基盤作り-がまだの方はこちら

wordpressで自作のお気に入り機能を使いたい人はこちら

実運用レベルで使ってみる(コピペで動くよ)

押したらお気に入りに追加。もう一度押したら削除されるように作ります。

完成イメージ

movie.gif

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>

<ul>
  <li no="1" class="add" onclick="addfav('1')"><span></span>No:1 りんご</li>
  <li no="1" class="remove" onclick="removefav('1')"><span></span>No:1 りんご</li>
  <li no="2" class="add" onclick="addfav('2')"><span></span>No:2 みかん</li>
  <li no="2" class="remove" onclick="removefav('2')"><span></span>No:2 みかん</li>
  <li no="3" class="add" onclick="addfav('3')"><span></span>No:3 ばなな</li>
  <li no="3" class="remove" onclick="removefav('3')"><span></span>No:3 ばなな</li>
</ul>

<style>
ul li.remove span {
  color: #F00;
}
</style>

<script>
/* 【解説Aパート】 "表示ソースの切り替え" */
function togleitem(oid,event){
    if(event == 'add'){
        // 未チェック(class="add")を非表示にして、チェック済(class="remove")を表示する
        $('li.add[no=' + oid + ']').hide();
        $('li.remove[no=' + oid + ']').show();
    } else if(event == 'remove'){
        // チェック済(class="remove")を非表示にして、未チェック(class="add")を表示する
        $('li.add[no=' + oid + ']').show();
        $('li.remove[no=' + oid + ']').hide();
    }
}


/* 【解説Bパート】 "ページロード時にローカルストレージの中をチェック" */
$(function(){
  $('ul .remove').hide();  // お気に入り中の表示は一時的に非表示に。

  // お気に入りリストに存在するか確認
  var key = 'お気に入りID';
  var getjson = localStorage.getItem(key);
  var oidlist = JSON.parse(getjson);
  if(oidlist != null){
    oidlist.forEach( function( oid ) {
      togleitem(oid,'add');
    });
  }

})  

/* 【解説Cパート】 "お気に入りに追加機能" */
function addfav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist == null){
        // 初めて「お気に入りID」というキーがローカルストレージに登録される時の処理
        oidary = new Array(oid);
        var setjson = JSON.stringify(oidary);
        localStorage.setItem(key, setjson);

        togleitem(oid,'add');

    } else {
        // 既に「お気に入りID」というキーが存在する時
        if(oidlist.indexOf(oid) == -1){
            // 且つ、まだお気に入りIDに登録されていない時
            oidlist.push(oid);
            var setjson = JSON.stringify(oidlist);
            localStorage.setItem(key, setjson);

            togleitem(oid,'add');
        }
    }
}

/* 【解説Dパート】 "お気に入りから削除機能" */
function removefav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist != null){
        // 「お気に入りID」というキーが存在した時
        var checkitem = oidlist.indexOf(oid);     // 配列の何番目に該当のIDがあるかを見る
        if(checkitem != -1){
            // 「お気に入りID」の中に該当のIDが見つかった時
            oidlist.splice( checkitem, 1 );
            var setjson = JSON.stringify(oidlist);
            localStorage.setItem(key, setjson);

            togleitem(oid,'remove');
        }
    }
}
</script>

</body>
</html>

〜〜解説〜〜

javascriptの処理を大きく4つの機能に分けて解説していきます。

【解説Aパート】 表示ソースの切り替え

function togleitem(oid,event){
    if(event == 'add'){  // 引数の[event]が`add`の時
        // 未チェック(class="add")を非表示にして、チェック済(class="remove")を表示する
        $('li.add[no=' + oid + ']').hide();
        $('li.remove[no=' + oid + ']').show();
    } else if(event == 'remove'){  // 引数の[event]が`remove`の時
        // チェック済(class="remove")を非表示にして、未チェック(class="add")を表示する
        $('li.add[no=' + oid + ']').show();
        $('li.remove[no=' + oid + ']').hide();
    }
}

パートAでは別段ややこしい処理を書いているわけではありませんが、no属性とクラス(addもしくはremove)で特定の<li>を見つけだし、eventが[add]ならお気に入りに追加済みの見た目に変更し、逆に[remove]なら未追加の表示にします。

【解説Bパート】 ページロード時にローカルストレージの中をチェック

$(function(){
  $('ul .remove').hide();  // お気に入り中の表示は一時的に非表示に。

  // お気に入りリストに存在するか確認
  var key = 'お気に入りID';  // ローカルストレージのキーを指定
  var getjson = localStorage.getItem(key);  // ローカルストレージから値を取得
  var oidlist = JSON.parse(getjson);        // JSON形式から配列に直す
  if(oidlist != null){                      // 配列が空でなければ
    oidlist.forEach( function( oid ) {      // 配列の中身を1つずつ[oid]に入れてループを回す
      togleitem(oid,'add');                 // togleitem()関数を動かす ※パートAで書いたやつ
    });
  }

})

パートBでは、ページロードした時に過去にお気に入りにチェックを入れた情報を元に表示するソースをお気に入り状態に変更する処理が書いています。

まず、2行目でデフォルトの状態(つまり全てお気に入り非チェック)にします。そして、ローカルストレージからお気に入りリストを取得してきて、もしお気に入りリストが登録されていれば、登録されているIDの<li>はチェック済の状態に切り替える。という処理を書いています。

【解説Cパート】 お気に入りに追加機能

function addfav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist == null){
        // 初めて「お気に入りID」というキーがローカルストレージに登録される時の処理
        oidary = new Array(oid);               // 初めてなので、新しい配列を作成
        var setjson = JSON.stringify(oidary);  // 配列をJSON形式に変換
        localStorage.setItem(key, setjson);    // ローカルストレージにお気に入りIDを保存

        togleitem(oid,'add');                  // クリックされた対象の<li>の表示を切り替え

    } else {
        // 既に「お気に入りID」というキーが存在する時
        if(oidlist.indexOf(oid) == -1){
            // 且つ、まだお気に入りIDに登録されていない時
            oidlist.push(oid);                     // 配列にクリックされたIDを追加
            var setjson = JSON.stringify(oidlist); // またJSON形式に変換
            localStorage.setItem(key, setjson);    // ローカルストレージにお気に入りIDを保存

            togleitem(oid,'add');                  // クリックされた対象の<li>の表示を切り替え
        }
    }
}

パートCでは、お気に入りにIDを登録(保存)する時の処理をかいています。

まず、お気に入りが追加されるシーンとしては2パターンあります。

1.ローカルストレージに初めてお気に入りIDが登録される時
2.既にお気に入りリストが登録されていて、他のIDがさらに追加される時

この2パターンがあるため、まず最初にローカルストレージの中身を確認し、「お気に入りID」というキーが既に登録されているかどうかをif文で分岐させます。
まだ登録されていなければ、新しく「お気に入りID」というキーを作成し、値にクリックしたIDを入れます。
既に登録されているのであれば、取得したお気に入りリストの配列の最後にクリックしたIDを追加します。
どちらのパターンでもパートAの関数を使い、ソースを切り替えて完了です。

【解説Dパート】 お気に入りから削除機能

function removefav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist != null){
        // 「お気に入りID」というキーが存在した時
        var checkitem = oidlist.indexOf(oid);     // 配列の何番目に該当のIDがあるかを見る
        if(checkitem != -1){
            // 「お気に入りID」の中に該当のIDが見つかった時
            oidlist.splice( checkitem, 1 );   // 配列から該当IDを削除
            var setjson = JSON.stringify(oidlist); // JSON形式に変換
            localStorage.setItem(key, setjson);    // ローカルストレージに保存

            togleitem(oid,'remove');               // クリックされた対象の<li>の表示を切り替え
        }
    }
}

ここまで読んでいる方であればほとんど説明は不要かもしれません。

削除をする場合は、配列の何番目にIDがあるかを調べないといけないので、indexOf()を使って、配列の何番目に該当IDがあるのかを調べます。
あとはspliceを使って該当IDを削除し、またJSON形式にしてからローカルストレージに保存。その後、ソースの表示を切り替えています。

おまけ php編 〜wordpressの投稿IDに応用〜

ここからは初級〜中級者向けのwordpressでお気に入り機能を自作した人向けの内容になります。

wordpressでローカルストレージを使ったお気に入り機能を実装

仕組み

1.onclickでリンク先を指定して起動
2.ローカルストレージからデータを取得
3.取得したデータをpostで渡す
4.postのデータを受け取って変数に格納
5.変数のIDを使ってwordpressのループを作成

xxxx.php
<!-- 飛び先のURLを引数に指定 -->
<div onclick="oidlisttrans('http://hogehoge.com/xxx/yyyy.php')">お気に入りリストを見る</div>

<script>
    // ローカルストレージにあるparamsのデータをpostで送信
    function post(path, params, method='post') {
        const form = document.createElement('form');
        form.method = method;
        form.action = path;
        for (const key in params) {
            if (params.hasOwnProperty(key)) {
                const hiddenField = document.createElement('input');
                hiddenField.type = 'hidden';
                hiddenField.name = key;
                hiddenField.value = params[key];
                form.appendChild(hiddenField);
            }
        }
        document.body.appendChild(form);
        form.submit();
    }

    // お気に入りリストにデータをpost()を使って送信
    function oidlisttrans(link,data='all'){
        var key = 'お気に入りID';
        var getjson = localStorage.getItem(key);
        var oidlist = JSON.parse(getjson);
        obj = {
            'IDリスト': oidlist
        };
        post(link, obj);
    }


飛び先のページ(上と同一ページでも可能)

yyyy.php
<?php
$idlist = explode( ',', $_POST['IDリスト'] );

$wp_query = new WP_Query();
$args = array(
    'post_status' => 'publish',
    'post__in' => $idlist,
    'orderby' => 'post__in',
);
$wp_query->query($args);
?>
<?php if (have_posts()) : ?>
    <?php while (have_posts()) : the_post(); ?>
        //ループ内の処理を自由に書く
    <?php endwhile; ?>
<?php else: ?>
    // 投稿がなかった時の処理
<?php endif; ?>

こんな感じの書き方でwordpressでも応用して使えるかな?

ちょっと長丁場になりましたがいろいろ使い道があると思います!

まとめ

長くなるためCSSはほとんど省きましたが、自由に表示を切り替えたり見た目を変更できるので、
好きな位置に好きなボタンを配置できるので、プラグインなどを使ったけどうまくいかない方の参考に少しでもなればいいかなと思います。

おそまつ!

~ Qiitaで毎日投稿中!! ~
【初心者向け】HTML・CSSのちょいテク詰め合わせ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptのLocalStorage を使ってお気に入り機能を自作してみた ③実装編-組み込み-

どうも7noteです。プラグインより多機能なお気に入り機能を作成。前回の続き

①準備編がまだの方はこちら
②実装編-基盤作り-がまだの方はこちら

wordpressで自作のお気に入り機能を使いたい人はこちら

実運用レベルで使ってみる(コピペで動くよ)

押したらお気に入りに追加。もう一度押したら削除されるように作ります。

完成イメージ

movie.gif

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>

<ul>
  <li no="1" class="add" onclick="addfav('1')"><span></span>No:1 りんご</li>
  <li no="1" class="remove" onclick="removefav('1')"><span></span>No:1 りんご</li>
  <li no="2" class="add" onclick="addfav('2')"><span></span>No:2 みかん</li>
  <li no="2" class="remove" onclick="removefav('2')"><span></span>No:2 みかん</li>
  <li no="3" class="add" onclick="addfav('3')"><span></span>No:3 ばなな</li>
  <li no="3" class="remove" onclick="removefav('3')"><span></span>No:3 ばなな</li>
</ul>

<style>
ul li.remove span {
  color: #F00;
}
</style>

<script>
/* 【解説Aパート】 "表示ソースの切り替え" */
function togleitem(oid,event){
    if(event == 'add'){
        // 未チェック(class="add")を非表示にして、チェック済(class="remove")を表示する
        $('li.add[no=' + oid + ']').hide();
        $('li.remove[no=' + oid + ']').show();
    } else if(event == 'remove'){
        // チェック済(class="remove")を非表示にして、未チェック(class="add")を表示する
        $('li.add[no=' + oid + ']').show();
        $('li.remove[no=' + oid + ']').hide();
    }
}


/* 【解説Bパート】 "ページロード時にローカルストレージの中をチェック" */
$(function(){
  $('ul .remove').hide();  // お気に入り中の表示は一時的に非表示に。

  // お気に入りリストに存在するか確認
  var key = 'お気に入りID';
  var getjson = localStorage.getItem(key);
  var oidlist = JSON.parse(getjson);
  if(oidlist != null){
    oidlist.forEach( function( oid ) {
      togleitem(oid,'add');
    });
  }

})  

/* 【解説Cパート】 "お気に入りに追加機能" */
function addfav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist == null){
        // 初めて「お気に入りID」というキーがローカルストレージに登録される時の処理
        oidary = new Array(oid);
        var setjson = JSON.stringify(oidary);
        localStorage.setItem(key, setjson);

        togleitem(oid,'add');

    } else {
        // 既に「お気に入りID」というキーが存在する時
        if(oidlist.indexOf(oid) == -1){
            // 且つ、まだお気に入りIDに登録されていない時
            oidlist.push(oid);
            var setjson = JSON.stringify(oidlist);
            localStorage.setItem(key, setjson);

            togleitem(oid,'add');
        }
    }
}

/* 【解説Dパート】 "お気に入りから削除機能" */
function removefav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist != null){
        // 「お気に入りID」というキーが存在した時
        var checkitem = oidlist.indexOf(oid);     // 配列の何番目に該当のIDがあるかを見る
        if(checkitem != -1){
            // 「お気に入りID」の中に該当のIDが見つかった時
            oidlist.splice( checkitem, 1 );
            var setjson = JSON.stringify(oidlist);
            localStorage.setItem(key, setjson);

            togleitem(oid,'remove');
        }
    }
}
</script>

</body>
</html>

コメント欄@il9437さんから他の書き方もご提示いただきました。

〜〜解説〜〜

javascriptの処理を大きく4つの機能に分けて解説していきます。

【解説Aパート】 表示ソースの切り替え

function togleitem(oid,event){
    if(event == 'add'){  // 引数の[event]が`add`の時
        // 未チェック(class="add")を非表示にして、チェック済(class="remove")を表示する
        $('li.add[no=' + oid + ']').hide();
        $('li.remove[no=' + oid + ']').show();
    } else if(event == 'remove'){  // 引数の[event]が`remove`の時
        // チェック済(class="remove")を非表示にして、未チェック(class="add")を表示する
        $('li.add[no=' + oid + ']').show();
        $('li.remove[no=' + oid + ']').hide();
    }
}

パートAでは別段ややこしい処理を書いているわけではありませんが、no属性とクラス(addもしくはremove)で特定の<li>を見つけだし、eventが[add]ならお気に入りに追加済みの見た目に変更し、逆に[remove]なら未追加の表示にします。

【解説Bパート】 ページロード時にローカルストレージの中をチェック

$(function(){
  $('ul .remove').hide();  // お気に入り中の表示は一時的に非表示に。

  // お気に入りリストに存在するか確認
  var key = 'お気に入りID';  // ローカルストレージのキーを指定
  var getjson = localStorage.getItem(key);  // ローカルストレージから値を取得
  var oidlist = JSON.parse(getjson);        // JSON形式から配列に直す
  if(oidlist != null){                      // 配列が空でなければ
    oidlist.forEach( function( oid ) {      // 配列の中身を1つずつ[oid]に入れてループを回す
      togleitem(oid,'add');                 // togleitem()関数を動かす ※パートAで書いたやつ
    });
  }

})

パートBでは、ページロードした時に過去にお気に入りにチェックを入れた情報を元に表示するソースをお気に入り状態に変更する処理が書いています。

まず、2行目でデフォルトの状態(つまり全てお気に入り非チェック)にします。そして、ローカルストレージからお気に入りリストを取得してきて、もしお気に入りリストが登録されていれば、登録されているIDの<li>はチェック済の状態に切り替える。という処理を書いています。

【解説Cパート】 お気に入りに追加機能

function addfav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist == null){
        // 初めて「お気に入りID」というキーがローカルストレージに登録される時の処理
        oidary = new Array(oid);               // 初めてなので、新しい配列を作成
        var setjson = JSON.stringify(oidary);  // 配列をJSON形式に変換
        localStorage.setItem(key, setjson);    // ローカルストレージにお気に入りIDを保存

        togleitem(oid,'add');                  // クリックされた対象の<li>の表示を切り替え

    } else {
        // 既に「お気に入りID」というキーが存在する時
        if(oidlist.indexOf(oid) == -1){
            // 且つ、まだお気に入りIDに登録されていない時
            oidlist.push(oid);                     // 配列にクリックされたIDを追加
            var setjson = JSON.stringify(oidlist); // またJSON形式に変換
            localStorage.setItem(key, setjson);    // ローカルストレージにお気に入りIDを保存

            togleitem(oid,'add');                  // クリックされた対象の<li>の表示を切り替え
        }
    }
}

パートCでは、お気に入りにIDを登録(保存)する時の処理をかいています。

まず、お気に入りが追加されるシーンとしては2パターンあります。

1.ローカルストレージに初めてお気に入りIDが登録される時
2.既にお気に入りリストが登録されていて、他のIDがさらに追加される時

この2パターンがあるため、まず最初にローカルストレージの中身を確認し、「お気に入りID」というキーが既に登録されているかどうかをif文で分岐させます。
まだ登録されていなければ、新しく「お気に入りID」というキーを作成し、値にクリックしたIDを入れます。
既に登録されているのであれば、取得したお気に入りリストの配列の最後にクリックしたIDを追加します。
どちらのパターンでもパートAの関数を使い、ソースを切り替えて完了です。

【解説Dパート】 お気に入りから削除機能

function removefav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist != null){
        // 「お気に入りID」というキーが存在した時
        var checkitem = oidlist.indexOf(oid);     // 配列の何番目に該当のIDがあるかを見る
        if(checkitem != -1){
            // 「お気に入りID」の中に該当のIDが見つかった時
            oidlist.splice( checkitem, 1 );   // 配列から該当IDを削除
            var setjson = JSON.stringify(oidlist); // JSON形式に変換
            localStorage.setItem(key, setjson);    // ローカルストレージに保存

            togleitem(oid,'remove');               // クリックされた対象の<li>の表示を切り替え
        }
    }
}

ここまで読んでいる方であればほとんど説明は不要かもしれません。

削除をする場合は、配列の何番目にIDがあるかを調べないといけないので、indexOf()を使って、配列の何番目に該当IDがあるのかを調べます。
あとはspliceを使って該当IDを削除し、またJSON形式にしてからローカルストレージに保存。その後、ソースの表示を切り替えています。

おまけ php編 〜wordpressの投稿IDに応用〜

ここからは初級〜中級者向けのwordpressでお気に入り機能を自作した人向けの内容になります。

wordpressでローカルストレージを使ったお気に入り機能を実装

仕組み

1.onclickでリンク先を指定して起動
2.ローカルストレージからデータを取得
3.取得したデータをpostで渡す
4.postのデータを受け取って変数に格納
5.変数のIDを使ってwordpressのループを作成

xxxx.php
<!-- 飛び先のURLを引数に指定 -->
<div onclick="oidlisttrans('http://hogehoge.com/xxx/yyyy.php')">お気に入りリストを見る</div>

<script>
    // ローカルストレージにあるparamsのデータをpostで送信
    function post(path, params, method='post') {
        const form = document.createElement('form');
        form.method = method;
        form.action = path;
        for (const key in params) {
            if (params.hasOwnProperty(key)) {
                const hiddenField = document.createElement('input');
                hiddenField.type = 'hidden';
                hiddenField.name = key;
                hiddenField.value = params[key];
                form.appendChild(hiddenField);
            }
        }
        document.body.appendChild(form);
        form.submit();
    }

    // お気に入りリストにデータをpost()を使って送信
    function oidlisttrans(link,data='all'){
        var key = 'お気に入りID';
        var getjson = localStorage.getItem(key);
        var oidlist = JSON.parse(getjson);
        obj = {
            'IDリスト': oidlist
        };
        post(link, obj);
    }


飛び先のページ(上と同一ページでも可能)

yyyy.php
<?php
$idlist = explode( ',', $_POST['IDリスト'] );

$wp_query = new WP_Query();
$args = array(
    'post_status' => 'publish',
    'post__in' => $idlist,
    'orderby' => 'post__in',
);
$wp_query->query($args);
?>
<?php if (have_posts()) : ?>
    <?php while (have_posts()) : the_post(); ?>
        //ループ内の処理を自由に書く
    <?php endwhile; ?>
<?php else: ?>
    // 投稿がなかった時の処理
<?php endif; ?>

こんな感じの書き方でwordpressでも応用して使えるかな?

ちょっと長丁場になりましたがいろいろ使い道があると思います!

まとめ

長くなるためCSSはほとんど省きましたが、自由に表示を切り替えたり見た目を変更できるので、
好きな位置に好きなボタンを配置できるので、プラグインなどを使ったけどうまくいかない方の参考に少しでもなればいいかなと思います。

おそまつ!

~ Qiitaで毎日投稿中!! ~
【初心者向け】HTML・CSSのちょいテク詰め合わせ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ボタンを押すとブラウザに画像を表示できる

ボタンを押すとブラウザに画像を表示できるようにする

canvas要素を指定してキャンバス上に表示したい画像を表示します。(別にcanvas要素以外の要素でも問題ありません)

canvas要素にid属性を指定して後でjavascriptで取得できるようにします。
2つのbutton要素にはonclick属性を指定して、ボタンをクリックしたときにそれぞれ関数が発火するようにします。appearメソッドを発火させたときは指定した画像を表示して、disappearメソッドを発火させたときは表示した画像を消去できるようにします。
img要素にはsrc属性に表示したい画像を設定して、id属性を指定して後で取得できるようにします。style属性には「display: none;」を指定してappear関数が発火するまで表示されないようにします。

<body>
  <canvas id="canvas" width="500" height="500"></canvas><br>
  <button onclick="appear()">表示</button>
  <button onclick="disappear()">非表示</button>
  <img src="#" id="img1" style="display: none;">
</body>

script要素内では、変数ctxを定義します。

appear関数内は定数canvasにbody内のcanvas要素を取得させ、変数ctxに代入してcanvas要素に画像を表示する準備をします。
変数imgにbody要素内のimg要素を取得させ、「drawImage( )」メソッドでキャンバスに画像を表示させます。
「drawImage(image, x, y, w, h)」メソッドはimageの部分に「img要素かvideo要素かcanvas要素」を指定できます。今回は静止画の表示なので「let img = document.getElementById("img1");」で代入したimgを指定します。xはキャンバスの左上隅からのx座標、yはキャンバス左上隅からのy座標、wは表示する画像の横幅、hは表示する画像の縦幅を指定します。
勘違いしやすいところですが、数学で学ぶ座標は中心から右に向かうとxが増え、上に向かうとyが増えますが、canvasを使うときはcanvasの左上隅が座標(0,0)で右に向かうとxが増えるのは同じですが、yは下に向かうと増えます。

disappear関数内は1行だけですが、「clearRect( )」メソッドで表示した画像を消去しています。
「clearRect(x, y , w, h)」メソッドは指定した(x, y)座標からwで指定した横幅、hで指定した縦幅分、四角形の形でクリアすることができるメソッドです。

  <script>
    let ctx;

    function appear() {
      const canvas = document.getElementById("canvas");
      ctx = canvas.getContext("2d");

      let img = document.getElementById("img1");
      ctx.drawImage(img, 100, 100, 300, 300);
    }

    function disappear() {
      ctx.clearRect(0, 0, 500, 500);
    }
  </script>

以下のコードをコピーして、ファイルに貼り付ければ試すことができます。(body内img要素のsrc属性の#はお持ちの画像に変更してください。drawImageメソッド内のw,hもお持ちの画像のアスペクト比に合わせて変更してください)

<!DOCTYPE html>

<html>

<head>
  <meta charset="UTF-8">
  <title>image</title>

  <style>
    button {
      width: 100px;
      height: 30px;
      margin-right: 10px;
    }
  </style>

  <script>
    let ctx;

    function appear() {
      const canvas = document.getElementById("canvas");
      ctx = canvas.getContext("2d");

      let img = document.getElementById("img1");
      ctx.drawImage(img, 100, 100, 300, 300);
    }

    function disappear() {
      ctx.clearRect(0, 0, 500, 500);
    }
  </script>
</head>

<body>
  <canvas id="canvas" width="500" height="500"></canvas><br>
  <button onclick="appear()">表示</button>
  <button onclick="disappear()">非表示</button>
  <img src="#" id="img1" style="display: none;">
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】連想配列の配列に対して、同じkeyの値を足し合わせたい

やりたいこと

下のように、連想配列(オブジェクト)の配列があったときに同じkeyの値を足し合わせたいことがありました。(きれいなデータを想定しています。valueはすべて数値です。)

const objs = [
    {key1: 100, key2: 200, key3: 300},
    {key1: 100, key2: 150, key3: 100},
    {key1: 100, key3: 200, key4: 100},
]

sumObjects(objs)
// {key1: 300, key2: 350, key3: 600, key4: 100}

シンプルに書けないかと考えてはみたのですが、うまい書き方が見つかりません(Python3.9のように単純に足す(+)ことはできない)。

なので、結局
「結果を入れる空の連想配列を用意して、配列の中の連想配列を1コずつ見ていく。その中のkeyごとに値を足していく。」
という手続きをそのまま書きました。

やったこと

function sumObjects(objs) {
    return objs.reduce((res, obj) => {
        for (key in obj) {
            key in res? res[key] += obj[key]: res[key] = obj[key]
        }   
        return res 
    }, {})
}

とりあえず書いてしまいましたが、もっとうまい書き方を募集中です。

@standard-software さんに指摘いただいた内容を修正しました。(2020-10-17 06:11追記)
  修正前のコードはreduceに初期値が指定されておらず、引数のオブジェクトを破壊していました。

気を付けたこと

・4行目、res[key] += obj[key]だけで大丈夫かなと思ったのですが、連想配列に存在しないキーを指定するとundefinedとなり、足した結果がNaNとなってしまいダメでした。

終わりに

たぶん、Pythonのcollections.Counterのように、この問題に適した道具があるんだろうなと思いつつ書きました。
良い方法を見つけたら書き足したいと思います。

※ いただいたコメントがとても良い内容ばかりです。(2020-10-17 06:11追記)
  本文よりもむしろコメント欄のほうを見てもらいたい気持ちでいっぱいです。ぜひどうぞ!

以上、お読みいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【NuxtJS×RailsAPI】折角0からプログラミングを勉強したので、推しキャラの誕生日アプリを作ってみた。

自己紹介

Webエンジニアをしたり、マジシャンをしたり、オンライン家庭教師をしたり、イベントを企画したり、色々なことをしておりますyukiと申します。
今年の1月からプログラミングを学習し、DMMWEBCAMP入学を経てエンジニアとして勤務、約4ヶ月ほどが経過しました。

過去学習してきたことなどは、こちらの記事にまとめてありますので、良ければご覧ください。
【卒業生】DMMWEBCAMPに通おうか迷っている人に伝えたい事

成果物

NuxtJS/RailsAPI/AWS/Firebase
【Webアプリ】https://tokidosaya.com
【GitHub】https://github.com/yuki-snow1823/project-saya
コードはお世話になっているエンジニアの方にレビューをしていただき、現在リファクタリング中です。ご容赦くださいませ。

この記事を読んで得られるもの、わかること

【技術面】

  • NuxtJS×RailsAPIのWebアプリ作成の時につまづいた部分と対応策の例
  • TwitterAPI使用の申請をした話
  • その他学びになったtips

【余談】

  • プログラミング未経験から、推しの誕生日をお祝いするに至るまでの流れ
  • 「発想とプログラミング」に関する自分のポエム

もし、読み進めてくださる方がいらしたら、よろしくお願いします。

その前に推しキャラと協力してくださった神の紹介

今回、自分の突拍子もないプロジェクトに賛同してくださった二人をご紹介します。
イラストレーターのKrarisとWebデザイナーのももりんごさんです。

お二人は素敵な絵を描いてくださったり、パーツのデザインを考えてくださったり、サイズを調整する案を出してくださったり、色彩のアドバイスをくださったり…とにかく全面的に協力してくださいました。

関係的には、Krarisは私の元バイト先の後輩で、ももりんごさんはDWCの先輩です。
改めて、お二人に感謝申し上げます。

推し:リトルバスターズEXより 朱鷺戸沙耶

    
Illustration by Kraris

この金髪の子です。お誕生日おめでとう。(10/21)詳しいシナリオはここには書きませんが、ちょっと個人的に思うことには報われなさすぎるキャラクターなので、なんとか幸せになって欲しいなと思い、このアプリを作るに至りました。ちなみに、男の子は理樹君という主人公です。
是非、リトルバスターズとリトルバスターズEX、アニメやゲームで体験してみてください。

技術の話 - NuxtJS×RailsAPIのアプリ作成の時につまづいた部分と対応策の例

こちらでは作成の際にぶつかったエラーと対策に関して、TwitterAPI以外のものをまとめます。

[Rails]gem 'active_model_serializers'がインストールできない

jsonをrailsAPI側から返すにあたり、gem serializersを使用しようと思いました。しかし、一向にbundle installが通らず、以下のエラーが出ていました。

Could not find case_transform-0.2 in any of the sources
Run `bundle install` to install missing gems.

こちらの件に関しては、spring stopを実行したのちに、bundle installを再度実行で解決しました。

bundle install周りって結構エラーが起きがちですが、これまでの経験上パスの指定が間違っていることが多い印象です。which bundleして一旦削除するとかも手だと思います。

[Rails]ミス:クラスメソッド名とカラム名を同じにしてしまった

これはもう自分がアホとしか言いようがないのですが、Counterテーブルというテーブルを作成したのち、カラム名の指定をcountというものにしました。よくよく考えたらrubyには元々そんなメソッドがありました…。

counter_controller.rb
    counter = Counter.find(1)
    counter.count = counter.count + 1

こんなみるも無残なコードを書いてしまいましたが、無事に動きました。リファクタリングやより良い実装ができるように精進します。この件は対策とかはなく、命名を注意してくださいということをお伝えできればと思います。

[Nuxt]Vuetifyでrowやcolの指定が効かないと思ったら…

Vuetifyというライブラリでデザインをしていたのですが、どうしてもグリッドシステムが思うように適用されないことがありました。しばし悩んだのち調べてみたところ、こんな記事を見つけました。

https://dev83.com/vue-vuetify-basic/

(引用させていただいております。)

v-appはVeutifyを使うために必須の要素です。Vuetifyのコンポーネントは必ずv-app要素の内側に書く必要があります。v-appで囲まないでVuetifyのコンポーネントを使うと予期しない動作や表示になってしまいます。下記のようにVuetifyのコンポーネントをv-appで囲みます。

( ω) ^ ^
案の定<v-app>で囲んでいないだけでした。しっかりとガイド読んでから使えよと戒めになりました。

TwitterAPI使用の申請、実装をした話

これが今回のプロジェクト1の衝撃だったのですが、「ハッシュタグのツイート収集」に関して私は公式から提供されているウィジェットを使えばいいやと思っていました。
ところが、調べてみると2018年にそのサポートは切れており、TwitterのDeveloperとして登録をして、自分でその機能を作らなければいけないことがわかりました。

ただ、どうしても実装したい機能だったので、1から調べて申請することにしました。

【参考にさせていただいた記事】
https://qiita.com/kngsym2018/items/2524d21455aac111cdee

ありがとうございました。Twitter側のUIは変わっていましたが、ほとんどこの通りに行いました。英語に関しては、急いでいたこともあったので、全部日本語で入力したのちにGoogle翻訳(最近の精度にびっくりしました。)、流石に変な部分を修正して申請しました。

3日くらいで通ったのですが趣味で使用すること(金銭が絡まない)危険な行為を一切しないことを強調した文章にしたところ、自分の場合はスムーズに通ったような気がします。

技術の話は次で終わりですが、gem twitterを使ってハッシュタグツイートを収集する方法に関しては、また別途記事でまとめたいと思います!チュートリアルでも作りたいですね!

その他学びになったtips

ツイートする時にカード?みたいなのを出したい。

リンクをツイートする時とかにふわっと出てくるアレ、名前も知らなかったのですが実装に成功しました。
名前は、Twitter Cardというらしいです。NuxtのSPAモードでの実装はどうやるんだろう…と悩んでいたところ、こちらの素晴らしい記事に救っていただきました。

Nuxt(SPAモード) + FirebaseでTwitterシェア用にOGP画像の設定をしたい。

そして、こちらのサイトを使うとリアルタイムでチェックができます。

スクリーンショット 2020-10-16 1.25.17.png

イラストの登場にふわっとアニメーションをつけたい

イラストがふわっと出ればいいなーと思っていたところ、友人がAnimate.cssなるものを教えてくれました。今回はガッツリコードを書いてアニメーションをつけるつもりもなかったので、CDNで導入してクラス名をつけるだけで、ふわっとしたアニメーションがつきました。感謝!

余談

ここからは、未経験からエンジニアになった自分が完全に自分が作りたいものを作るに至った考えやポエムをまとめたいと思います。自己満足なので、イラっとしない寛大な方だけお進みください。

プログラミング未経験から、推しの誕生日をお祝いするに至るまでの流れ(withWEBデザイナーさん、イラストレーターさん)

キャラクターの誕生日アプリを作りたい!と思ってから以下の順序で完成に至りました。

①キャラクターの誕生日とは、どんなことをするものなのか調べる

こちらの結論は、イラストを投稿したり、お祝いメッセージを書いたりすることでした。ですから、ハッシュタグをつけたツイートの収集+投稿機能は必須で入れたいと思いました。

②イラストが必要なため、イラストレーターを探して依頼する

私は絵が描けませんし、可愛い沙耶と理樹君の絵が見たかったので、昔から仕事を依頼していたKrarisに依頼をしました。

③アイテムの配置を考えた時に、Krarisや自分よりも知見の人を探す必要があった

Krarisもアイテムの配置や色彩に知見はありましたが、本人とも話した結果、Webデザイナーとして活動している人からもアドバイスをもらおうという結論に至り、DWCの先輩であるももりんごさんに参加いただきました。

④ハッシュタグツイートの収集にTwitter Developerの申請が必要と知る

不慣れな英語で申請をし、英語のドキュメントを読んでgem twitterを駆使してなんとか実装しました
ここでも、一度エンジニアの先輩にお世話になりました。

⑤せっかくならフロントとバックを分けたいのでNuxtJSを引っ張り出す

これはもう何度も出していますが、こちらのチュートリアルのおかげです。
https://qiita.com/saongtx7/items/d97ef5aec393e704fd3f

全て、何か必要がある→行動する、勉強するというプロセスでした。今回の実装にあたり、何個も何個も勉強になった部分があるので、これからもアウトプットとして個人開発は行っていきたいなと思います。

「発想とプログラミング」に関する自分のポエム

自分の強みは発想力だと思っています。これまでも、あまり人が思いつかないことや、思いついてもめんどくさくてやらないことを、色々やってきた自負が少しだけあります。ただ、形にしたくても、どうしてもできなくて挫折した経験もあります。

今、プログラミングを学び始めて半年くらいが経ち、改めて「発想を形にする力」を貸してもらっていると感じています。それと同時に、プログラミングだけじゃ駄目だという思いが強くなっています。

デザイナーさんがいるからアプリの見た目が良くなるし、イラストレーターさんがいるから絵を通じて想いを伝えられるし、営業さんがいるから作ったものが売れるし、マネージャーさんやディレクターさんがいるから作ることに専念できるし…いろんな人がいろんな発想を使って頑張っているから、素敵なアプリやプロダクトができていると今回改めて勉強になりました。

本当に、周りの方には感謝するばかりです。ありがとうございます。

さいごに

リトルバスターズというゲームをやって、この朱鷺戸沙耶と主人公の理樹君ががたどった結末を見た時に、私は大きなショックを受けました。当然現実には存在しませんが「この二人に幸せになって欲しい」「流石にキャラクターの誕生日に向けたアプリを作る人は少ないのではないか→やってやろう!」という思いで完成に至りました。

作りたいものを形にできるって、ありがたいことだなと思います。

これからも好きなものを作っていこうと思います!
もし、プログラミングはできないけど…推しの誕生日はお祝いしたい!みたいな人がいらしたら、是非連絡ください。
作りたいものを形にしたい方も、応援したいなと思います。

そして、もしも、、、万が一、、、朱鷺戸沙耶と理樹君が好きでここまで読み切った猛者がいたとしたら…いつか沙耶アフターを作りましょう!!ご連絡待ってます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue コンポーネントを分割してpropsで値を渡す方法

はじめに

Vue 映画情報をTMDb APIを使って取得
:point_up:この記事のコードは全て上記リンクが元になってます。詳しく気になる人は、覗いてみてね〜

Vueの基礎を教えてくれた@popo62520908 さん、ありがとう:relieved:

このファイルを分割したい

「コンポーネントを分割」と言うと、どうも難しそうだが言い換えるならば「ファイルを分割」と言った感じ。

以下のようなファイルがあった場合、<v-card>の部分だけを別のコンポーネント(別ファイル)にしたいとする。

Home.vue
<template>
    <v-container>
        <v-row>
            <v-col v-for="movie in movies" :key="movie.id">
                <v-card>
                    <v-img v-bind:src="'http://image.tmdb.org/t/p/w300/' + movie.poster_path"></v-img>
                    <v-card-title>{{ movie.title }}</v-card-title>
                    <v-card-subtitle>{{ movie.release_date }}</v-card-subtitle>
                    <v-card-text>{{ movie.overview }}</v-card-text>
                </v-card>
            </v-col>
        </v-row>
    </v-container>
</template>

<script>
import axios from 'axios'

export default {
    name: 'Home',
    data() {
        return {
            movies: [],
         // 自分のkeyに置き換える
            apiKey: '***',
        }
    },
    methods: {
        getMovies() {
            // &language=jaで日本語に(日本語版がないものは空欄になるけど、、)
            axios.get(`https://api.themoviedb.org/3/movie/now_playing?api_key=${this.apiKey}`)
            .then(response => {
                this.movies = response.data.results
                console.log(this.movies);
            })
            console.log(this.movies);

        },
    },
    created() {
        this.getMovies();
    }
}
</script>

<v-card>の部分とは、下記動画で見てわかる通り「映画1つ分のcard」の事である。

子コンポーネントを作る

親コンポーネントが上記のHome.vue

そして<v-card>の部分になる子コンポーネント、CardTemplate.vueを作成しよう。

CardTemplate.vueHome.vueと同じ階層に設置する。

Home.vue

Home.vue
<!-- 省略 -->
 <v-col v-for="movie in movies" :key="movie.id">
   <!-- scriptタグ内のcomponentsで定義したcard-template -->
   <card-template
    class="d-flex"
    :oneMovie="movie"
     >
    </card-template>
 </v-col>
<!-- 省略 -->

<script>
import axios from 'axios'

// v-cardの部分になる子コンポーネント
import CardTemplate from './CardTemplate.vue'
export default {
    name: 'Home',
    components: {
        // 
        'card-template': CardTemplate,
    },
// 省略
</script>

<card-template>タグの中の:oneMovie="movie"が、子コンポーネントに渡される値。

CardTemplate.vue

CardTemplate.vue
<template>
    <v-card>
         <div>
        <v-img v-bind:src="posterImg" ></v-img>
    </div>
    <div>
        <v-card-title>{{ oneMovie.title }}</v-card-title>
        <v-card-subtitle>{{ oneMovie.release_date }}</v-card-subtitle>
        <v-card-text>{{ oneMovie.overview }}</v-card-text>
    </div>
    </v-card>
</template>

<script>
export default {
    props: [
        'oneMovie',
        ],
     data() {
        return {
            posterImg: `http://image.tmdb.org/t/p/w154/${this.oneMovie.poster_path}`,
        }
    },
}
</script>

propsで親コンポーネントから渡されたoneMovieを受け取っている。

受け取ったoneMovieをconsoleで確認してみるとこんな感じで、映画1つ分のオブジェクト?(こう言うのなんて言うの?w)が取得できている。

CardTemplate.vue
created() {
   console.log(oneMovie);
    }

スクリーンショット 2020-10-15 21.37.39.jpg

これでコンポーネント分割ができました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで配列をインデックス付きで逆順でループする

以前「Google Apps Scriptでの業界ニュース通知を社内で運用した話」に書いた通り、GoogleAppScriptを使って業界ニュースを通知しています。

簡単に言うと、「SpreadSheetにあるURLをクローリングして、新しいニュースがあれば通知し、通知済みのニュースを別のシートに書き出す」ような仕様です。他のシートの同じ行に書き出すため、次のようなコードで実装されています。

/**
 * 処理を実行する。
 * @throw クローリングで1つ以上エラーが発生した場合
 */ 
function myFunction() {
  var parsers = createParsers();
  var errors = [];
  readConfig().forEach(function(config, i) {
    if (config['skip']) return;
    try {
      var latest_news = fetchLatestNews(config['title'],
         parsers[config['parser']], [config['feed']], config['charset'], i + 1);
      postNews(latest_news, config);
    } catch (err) {
      errors.push(config["title"] + "のニュース通知で次のエラーが発生しました: " + new String(err));
    }
  });
  notifyErrors(errors);
}

ただし、これでは「先に追加したニュースソースほど先に通知される」ため、「(先に追加されたニュースソースのほうが重要度が高い傾向があるため)チャットを表示したときに重要度が低いニュースが表示されてしまう」という問題点がありました。そのためインデックスを含めて逆順にループする必要があり、reverseメソッドで配列を逆順にするだけでは対応できません。

こちらのstackoverflowによると、reduceRightというメソッドで逆順のイテレーションが可能なようです。

const array = ['alpha', 'beta', 'gamma'];
array.reduceRight((_, elem) => console.log(elem), null);

// この方法は、インデックスは逆順にならないことが利点
array.reduceRight((_, elem, i) => {
  console.log(elem);
  console.log(i);
}, null);

先程のコードは、次のようなコードに書き換えました。

/**
 * 処理を実行する。
 * @throw クローリングで1つ以上エラーが発生した場合
 */ 
function myFunction() {
  var parsers = createParsers();
  var errors = [];
  // 重要なニュースを後に通知するためreduceRightを使う
  readConfig().reduceRight(function(_, config, i) {
    if (config['skip']) return;
    try {
      var latest_news = fetchLatestNews(config['title'],
         parsers[config['parser']], [config['feed']], config['charset'], i + 1);
      postNews(latest_news, config);
    } catch (err) {
      errors.push(config["title"] + "のニュース通知で次のエラーが発生しました: " + new String(err));
    }
  }, null);
  notifyErrors(errors);
}

ちなみに、先程のコードでは、実装した当時は利用できなかったという理由で利用していませんが、GoogleAppScriptの現在の仕様ではconstやアロー関数は利用できます。積極的に使っていきましょう。

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

高校生がAIでWebサイトの改善点を提案するサービスを作った話

まえがき

アクセシビリティーの観点からWebサイトを診断し、AIプラットフォームを利用して得た情報をもとにベストプラクティスを提案してくれるオープンソースのWebサービス「Visible」を開発しました。

Visible ─ アクセシビリティー診断&修正提案

GoogleのLighthouseなど、Webサイトの診断を行ってくれるサービスは以前からありましたが、診断だけではなく改善点の提案も行う新しいサービスになっています。また、アクセシビリティーに関する理解を深めてもらえるように工夫をした設計にしていたり、コマンドライン版ではスタンドアロンで実行可能なようになっています。

2020年度の「独創的アイデアと卓越した技術を持つ小中高生クリエータ支援プログラム」未踏ジュニアに採択されていて、技術・資金面で援助を頂いており、11月1日にYouTube Liveで最終成果報告会が行われる予定です!

機能の紹介

WebサイトのURLを入力することでページを診断し、改善点を自動で提案してくれます。

診断結果のスクリーンショット

以下に提案する修正の一例を紹介します。

alt属性

<img>要素はスクリーンリーダーやSEOのクローラーに画像の内容を説明するためにalt属性が提供されることが推奨されており、Google Cloud PlatformのVision APIを利用してキャプションを生成することで改善のヒントを提案します。

image.png

lang属性

Webページの言語が明示的に指定されていないと、言語情報を必要とするユーザーエージェントで問題があるためページの内容からTranslate APIから言語情報を検出し提案します。

image.png

色コントラスト比

もちろん機械学習を使わない改善提案も可能です。色コントラスト比が低いと色覚特性を持ったユーザーが使いづらいため、コントラストを上げる修正を提案します。

コントラスト

アクセシビリティーに関するベストプラクティスはW3CによってWCAGという名前で標準化されており、他にも標準に基づいたルールがいくつかあります。

提案の仕組み

診断プログラムの実行にはChromeをヘッドレスで実行できるPuppeteerを使っていて、チェックポイント(Rule)のインターフェイスを実装したプログラムを実行し、各ruleから返された値をもとにファイルの情報と紐付けて最終的に差分として表示できるようになっています。

ワークフロー。下で詳解します

全体はコア、プラグイン、アプリケーションの3つのコンポーネントから構成されていて、コアで公開されている最低限の実装とインターフェイスを実装する形でプラグインで実際の処理を書いています。プラグインの形式はESLintを参考にしています。

動作フローを図で示しています。下で詳しく説明します。

改善点の生成アルゴリズムやヘッドレスブラウザーもruleと同様にプラグインとして拡張可能なため、Google Cloud Platformに限らず別の方法を使うことも可能です。

使った技術

プロジェクトはTypeScriptで開発されています。Tech stacksは以下のとおりです。

コア部分

  • Puppeteer - Chromeを使ったヘッドレスブラウザーです。診断プログラムの実行に使っています
  • domhandler - HTMLのASTです
  • PostCSS - プリプロセッサーとして知られていますが、stylelintを参考にASTとして使っています

Webバックエンド

  • Clean Architecture - 4層にレイヤー分けした有名なバックエンドの設計方法です。
  • TypeORM - TypeScript向けのORMです。
  • Bull - Redisを使ったジョブキューのフレームワークです
  • Apollo - Node.jsのGraphQL実装です

Webフロントエンド

  • Next.js - SSR/SSG/LambdaをやってくれるReact向けのBFFです
  • Apollo - Node.jsのGraphQL実装です
  • Tailwind CSS - Utility-firstのCSSフレームワークです
  • i18next - JS向けの国際化ライブラリです。

その他

  • GitHub Actions - CI/CDに使っています
  • Docker - Web版のデプロイに使っています
  • Yargs - CLI版に使っているフレームワークです
  • Lerna - JSのモノリポジトリのためのツールです

開発時のエピソード

Webアクセシビリティーという技術には前から興味があった一方で、僕自身は支援技術の利用を迫られたことがなく、正しいマークアップを心がけるモチベーションは専ら検索エンジンへの最適化くらいだったため、知識はあってもアクセシビリティーを欠いたWebサイトを作ってしまうことが多くあり、ESLintみたいに修正の方法も教えてくれるソフトウェアがあればいいと考えていました。

同時に、未踏ジュニアに応募できる年齢制限が17歳までで、当時僕はすでに17歳だったため最後にチャレンジしてみたいと考えていて、丁度いい機会にそれを作って応募してみようとプロトタイプを制作し始めました。

プロトタイプ

僕自身は中学生のときからプログラムを書いていてアルバイトだったりもしてるのでコードはかなり書いていましたが、それでも応募まで数ヶ月しかない状態でプロトタイプを仕上げるのは結構厳しく(正直採択後よりも応募段階のほうが忙しかったかもしれない)明確にゴールを設定する必要がありました。

応募自体にはプロトタイプは必須ではなくプロダクトの概要を書いた書類を送れば良かったものの、採択されるには僕自身が持っている技術を示して最後まで作り上げることができることを証明する必要がありました。逆に、それを示せればどこが面白いのかは伝わると判断し診断できる項目は最低限に絞り、「修正の提案」はバッサリ捨てて、「URLを入力したら診断結果が出てくる」ところまでをやることにしました。あとは脳内のふわふわした概念をノートに書いてドメインモデルに落とし込み、一番慣れている技術スタックでコードを書き始めました。

応募時のプロトタイプのスクリーンショット
応募時のプロトタイプのスクリーンショット

大体2ヶ月くらいで動くプロトタイプが出来上がり、無事書類選考も通過しました。その後は面接を受けることになっていて、オンラインでメンター陣にプロダクトに関する質問を受けました。正直何を言ったかよく覚えていませんが、確かプロダクト自体が将来的にどういう目標があるかみたいなことを訊かれて、漠然とした回答しかできなかったのは覚えています...。

採択後

採択後は、既にプロトタイプがあったためアジャイルで開発しました。未踏ジュニアでは定期的にメンタリングを受けられることになっていて、僕のプロジェクトでは週に一度メンターに進捗を報告しフィードバックを頂いていたので、だいたい各週でマイルストーンを設けてそれまでに小分けにした機能の開発を進めました。

ユーザーに使ってもらうまでのタスクの優先順位は完全に僕の勘で「これができるようになったぞ」って言ったときのインパクトが強い順でやっていましたが、今考えるとあんまりいい方法ではなかったかなと思います。ただ、それがあったおかげでビジネスロジックには拘ってもフレームワークに関する細かいところを弄りすぎることはなくてスピードは上がっていた気がします。

インパクト重視な機能の例
インパクト重視な機能の例

初ユーザーテスト

未踏ジュニアでは期間中、採択直後と中間時点と最後の3回登壇する機会があり(今年はオンラインでしたが)、一回目のプレゼンの機会がやってきたので既にあるプロトタイプ+αの段階のものを発表しました。

その際に、その時点のものをデプロイしたURLを共有し聴いていた方たちに実際に使っていただいたのですが、dockerの共有メモリの設定をミスっていたり非同期にするべきところを同期でレスポンスしたいたりして発表直後にサーバーがダウンしてしまい、期待していたほどのフィードバックは得られませんでした (トホホ〜)

A11yが専門の方々へのインタビュー

期間の四分の一を過ぎたあたりでメンター陣のご協力もあり某社のアクセシビリティーチームの方にフィードバックしていただく機会を頂けました。

アクセシビリティーの現場でどんなプロセスが行われているのかや、どんなツールを使っているか、チーム開発特有の問題を伺うことができ、このインタビューでストーリーラインを具体化してユーザー層を絞ることができました。タスク優先度もここらへんから明確になってきたと思います。

その後の改善

未踏ジュニアでの2回目の発表(もちろん負荷対策はしました...)やTwitterのフォロワーなどを活用して、実際に使ってもらいアンケートに答えてもらうというフィードバックループを小分けに回しました。

フィードバックは「How to create a good survey」と検索して上に出てきた良いとされる質問をパクってフィードバック用のフォームをGoogle Formsで作成したものと、Google Analyticsのタグを埋め込んだもので多面的に得られるようにしています。

特にユーザーがどの情報を欲しがっているかは重視していて、例えば未実装の機能にもURLを割り振ってそのURLにどれくらいトラフィックが発生したかで機能の優先度を付けることで開発に反映しました。

Google formのスクリーンショット

詰まったところ

すげーニッチな内容かもしれませんが、開発にあたって詰まったところのメモを書いておきます。

CSSのDOMから取れる情報とASTのマッピングができない

HTMLやCSSなどのソースコードはブラウザでパースされたあとにDOMに変換されてJavaScriptから利用可能になりますが、getComputedStlyeなどのメソッドから取得できるCSSの情報からは、どのファイルや宣言が適用されているのかわかりません。

そこで、Google Chromeの開発者ツールのAPIである Chrome Devtools Protocol を使うことにしました。CDPはCSS.styleSheetAdded というイベントから読み込まれたスタイルシートの情報を取得できるため、問題検出時にNodeのIDと該当のCSSプロパティから対応するCSSファイルを探し出し、PostCSSのASTに変換して扱うことができました。

Clean ArchitectureでORMをどこに置くか問題

書籍では「Interface adapter層はframework層が必要としているデータ形式に変換するレイヤー」と説明されており、そのためSQLクエリを発行する部分は interface adapter層、それを具体的なRDBMSで実行するのはframework層という扱いになっているのですが、ORMではこの2つのプロセスの境界が曖昧で、ググっても色んな人が全然違うことを言ってるっぽかったです。

TypeORMは(もちろん限度はありますが)どのRDBMSを使うかが抽象化されていて、最終的にormconfigで決定するようになっているので、詳細について言及していないと割り切ってinterface adapter層でDALを実装することにしました。

余談ですが、ドメインモデルをAPIの形に変換するpresenterもGraphQLの定義を直接扱うのではなくpresenter側で定義した型を使うことで依存の方向を守っています。

モノレポ(Yarn Workspace)でDockerをやるのが辛すぎる

マイクロサービスとかだとモジュール同士が依存することはあんまり無いのかと思いますが、フロントエンドとバックエンドで共有するパッケージがあるみたいなケースでYarn workspaceとDockerを使いたくなってしまうと、パッケージごとにlockfileを作れなかったりnode_modules以下がシンボリックリンクになっているから単純にコピーしても動かなかったりして詰みます。

今の所ごちゃごちゃなワークアラウンドを書いてなんとか動いています。Yarn v2 (berry) では yarn workspace focus という機能が提供されて、ほしいパッケージの依存だけをインストールして独立して動かせるようになっているっぽいので早く移行したいなと思っているのですが、Plug'n'Play周りが理解しきれていないためまだ手を付けられていません。

styled-componentsが辛い

初めてちゃんとデザインシステム的なUIコンポーネントを作ったんですが、一つのコンポーネントが複数のバリアントを持ってるみたいなとき(下記)にstyled-componentsでやると可読性が最悪で最終的にTailwindに逃げました。

const Button = styled.button`
  font-size: 12px;

  ${
    (props) => props.variant === 'primary'
      ? css`
        color: ${props.theme.primaryFgColor};
        background-color: ${props.theme.primaryBgColor};
      `
      : css`
        color: ${props.theme.secondaryFgColor};
        background-color: ${props.theme.secondaryBgColor}
      `;
  }
`;

詳細: https://qiita.com/rigarashi/private/5c97be5ed8fb15ea2d96

Utilify-firstをCSS-in-JSに輸入したstyled-systemとかxstyledというやつもあるっぽいんですが、テーマの型が静的に付かなかったので見送りました。

Puppeteerで並列処理

puppeteer-clusterというライブラリがいい感じっぽかったのですが、unmaintained気味なのと、pageのインスタンスをコールバックで受け取って簡単な処理をすることしかできず、例えばObservableに変換するみたいな今回のユースケースだと厳しかったのでやめました。

代わりに、いわゆるObject Poolingでbusyなインスタンスとそうでないインスタンスを管理して、暇そうなインスタンスに処理を投げるようにしました。こういうのは多分ワーカープロセスをforkしていくのがいいのかと思いますが、Puppeteerを起動した時点ですでにChromiumのプロセスと分かれるのであんまり意味がなかったのと、GraphQLのsubscriptionのソースになっているのがただのシングルトンで、redisとかを挟んでなくて同じプロセスで呼ばないとブラウザに伝えられないためです。

あとがき

蔑ろにされがちなアクセシビリティーで人々の気を引くにはどうすればいいか考えた結果、若干地雷っぽいタイトルになってしまったことをお赦しください。

また、web版等公開していますのでぜひお試しいただきフィードバックをお訊かせいただけると嬉しいです。

関連リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オセロの盤を作成する

オセロの盤の作り方

8✖️8のオセロの盤を作ります。まずはbodyタグ内を以下のように記述します。body要素内のonload属性はブラウザがページの読み込みを完了した際にダブルクォーテーション内の関数を実行してくれます。

table要素内にセルを並べていくので、id属性に「board」を指定します。

<body onload="init()">
  <h2>オセロの盤</h2>
  <table id="board"></table>
</body>

head要素内のscript要素は以下のようになります。
変数boardにbody要素内のtable要素を取得させます。

繰り返し文
「for(let r = 0; r < 8; r++) {
let tr = document.createElement("tr");


}
board.appendChild(tr);
}」
の部分でオセロの盤の行にあたるものを8つ作ります。
「createElement( )」メソッドは「document.createElement("要素名")」のかたちで引数に指定したHTML要素を生成することができます。
「appendChild( )」メソッドは
「親要素.appendChild(子要素)」のかたちで親要素の末尾に子要素を追加することができます。
今回は「tr」要素を8つ作り、table要素に追加しています。

繰り返し文
「for(let d = 0; d < 8; d++) {
let td = document.createElement("td");
td.className = "ceil";
tr.appendChild(td);
}」
の部分はオセロの盤の列にあたるものを8つ作っています。
変数tdに生成したHTML要素である「td」要素を代入し、「tr.appendChild(td)」でtr要素一つ一つにtd要素を8つ追加しています。
「td.className = "ceil"」で作成したtd要素に「ceil」というclass属性を付与しています。classNameプロパティはclass属性の取得や設定に用いることができるプロパティです。

  <script>
    function init() {
      let board = document.getElementById("board");

      for(let r = 0; r < 8; r++) {
        let tr = document.createElement("tr");
        for(let d = 0; d < 8; d++) {
          let td = document.createElement("td");
          td.className = "cell";
          tr.appendChild(td);
        }
        board.appendChild(tr);
      }
    }
  </script>

完成は以下になります。コピーしてhtmlファイルに貼り付けていただければ、ブラウザで表示できます。

<!DOCTYPE html>

<html>

<head>
  <meta charset="UTF-8">
  <title>board</title>

  <style>
    table {
      border: 3px solid black;
      border-collapse: collapse;
    }
    .cell {
      width: 50px;
      height: 50px;
      background-color: green;
      border: 1px solid black;
    }
  </style>

  <script>
    function init() {
      let board = document.getElementById("board");

      for(let r = 0; r < 8; r++) {
        let tr = document.createElement("tr");
        for(let d = 0; d < 8; d++) {
          let td = document.createElement("td");
          td.className = "cell";
          tr.appendChild(td);
        }
        board.appendChild(tr);
      }
    }
  </script>
</head>

<body onload="init()">
  <h2>オセロの盤</h2>
  <table id="board"></table>
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascript コンテキスト&コールスタック

Javascriptを普段触ってますが、初学者の方でもわかるようにコンテキストとコールスタックについてまとめてみました。

◎コンテキストとは

コードが実行されている時の状況を表します。
実行される状況によってコンテキストが変わることを実行コンテキストと表現します。
実行コンテキストには、グローバルコンテキストと関数コンテキストがあります。

◎グローバルコンテキスト

Javascriptファイル直下の実行環境を指します。
グローバルコンテキストでは、以下の変数やオブジェクトが使用可能です。

スクリーンショット 2020-10-16 15.56.49.png

関数コンテキスト

関数の内部の実行環境になります。
関数が実行される時に実行されるコンテキストを指します。
関数コンテキストでは、以下の変数やオブジェクトが使用可能です。
スクリーンショット 2020-10-16 16.00.20.png

コールスタックとは

callstack => Call(呼ぶ) + Stack(積む)
実行中のコードが辿ってきたコンテキスト(文脈)の積み重ねです。
現在どの関数が現在実行されているのか、その関数の中でどの関数が呼び出されたかなどを追跡することができる。

以下のような処理があったとします。

function fourthFn(){
  console.log("callstack 4")
}

function thirdFn(){
  console.log("callstack 3");
 fourthFn();
}

function secondFn(){
  console.log("callstack 2");
  thirdFn();
}

function firstFn (){
  console.log("callstack 1")
  secondFn();
}

firstFn();
//firstFn関数を実行

上記の処理は下記のようなコールスタックが生成されます。
スクリーンショット 2020-10-16 18.15.18.png

以下がコールスタックの流れです。

コード実行→グローバルコンテキストが生成→firstFn実行→secondFn実行→thirdFn実行→fourthFnが実行され、現在実行中の関数になります。

次に関数の実行が終了した時の流れです。

fourthFnが終了→thirdFnコンテキストが消滅→secondFnコンテキストが消滅→firstFnコンテキストが消滅→グローバルコンテキストが消滅

積み上がったコンテキストは関数の実行が終了されると順番が後のコンテキストから順に消滅します。
※fourthFn→thirdFn→secondFn→firstFn→グローバルコンテキストの流れでコンテキストが消滅します。

まとめ

コンテキストとコンテキストが積み上がって生成されるコールスタックについての説明でした。
実行からコンテキストの消滅までをまとめてみました。

参考サイト

[MDN(https://developer.mozilla.org/ja/docs/Glossary/Call_stack)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】FizzBuzz問題を解いてみよう

今回作ったもの

テキストボックスに入力した整数の倍数をそれぞれFizzBuzz、公倍数をFizzBuzzとして順々に表示させます。
また、テキストボックスに整数値以外(文字列や小数値)が入力された場合は整数値を入力してくださいと表示されます。
ezgif-7-210c9016cd1a.gif

コード解説

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>FizzBuzz問題</h1>

    <div class="r1">
        <p class="p1">FizzNum:</p>
        <input type="text" value="" id="text1">
    </div>

    <div class="r2">
        <p class="p2">BuzzNum:</p>
        <input type="text" value="" id="text2">
    </div>

    <p><input type="button" value="実行する" onclick="fizzbuzz()"></p>
    <div id="fizzbuzz-result"></div>
    <script>
        function fizzbuzz() {
            const fizzNum = Number(document.getElementById('text1').value);
            const buzzNum = Number(document.getElementById('text2').value);
            const fbArry = [];
            const fbText = [];
            const fbResult = document.getElementById('fizzbuzz-result');

            if(!Number.isInteger(fizzNum) || !Number.isInteger(buzzNum)) {
                fbResult.textContent = '整数値を入力してください';
                return;
            }

            for(let i = 1; i < 101; i++){
                if(i % fizzNum === 0 && i % buzzNum === 0) {
                    fbArry.push('FizzBuzz' + i);
                } else if(i % fizzNum === 0) {
                    fbArry.push('Fizz' + i);
                } else if(i % buzzNum === 0) {
                    fbArry.push('Buzz' + i);
                }
            }

            for(const v of fbArry) {
                fbText.push(`<p>${v}</p>`);
            }

            const fbHTML = fbText.join('');

            fbResult.innerHTML = fbHTML;
        }
    </script>
</body>
</html>

まず、htmlでテキストボックスや、Fizz,Buzz,FizzBuzzを表示するdivタグを用意します。
JS本文では、
1. テキストボックスからfizzbuzzの倍数となる数字を数値型で取得する
2. 1.で取得した値が整数値ではなかった場合に整数値を入力してくださいと表示させる処理(※整数値ではなかった場合、関数の処理はそこで終了)
3. 1~100の数値の中で公倍数のものをFizzBuzzという文字列とともに、fizzの倍数のものをFizzという文字列とともに、buzzの倍数のものをBuzzという文字列とともにfbArryに配列として追加
4. 3.のfbArry<p></p>タグで囲ったものを配列としてfbTextに追加
5. 4.のfbTextjoin(''),(カンマ)を除いた文字列としてfbHTMLに代入
6. 最後にfbResult.innerHTML = fbHTMLでHTML本文に追加

↑の流れになっています。

※コード本文追記(修正)

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>FizzBuzz問題</h1>

    <div class="r1">
        <p class="p1">FizzNum:</p>
        <input type="text" value="" id="text1">
    </div>

    <div class="r2">
        <p class="p2">BuzzNum:</p>
        <input type="text" value="" id="text2">
    </div>

    <p><input type="button" value="実行する" onclick="fizzbuzz()"></p>
    <div id="fizzbuzz-result"></div>
    <script>
        function fizzbuzz() {
            const fizzNum = Number(document.getElementById('text1').value);
            const buzzNum = Number(document.getElementById('text2').value);
            const fbArry = [];
            const fbResult = document.getElementById('fizzbuzz-result');

            if(!Number.isInteger(fizzNum) || !Number.isInteger(buzzNum)) {
                fbResult.textContent = '整数値を入力してください';
                return;
            }

            for(let i = 1; i < 101; i++){
                if(i % fizzNum === 0 && i % buzzNum === 0) {
                    fbArry.push('FizzBuzz' + i);
                } else if(i % fizzNum === 0) {
                    fbArry.push('Fizz' + i);
                } else if(i % buzzNum === 0) {
                    fbArry.push('Buzz' + i);
                }
            }

            for(const v of fbArry) {
                const p = document.createElement('p');
                p.textContent = v;
                fbResult.appendChild(p);
            }
        }
    </script>
</body>
</html>

後から気づきましたが、最後のfor...ofの中でこう書いた方がすっきりするような気がしたのでこちらも載せておきます。
変更箇所の処理は、

  • for...ofの中でpタグを作り、pタグそれぞれのテキストにfbArryの値を代入して随時fbResultに追加する

↑の流れになっています。

※ただ、コメントで教えていただいた通り、この書き方だとDOMの再レンダリングを繰り返してしまい処理が重くなるようです。お教えくださった方ありがとうございます!

感想

FizzBuzz問題を解くこと自体はすんなりいきましたが、テキストボックスに整数値以外が入力された時の処理で少し時間がかかりました。
Number関数のメソッドは微妙に返す値が違うので気を付けないとですね(笑)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分用メモ 初心者のgit

はじめに

私自身大学卒業後はフロントエンドエンジニアとして働いていこうと考えております。そのためにもチーム開発では必須であるgitがどのようなものであるかは最低限、理解しておかなければなりません。
もちろんチーム開発をすることがgit理解の1番の近道だとは思いますが、現段階では、どのようなものか理解し、自分が作成したものをgit hub上で公開、管理できるようになっていれば最低限、いいのかなと考えております。

この記事では、ローカルファイルからのリモートへpush方法、変更の方法を載せていきたいと思います。
そのため私と同様の初心者の方向けの記事になるかと思います。
至らない点などあると思いますが、その時は、教えていただけると幸いです。

Gitとは

Gitとは、分散型バージョン管理システム

簡単にいうとファイルの状態(バージョン)を保持し、元の状態へ戻したり、更新したり、することのできるツール。
ひとつひとつコードの編集の度に古いバージョンの日付や時刻ををつけて保存して、また新しい作業をするようなことをしていては、時間はかかりますし、人的ミスも増えることは、いうまでもありません。そういった作業を無駄なく、効率的に行うためのツールがGit

これができると複数人で管理するときにとても便利!!

git hub

git hubは、gitを使った開発者向けのwebサービス。
クラウド上にリモートリポジトリを提供することで、遠く離れた開発者同士でもソースコードを管理できるツール

origin リモートリポジトリ(git hub)の別名
master デフォルトのブランチの名前。メインになる。git initしたときのデフォルトで入る名前

基本的なコマンド

git init     //git管理するリポジトリを作成するときに利用(一番最初)
git add      //バージョン管理対象へ追加 ステージング
git commit -m 'commit message'    //ファイルやディレクトリの追加・変更をリポジトリに記録
git clone    //リモートリポジトリからのコピー
git push     //コミットをリモートリポジトリに追加
git pull     //変更されたリモートリポジトリをローカルへ取り込む
git status   //ステージング、管理対象のファイルの一覧
git checkout  //ブランチの切り替え コミットの移動でも、、、
git branch   //ブランチ一覧
git log      //コミット履歴
git merge    //変更をマージさせる

github(リモートリポジトリ)変更、更新方法

1.ブランチ作成

git checkout -b 作成するブランチ名

Switched to a new branch ブランチ名と出たら成功です!

git branch -a でブランチの一覧を確認してみていください!!

2.作成したブランチをリモートに登録

git push -u origin 作成したブランチ名

スクリーンショット 2020-10-16 16.22.53.png
git hubのこの場所で確認できます。

3.git add で変更点の追加

git add 追加したいファイル、ディレクトリ

オプションでadd .とするとカレントの変更点全てをaddできる

4.git commitでファイルをコミット

git commit -m 'コミットメッセージ'

git logでコミットを確認できる!

5.ファイルをpush

git push

これでgit hubの追加したブランチのソースコードが更新されています。

6.マージする

git checkout master 

masterブランチに切り替える

git merge ブランチ名

これでmasterブランチが先ほどのブランチとmergeされ更新される

7.git hubのリポジトリに反映

git push origin master

これでgit hub上に変更を更新できる。

最後に作ったブランチを削除

git branch --delete ブランチ名

最後に

この流れが初心者ののgit管理で最低限必要なものかなと思いました。
まだまだ学ぶべきものはあると思いますが、それはこれから深めていこうと思います。
記事についてですが、間違っている点などあれば教えていただけると幸いです。

参照

Git基本操作。ローカルでの作業からリモートリポジトリのマージまで。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavascriptでFirebaseのrealtimeDatabaseを使う時のセキュリティを考える

Firebaseを使うと簡単にデータベースを使う事ができる。という事はしっていた。
realtimeDatabaseなどに関しては、SQLみたいな知識もほとんどいらない状態だった。

好きなjsonを作ってそのままの形で内部のjsonに吊り下げていける。というイメージで、javascriptで階層をいくらでも深くほれる。が、ここで問題がある

好きなようにデータベースをいじれる→おもいっきり突っ込まれる→従量課金→破産。

このサービスの利点であり、欠点は従量課金である事。従量課金であれば、スパムみたいのにアタックされたら、金額もかなりが吹っ飛ぶとは思う。

問題点:大量にデータをつっこまれると破産

ここでから、できる事を整理をすると

ユーザーに認証をさせる。

認証はドメインなどで認証ができるので、アップロードするサーバーのドメインで認証をすると変なアクセスをはじける。
authorizedで出来る。(匿名ログイン)

認証情報がないとデータベースへ投稿をできなくする。

これは割とrulesっていうので、出来る。

認証情報を元にIDを発行してIDに紐付いた構造だけをいじれる

これもrulesで対応ができる。

ここまでで、投稿したユーザーがtable/userID/***のみしかいじれないようになるので、他の人の情報を消したり、変更したりってできないようにする事ができた。

ユーザーの特定はできても、データを突っ込まれる可能性はまだある。

ここからは少しひらめきを書いていく。

createを使わないでupdateを使う

{name:aaa,old:17}みたいのを変更はできても{name:aaa,old:17,sex:male}みたいに項目は追加できない。一つの情報量は一定以上には入らないはずなので、データを大量に突っ込まれる可能性は少なくなる

createはユーザー登録時に行う

データを入力するエリアを作るにはどうしたらいいかと言うと、Functionと言う機能がある。functionはイベントをトリガーにして内部でプログラムを実行する事ができるので、ユーザーが認証をしたら、その段階で、そのユーザーの情報を{name:'',old:0}みたいのをcreateする。これは内部的な処理になるので、データベースのcreateがfalseでも作れるはず。もしくは、作れるように設定ができるはず。

1ユーザーから、大量のデータをつっこまれなくても、大量のユーザーからデータを突っ込まれる可能性はまだある。

同時並行で大量の認証をするアカウントを作って、それを書き込みまくる。みたいな事をされない限り問題はなくなる。

ここで、authorizeを見ると

同一IPからの新規作成の数を制限

30に変更などもできるので、大量の認証アカウントを同一IPから発行する事もできなくなる。


ここまででが、現状javascriptで出来るであろうセキュリティ対策の内容で、ここで更に出来ることといえば、
各データの入力できる値の制限ができるのであれば(例えば文字数20までとか)

が対応方法としては有力かなと思います。

あとは、予算を設定している場合には、予算に対してのどれくらいの予算が消化されているかでアラート通知が来るようにはなっているが、一晩で気づかぬウチにアタックをくらっていたら、終わりじゃないかとは思ってるが、予算のアラートをいれておくのも、それなりに有効な手ではあると思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】初回ログイン時・jQueryを使用した、画面を真っ二つに割る方法

目標

split.gif

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

【Ruby on Rails】初回アクセス時に一度だけ表示(jquery.cookie.js使用)
こちらに少し手を加えた形となりますので、
コードをそのまま使うとcookieがなくなるまでは表示出来ません。

実際のコード

app/views/layouts/application.html.erb
<div class="indication-left"></div> # 追加
<div class="indication-right"></div> # 追加
<div class="box">
  <p>下記の表示終了ボタンを押すと、<br>更新しても見ることは出来ません。<br>
    新しいブラウザを立ち上げると表示されます。
  </p>
  <button>表示終了</button>
</div>
app/assets/stylesheets/application.css
/* 追加 */
/* ここから */
 .indication-left, .indication-right{
  position: fixed;
  top: 0;
  width: 100%;
  height: 100vh;
  background-image: url("image1.jpg");
  background-size: cover;
  background-position: center;
  z-index: 1040;
  transition: 3s;
}
.indication-left{
  left: 0;
  clip: rect(0px 50vw 100vh 0px);
}
.indication-right{
  right: 0;
  clip: rect(0px 100vw 100vh 50vw);
}
.leftslide{
  transform: translateX(-100%);
}
.rightslide{
  transform: translateX(100%);
}
/* ここまで */


.box{
  position: absolute;
  top: 40%;
  left: 35%;
  width: 400px;
  height: 200px; 
  background-color: #ffffff;
  z-index: 1050; /* 変更 */
}
.box p{
    padding: 15px;
}
.box button{
  display: block;
  margin: 0 auto;
}
app/assets/javascripts/application.js
$(function(){
  $(".indication").show();
  if($.cookie('Flg') == 'on'){
    $(".box").hide(); // indicationの子ではなくなったので追加
    $(".indication-right").hide(); // 追加
    $(".indication-left").hide(); // 追加
  }else{  
    $(".box").show(); // 追加
    $(".indication-right").show(); // 追加
    $(".indication-left").show(); // 追加
  }
  $(".box button").click(function(){
    $(".indication-right").addClass("rightslide"); // 追加
    $(".indication-left").addClass("leftslide"); // 追加
    $(".box").fadeOut(); // 追加
    $.cookie('Flg', 'on', { expires: 1, path: '/' });
  });
});

z-indexについて

bootstrapを使用する場合、今回のような機能の実装では
.fixed-topよりも高い位置に表示する必要があったため、
1030より高い数値を指定しております。

bootstrap.css
.fixed-top {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  z-index: 1030;
}

clip: rect( ); について

clip要素とは画像などの要素について、切り抜き領域の外にある内容を表示せず、
切り抜き領域を指定するプロパティです。
指定方法はrectととなり、rectの長さには
rect(上端からの距離, 右端からの距離, 下端からの距離, 左端からの距離)のように、
上から時計回りの順でコンマで区切って4つの値を指定します。

右側の画像clip: rect(0px 50vw 100vh 0px);
左側の画像clip: rect(0px 100vw 100vh 50vw);

まとめ

jqueryを使用せず、cssのinputを使用した方法もあるため、
両方の知識を入れておくのは大事だと思います。
近々更新できればと思います。

またtwitterではQiitaにはアップしていない技術や考え方もアップしていますので、
よければフォローして頂けると嬉しいです。
詳しくはこちら https://twitter.com/japwork

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サイコロの作り方

javascriptでのサイコロ作成

javascriptでサイコロを作る方法です。

<body>
  <button onclick="dice()">サイコロ</button>
  <p id="result"></p>
</body>

ボタンタグをクリックすると1〜6の数字がpタグに表示されます。
onclick属性をbutton要素に記述することでボタンをクリックしたときにjavascriptに作成したdice関数を起動させます。

<head>
  <script>
    function dice() {
      let d = document.getElementById("result");
      let r = Math.ceil(Math.random()*6);
      d.textContent = r;
    }
  </script>
</head>

dice関数には変数dとrを定義します。dにはサイコロを振った結果を表示するためにp要素に指定したid属性のresultを取得させます。
rには1〜6のどれかの数字がランダムに取得できるようにします。

「Math.ceil( )」メソッドは引数に指定した値を小数点以下繰り上げしてくれるメソッドです。(仮に1.34という少数なら2に繰り上げてくれます。)

「Math.random( )」メソッドは0より大きく1より小さい値をランダムに返してくれるメソッドです。「Math.random( )*6」とする事で0より大きく6より小さい数値を返してくれます。

この2つのメソッドを組み合わせることで1〜6のランダムな数値を取得できるのでサイコロが完成します。

「d.textContent = r;」で1〜6の結果をid属性でresultを持つp要素に表示することができます。

修正

上記の内容に間違いがあったようなので、修正します。
「Math.random()」メソッドは0より大きく1より小さい値を返すのではなくて、【0以上1未満の値を返す】ようなので、script要素内は下記になります。

  <script>
    function dice() {
      let d = document.getElementById("result");
      let r = Math.floor(Math.random()*6+1);
      d.textContent = r;
    }
  </script>

「Math.floor()」メソッドは引数に指定した値を小数点以下切り捨てしてくれるメソッドです。「Math.floor」の引数内は1以上7未満になるので、1〜6の値をランダムに変数rに代入してくれます。

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>dice</title>

  <script>
    function dice() {
      let d = document.getElementById("result");
      let r = Math.floor(Math.random()*6+1);
      d.textContent = r;
    }
  </script>
</head>

<body>
  <button onclick="dice()">サイコロ</button>
  <p id="result"></p>
</body>
</html>

上記のコードをhtmlファイルにコピーすれば試すことができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

canvasの設定方法

canvasの設定方法

ブラウザに自作のゲームを表示する場合に、HTML5から使用できるようになったcanvas要素を使用することでスクリーンに表示することができるようになりました。

設定方法を記載します。

<body>
  <canvas id="canvas" width="700" height="700"></canvas>
</body>

まず、body要素内にcanvas要素を記述します。id属性は後でjavascriptでcanvas要素を取得するために設定します。
widthでブラウザに表示するcanvasの画面の横幅を指定します。
heightでブラウザに表示するcanvasの画面の縦幅を指定します。

  <script>
    var ctx;
    var canvas = document.getElementById("canvas");
    ctx = canvas.getContext("2d");
  </script>

次にhead要素内にscript要素を記述します。変数canvasに先ほどbody内のcanvas要素に指定したid属性を取得するための記述をします。
変数ctxには上記の記述をすることでブラウザのcanvasの画面に描画する準備が完了します。「getContext( )」メソッドはcanvasに描画するためのインスタンスを取得するという機能があります。引数の「2d」は2Dの描画をするという意味があります。今現在2D以外の3
Dにも対応したのかは不明です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptのメソッド(初心者向け)

この記事の対象者

JavaScirptのメソッドが全然わからない方

JavaScriptのメソッドとは

こういうのです。

test.html
<body>
    <script>
        arrayA = ["リンゴ","ゴリラ","ラッコ"]
        arrayA.shift()
    </script>
</body>

2行目の事です。ドットの後に何やら書いてあるところがメソッドです。

arrayA.shift()

解説 

この右辺のarrayA.shift()が何をしているかというと

1.配列arrayAに対して.shift()で配列の一つ目の要素を削除 ("リンゴ"が削除)
   ["リンゴ","ゴリラ","ラッコ"] => ["ゴリラ","ラッコ"]

以上です。

つまり「.」を挟んだ左側の物(arrayA)に対して右側の処理(shift())を行う」のです。

いいですか?左側の物に対して右側の処理を行うです。これだけです。
あなたは基本的にこの右側の処理についてググることになります。

ちなみに

皆さんが最初に通るであろう console.log('hello world')  も 左側の物(console)に対して右側の処理(log('hello world'))を行う」をしているということなんですね。

おわりに

今回学んだ左側の物に対して右側の処理を行うという考え方でできないんだけど?という記述に今後出会うだろうと思います。その時は初級者ではないはずなのでもっと難しそうな解説が読めるはずです。触りはこれくらいでよいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm install / yarn add でパッケージをインストールする【for初心者】

前提

参考:npmとyarnのコマンド早見表

npmやyarnを使ってパッケージをインストールしたら、どこに保存されているの?
グローバル環境にインストールローカル環境にインストール に保存される

グローバル環境とは

システム共通ディレクトリ、nodeのグローバル環境のこと
システム全体で利用するツール・コマンド類をインストールする場合に利用する

ローカル環境とは

自身で作ったアプリケーション(プロジェクト)の中の環境のこと
追加されたパッケージは、アプリケーション(プロジェクト)の中のpackage.jsonの中に「どのパッケージをインストールするか」を記述する
アプリケーション単位で利用するパッケージをインストールする場合に利用する


グローバル環境へインストール

nodeのグローバル環境へパッケージをインストールされる
-g オプションはグローバル環境のこと
installは i で省略できる

npm

npm install -g パッケージ名

// 以下でもOK

npm i -g パッケージ名

yarn

yarn global add パッケージ名 

ローカル環境へインストール

ローカル環境へインストールには --save をつける
インストールされると、package.json ファイル内の dependencies に追記される

npm

npm install --save パッケージ名

yarn

yarn add パッケージ名

ローカル環境へ、開発パッケージをインストール

インストールする際に --save-dev をつける
インストールされると、package.json ファイル内の devDependencies に追記される
--save-dev-D で省略できる

npm

npm install --save-dev パッケージ名

// 以下でもOK

npm i -D パッケージ名

yarn

yarn add --dev パッケージ名

Tips

github や gitlab でチーム開発をしているとき、新しいパッケージが導入されたけど、まだ自分のローカル環境で設定されていないとき

$ git pull origin master

// npmで開発している場合
$ npm install

// yarnで開発している場合
$ yarn install
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsのダイナミックルーティングを実装してみた

Next.jsでは、getStaticPaths・getStaticProps・getServerSidePropsといった似た名前の関数が多く違いがわかりづらいですよね...

今回は、その中でも使用頻度の高いgetStaticPaths・getStaticPropsを使用し、ダイナミックルーティングの簡単なサンプルを作成したいと思います。

こちらが今回作成するサンプルの完成形です。
画面収録-2020-10-15-17.32.51.gif
URL:https://nextjs-dynamic.vercel.app/

JSONPlaceholderのAPIサーバーを使用し、記事一覧・記事詳細ページを表示しています。
見ての通り、全てのデータが事前ビルドされているため表示が高速です。

では、実装までの流れについて説明していきます。

1. ダイナミックルート用のファイル作成

まずは、ダイナミックルート用のファイル(記事詳細ページ)を作成します。
Next.jsでは、pages内でファイル名に[]を使用することで自動的にダイナミックルート対象となります。

 /pages/[id].js                               
   ├ http://localhost:3000/1
   ├ http://localhost:3000/2
   └ ...

ダイナミックルート用のファイルでは、getStaticPathsgetStaticPropsの関数が必要です。

getStaticPaths:ビルド時にレンダリングする必要のあるパスのリストを生成する
getStaticProps:ビルド時に静的なファイルを生成し、ページコンポーネントで使用する値を用意する

これらの関数はクライアント側で実行されることはなく、必ずサーバーサイドで実行されます。

以下は作成した記事詳細ページです。

pages/[id].js
import Link from 'next/link'

// post:getStaticPropsから取得したデータ
export default ({ post }) => {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
      <Link href="/">
        <a>Back</a>
      </Link>
    </div>
  )
}

export const getStaticPaths = async () => {
  // 外部APIエンドポイントを呼び出しデータ取得
  const res = await fetch("https://jsonplaceholder.typicode.com/posts")
  const posts = await res.json()  

  // 事前ビルドしたいパスを指定
  const paths = posts.map((post) => ({
    params: {
      // ファイル名と合わせる ※文字列指定
      id: post.id.toString(),
    },
  }))
  // paths:事前ビルドするパス対象を指定するパラメータ
  // fallback:事前ビルドしたパス以外にアクセスしたときのパラメータ true:カスタム404Pageを表示 false:404pageを表示
  return { paths, fallback: false }
}

// paramsには上記pathsで指定した値が入る(1postずつ)
export const getStaticProps = async ({ params }) => {  
  // 外部APIエンドポイントを呼び出しデータ取得
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`)
  const post = await res.json()  

  // ページコンポーネントにpropsとしてに渡す
  return {
    props: {
      post
    },
  }
}

画面表示までの流れ

  1. getStaticPathsで、レンダリングする必要のあるパスのリストを生成する
  2. getStaticPropsで、1で生成したパスのリストから1postsずつAPIエンドポイントを呼び出しデータを取得する
  3. ページコンポーネントがgetStaticPropsからデータを受け取り、画面に表示される

実際にURLにアクセスしてみると、表示が切り替わることが確認できるかと思います。

画面収録-2020-10-15-16.30.12.gif

2. 記事一覧ページ作成

次に、それぞれの詳細ページにリンクする記事一覧を作成します。

こちらのページに関しても、getStaticPropsでページコンポーネントで使用する値を事前に用意する必要があります。

以下は作成した記事一覧ページです。

pages/index.js
import Link from 'next/link'

// posts:getStaticPropsで取得したデータを受け取る
const Home = ({ posts }) => {
  return (
    <div>
      <h1>記事一覧</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            {/* リンク先を指定 */}
            <Link href={`/${post.id}`}>
              <a>{post.title}</a>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  )
}

// ビルド時にデータを取得し静的なファイルを事前に生成
export const getStaticProps = async () => {
  // 全記事データを取得
  const res = await fetch("https://jsonplaceholder.typicode.com/posts")
  const posts = await res.json()  

  // コンポーネントに渡すデータを指定
  return {
    props: {
      posts,
    },
  }
}

export default Home

画面表示までの流れ

  1. getStaticPropsで、画面表示させたいデータを取得する
  2. ページコンポーネントがgetStaticPropsからデータを受け取り、画面に表示される

以上でダイナミックルーティングの実装完了になります!

おわりに

Next.js公式がSSGを推薦していることもあり、今後さらにダイナミックルーティングを使用する機会が増えていきそうですね!

公式チュートリアルでは、実際にブログを作成しながらダイナミックルーティングについて学ぶことができるので、より詳しく知りたいという方はぜひお試し下さい!
Next.js Dynamic Routes

参考資料

Next.js 公式ドキュメント
Next.js 9.3 Released, Improves Static Site Generation
JSONPlaceholder

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript超初心者】Chromeコンソールパネルで改行するには

はじめに

ChromeのコンソールパネルでJavaScriptを実行するときに、改行の仕方がわからずEnterをクリックしてはエラーが発生し困ったので、改行の仕方をシェアしたいと思います。

コンソールパネルで改行

consoleパネル
Shift + Enterキー

こうすることで今までに書いたコードが実行されることなく改行することができます。

【おまけ】

ディベロッパーツールは⌘ + option + Cのショートカットキーで表示することができます。
左手だけで表示できるので、二本指でタップして検証を選択するよりもとても楽です!
ショートカットキーを使ったことがない方はぜひお試しください!

参考URL

it-swarm-ja.tech

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue3 v-modelの仕様

v-modelに使用するデフォルトのprop名とイベントが変更になっています

Vue2.x

SomeComponent.vue
// valueのpropで受け取り
props: [value]
// onInputで返却
this.$emit('input', updatedValue)
<some-component v-model="hoge" />
// ↑は↓と等価
<some-component :value="hoge" onInput="(val) => hoge = val"/>

Vue3

SomeComponent.vue
// modelValueのpropで受け取り
props: [modelValue]
// onUpdate:modelValueで返却
context.emit('update:modelValue', updatedValue)
<some-component v-model="hoge" />
// ↑は↓と等価
<some-component :modelValue="hoge" onUpdate:modelValue="(val) => hoge = val"/>

Vue3別例

SomeComponent.vue
// modelValue以外もつかえる
props: [foobar]
// onUpdate:propnameで返却
context.emit('update:foobar', updatedValue)
// modelValue以外のpropにv-modelで双方向バインドする場合は明示が必要
<some-component v-model:foobar="hoge" />
// ↑は↓と等価
<some-component :foobar="hoge" onUpdate:foobar="(val) => hoge = val"/>

ref https://github.com/vuejs/rfcs/blob/master/active-rfcs/0011-v-model-api-change.md

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む