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

WebView2 C#とJavaScriptの連携

昨日の今日ではあるんですが、WebView2を使ってJavaScriptとC#を連携させる方法がわかったので、記事に残します。
(筆者の個人的な事情なんですが、Qiitaの記事作成が今日で三日連続です(笑)
今日は仕事終わりなので、サクッと記事を書いて終わりたい…(笑))

コーディング

とりあえず、サンプルのコードを下記に貼り付けます。
Form1.cs と JavaScriptを仕込んだ sample.htmlだけなので、察しの良い人ならサンプルのコードを見ただけで十分かもしれません。
(.htmlに関しては適当に書いたので、あまり深く突っ込まないでください(笑))

Form1.cs
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace SampleWebView2Form
{
    public partial class Form1 : Form
    {
        /// <summary>webviewのコントロール(わかりやすい様に、デザイナーを使わずにコード側で実装します。)</summary>
        private WebView2 WebView = new WebView2
        {
            //個人の環境に合わせて下さい
            Source = new Uri("file:///C:/Users/name/Desktop/sample.html"),
        };

        /// <summary>JavaScriptで呼ぶ関数を保持するオブジェクト</summary>
        private JsToCs CsClass = new JsToCs();

        public Form1()
        {
            this.Controls.Add(WebView);
            InitializeComponent();

            //WebView2のサイズをフォームのサイズに合わせる
            WebView.Size = this.Size;
            this.SizeChanged += Form1_SizeChanged;

            //WebView2のロード完了時のイベント
            WebView.NavigationCompleted += WebView_NavigationCompleted;
        }

        /// <summary>WebView2のロード完了時</summary>
        private void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
        {
            try
            {
                if (WebView.CoreWebView2 != null)
                {
                    //JavaScriptからC#のメソッドが実行できる様に仕込む
                    WebView.CoreWebView2.AddHostObjectToScript("class", CsClass);

                    //JavaScriptの関数を実行
                    CsToJs();
                }
                else MessageBox.Show("CoreWebView2==null");
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        /// <summary>Jsのメソッドを実行</summary>
        private async void CsToJs()
        {
            //WebView.ExecuteScriptAsync("func1()").ResultをするとWebView2がフリーズする
            string str1 = await WebView.ExecuteScriptAsync("func1(\"C#からの呼び出し\")");
            MessageBox.Show("Jsからの戻り値>" + str1);
        }

        /// <summary>サイズ変更時のイベントでWebView2のサイズをフォームに合わせる</summary>
        private void Form1_SizeChanged(object sender, EventArgs e)
        {
            WebView.Size = this.Size;
        }
    }

    //↓属性設定が無いとエラーになります
    /// <summary>WebView2に読み込ませるためのJsで実行する関数を保持させたクラス</summary>
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    public class JsToCs
    {
        public void MessageShow(string strText)
        {
            MessageBox.Show("Jsからの呼び出し>" + strText);
        }
    }
}
sample.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
    <script language="javascript" type="text/javascript">
        //C#から呼び出すための関数
        function func1(str1) {
            alert("C# called>" + str1);
            return "success"
        }
        function ButtonClick() {
            //C#の関数の実行
            chrome.webview.hostObjects.class.MessageShow("Js send text");
        }
    </script>
</head>
<body>
    <h1>wasm_sample</h1>
    <input type="button" value='send Message' onclick="ButtonClick();"/>
    <script></script>
</body>
</html>

解説

C#->JavaScript
C#からJavaScript内の関数を呼び出すには、ExecuteScriptAsync()を使います。この関数は、先日書いた記事でExecuteScriptAsync("alert(\"message\")");的な使い方をしたのですが、引数の中身がJavaScriptとして処理できるのであれば、JavaScriptのコード内の独自で作成した関数でも実行できます。
注意点として、ExecuteScriptAsync()は Task なので、戻り値を取得する場合は.Resultawaitを使用することになるのですが、.Resultを使用するとフリーズして処理が進まなくなるため、戻り値を取得するならawaitを使用する必要があります。
サンプルコードでは、下記の部分が該当の箇所です。

Form1.cs
        /// <summary>Jsのメソッドを実行</summary>
        private async void CsToJs()
        {
            //WebView.ExecuteScriptAsync("func1()").ResultをするとWebView2がフリーズする
            string str1 = await WebView.ExecuteScriptAsync("func1(\"C#からの呼び出し\")");
            MessageBox.Show("Jsからの戻り値>" + str1);
        }
sample.html
    <script language="javascript" type="text/javascript">
        //C#から呼び出すための関数
        function func1(str1) {
            alert("C# called>" + str1);
            return "success"
        }
    </script>

挙動としては下記画像の様になります。
・ロード直後にJavaScriptのアラート出力
image.png
・アラートを閉じると、メッセージボックス出力
image.png
JavaScript->C#
JavaScriptからC#の呼び出しには、AddHostObjectToScript()を使ってC#内で作成した関数をJavaScriptに読み込ませます。
WebMessageReceivedを使う方法もあるらしいので、気になる方は調べてみてください)
サンプルコードでは、下記の部分が該当の箇所です。

Form1.cs
        /// <summary>JavaScriptで呼ぶ関数を保持するオブジェクト</summary>
        JsToCs CsClass = new JsToCs();
        //JavaScriptからC#のメソッドが実行できる様に仕込む
        WebView.CoreWebView2.AddHostObjectToScript("class", CsClass);

//~~~一部省略~~~
    /// <summary>WebView2に読み込ませるためのJsで実行する関数を保持させたクラス</summary>
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    public class JsToCs
    {
        public void MessageShow(string strText)
        {
            MessageBox.Show("Jsからの呼び出し>" + strText);
        }
    }
sample.html
    <script language="javascript" type="text/javascript">
        function ButtonClick() {
            //C#の関数の実行
            chrome.webview.hostObjects.class.MessageShow("Js send text");
        }
    </script>

<body>
    <input type="button" value='send Message' onclick="ButtonClick();"/>
</body>

挙動としては、ブラウザー内の「send Message」を押下してもらうと、C#のメッセージが出力される様になっています。
image.png

まとめ

今回はこんなところです。
WebView2に関しての記事は昨日書いたのですが、JavaScriptからC#を実行する処理に関しては直近一週間くらい、ずっと放置だったので、その問題が解消して良かったです。

今日は特に書くことないですね(別に無くても良いんですが(笑))
昨日書いた記事のリンクを一応貼り付けときます。
https://qiita.com/NagaJun/items/4925a63ce7b93b80639e
最後まで読んで頂き、ありがとうございました。

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

Vue.jsでObnizを操作する基本-単純Lチカ(メモ)

Vue.jsからのObniz操作でエラーになったのでメモ。Webアプリでボタン押下でLEDを点灯。

Consoleエラー

image.png

原因

var obniz = new Obniz('1234-5678');
obniz.onconnect = async function() {
  // **** 実行する処理 ****
}
  if (obniz.connectionState === "connected") {
    // **** 実行する処理 ****
  } else {
    obniz.on('connect', () => {
      // **** 実行する処理 ****
  })

修正後のコード

.html
<!DOCTYPE html>
<html lang="jp" >
<head>
  <meta charset="UTF-8">
  <title>Obniz Test</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <!-- 全体をVue.js有効にする -->
  <div id="app">
    <!-- タイトル -->
    <h1>Obniz Test</h1>

    <!-- 設定 -->
    <h5>Obniz ID</h5>
    <input v-model:value="ObnizID[0]" type="text" maxlength="4">
    <label>-</label>
    <input v-model:value="ObnizID[1]" type="text" maxlength="4">
    <!-- LED-ON-OFF -->
    <button v-on:click="PowerON">LED-ON</button>
    <button v-on:click="PowerOFF">LED-OFF</button>
  </div>

  <!-- CDN -->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js'></script>
  <script src='https://unpkg.com/obniz@3.9.0/obniz.js'></script>
  <!-- 実行script -->
  <script  src="./script.js"></script>

  </body>
</html>
script.js
let obniz;  // Obniz関数

// Obniz呼び出し関数
const connect = function(func, ob){
  console.log(ob.connectionState);
  // Obnizへの接続を確認
  if (ob.connectionState === "connected") {
    func();
  } else {
    ob.on('connect', () => {
      func();
    })
  }
}

const app = new Vue({
  el: '#app', // Vueが管理する一番外側のDOM要素
  data: {
    // Vue内部で利用する変数定義
    ObnizID: ['0000', '0000'],
  },
  methods: {
    // 関数はココに記述
    PowerON: function() {
      // LED ON
      // Obniz ID 指定
      let obnizid = `${this.ObnizID[0]}-${this.ObnizID[1]}`;
      this.obniz = new Obniz(obnizid);
      console.log(obnizid);

      let me = this; // thisを関数内で使えないので変数に代入
      // connect関数を呼んで、connect関数内で以下のFunctionを実行
      connect(async function() {
        const led = me.obniz.wired('LED', { anode: 0, cathode: 1 });
        me.obniz.display.clear();
        me.obniz.display.print('ON');
        led.on(); // LED点灯
      }, this.obniz);
    },

    PowerOFF: function() {
      // LED OFF
      // Obniz ID 指定
      let obnizid = `${this.ObnizID[0]}-${this.ObnizID[1]}`;
      this.obniz = new Obniz(obnizid);
      console.log(obnizid);

      let me = this; // thisを関数内で使えないので変数に代入
      // connect関数を呼んで、connect関数内で以下のFunctionを実行
      connect(async function() {
        const led = me.obniz.wired('LED', { anode: 0, cathode: 1 });
        me.obniz.display.clear();
        me.obniz.display.print('OFF');
        led.off();
      }, this.obniz);
    },
  },
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Apps Scriptで画像データからビットマップを作成する

Google Apps Script(GAS)でPNGファイル自動生成したいと思ったところ、当然ながら<canvas>が無いので、Javascriptのようには行かないことがわかりました。
しかし、ビットマップのBlobを作ればBlob.getAs(mineTipe)メソッドでPNGに変換することは出来ます。

というわけで、画像データからビットマップのBlobを生成する関数を作りました。

ソースコード

24bitフルカラー固定です。

/*
  dataは左上から右下に向かってのピクセルの色情報を持つUint8Array。色はRGBAの4バイト。
  width, heightは横縦のピクセル数。
*/
function toBMP( data, width, height ) {

  const RGBA_BYTES = 4;
  const BMP_BYTES_PER_DOT = 4;
  const BYTES_PER_DOT = 4;

  if ( data == null || width == null || height == null ) {
    throw new Error("引数が不足しています。");
  }
  data = Uint8ClampedArray.from( data.map( v => ( v >>> 0 ) & 0xff) );

  if ( data.length != ( height * width * RGBA_BYTES ) ) {
    throw new Error("データの長さが画像サイズと一致しません。");
  }

  const rowCount = height;
  const rowByteCount = width * RGBA_BYTES;
  const paddingCount = ( BMP_BYTES_PER_DOT - ( rowByteCount % BMP_BYTES_PER_DOT ) ) % BMP_BYTES_PER_DOT;
  const imageDataSize = paddingCount == 0 ? data.length : ( rowByteCount + paddingCount ) * rowCount;
  const imageData = new Uint8ClampedArray( imageDataSize );
  const paletteData = new Uint8ClampedArray( 0 ); //フルカラーなら要らない
  for ( let rowIndex = rowCount -1, j = 0; rowIndex >= 0; rowIndex-- ) {
    const offset = rowIndex * rowByteCount;
    for ( let i = offset, end = rowByteCount + offset; i < end; ) {
      const [ red, green, blue, alpha ] = data.slice( i, i += BYTES_PER_DOT );
      for (const ori of [ blue, green, red ]) {
        const color = (ori * alpha / 0xff) | 0;
        imageData[ j++ ] = color;
      }
    }
    for ( let i = 0; i < paddingCount; i++ ) {
      imageData[ j++ ] = 0;
    }
  }

  const paletteDataSize = 0;
  const informationHeaderSize = 40;
  const fileHeaderSize = 14;
  const imageDataOffset = fileHeaderSize + informationHeaderSize + paletteDataSize;
  const fileSize = fileHeaderSize + informationHeaderSize + paletteDataSize + imageDataSize;

  const bmpData = new Uint8Array( fileSize );
  let cursor = 0;

  // Bitmap File Header //
  for ( const bfType of "BM".split("").map( ch => ch.charCodeAt( 0 ) ) ) {
    bmpData[ cursor++ ] = bfType;
  }
  for ( const bfSize of UnsignedLong( fileSize ) ) {
    bmpData[ cursor++ ] = bfSize;
  }
  for ( const bfReserved1 of UnsignedInt( 0 ) ) {
    bmpData[ cursor++ ] = bfReserved1;
  }
  for ( const bfReserved2 of UnsignedInt( 0 ) ) {
    bmpData[ cursor++ ] = bfReserved2;
  }
  for ( const bfOffBits of UnsignedLong( imageDataOffset ) ) {
    bmpData[ cursor++ ] = bfOffBits;
  }

  // Bitmap Information Header //
  for ( const biSize of UnsignedLong( informationHeaderSize ) ) {
    bmpData[ cursor++ ] = biSize;
  }
  for ( const biWidth of Long( width ) ) {
    bmpData[ cursor++ ] = biWidth;
  }
  for ( const biHeight of Long( height ) ) {
    bmpData[ cursor++ ] = biHeight;
  }
  for ( const biPlanes of UnsignedInt( 1 ) ) {
    bmpData[ cursor++ ] = biPlanes;
  }
  for ( const biBitCount of UnsignedInt( 24 ) ) {
    bmpData[ cursor++ ] = biBitCount;
  }
  for ( const biCompression of UnsignedLong( 0 ) ) {
    bmpData[ cursor++ ] = biCompression;
  }
  for ( const biSizeImage of UnsignedLong( imageDataSize ) ) {
    bmpData[ cursor++ ] = biSizeImage;
  }
  for ( const biXPixPerMeter of Long( width ) ) {
    bmpData[ cursor++ ] = biXPixPerMeter;
  }
  for ( const biYPixPerMeter of Long( height ) ) {
    bmpData[ cursor++ ] = biYPixPerMeter;
  }
  for ( const biClrUsed of UnsignedLong( 0 ) ) {
    bmpData[ cursor++ ] = biClrUsed;
  }
  for ( const biCirImportant of UnsignedLong( 0 ) ) {
    bmpData[ cursor++ ] = biCirImportant;
  }

  // Palette Data //
  for ( const palette of paletteData ) {
    bmpData[ cursor++ ] = palette;
  }

  // Image Data //
  for ( const image of imageData ) {
    bmpData[ cursor++ ] = image;
  }

  const blob = Utilities.newBlob( bmpData, MimeType.BMP );

//Javascriptでは下記の方法でBlobが作れる。
//  const blob = new Blob( [ bmpData.buffer ], { type: "image/bmp" } );

  return blob;

}

最後はスプレッド構文にしたら関数呼び過ぎで落ちてしまったので、for文たくさんに置き換えましたけど、もうちょっと上手い書き方がありそうですね。

参考

Bitmapファイルフォーマット
http://www.umekkii.jp/data/computer/file_format/bitmap.cgi

BMP ファイルフォーマット
https://www.setsuki.com/hsp/ext/bmp.htm

【バイナリファイル入門】Bitmapファイルを手書きで作って遊んでみる

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

【JavaScript】基本的なモジュール(ESM)の使い方についてまとめ

モジュール(ESM)とは

・ソースコードを整理、分割する仕組み
・ブラウザ上で動作
・ES6から導入
・import / exportを使用
・IEでは未対応

これを使用することにより、別ファイルで定義した変数や関数を扱うことができる

import

モジュールを読み込む

export

モジュールを出力

主な用途

例としてmoduleA.jsmoduleB.jsのファイルを作成。
moduleA.jsexportしたモジュールをmoduleB.jsimportします。

フォルダ構成
|--index.html
|--moduleA.js
|--moduleB.js

htmlファイルの準備

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>Document</title>
  </head>
  <body><!-- ↓typeを"module"に。   ↓moduleB.jsを読み込む。 -->
    <script type="module" src="moduleB.js"></script>
  </body>
</html>


moduleA.jsの準備
適当な変数と関数を定義

moduleA.js
const hoge = 'ほげ';

const fuga = () => {
  console.log('ふが');
}

現状もちろんこれらの変数、関数はmoduleA.js内でしか使用できません。
これをexportを使いmoduleB.js内で使用できる様に準備します。

moduleA.js
export const hoge = 'ほげ';

export const fuga = () => {
  console.log('ふが');
}


moduleB.jsの準備

moduleA.jsで定義したhogefugaimportしています。

moduleB.js
import { hoge, fuga } from './moduleA.js'

console.log(hoge); // => ほげ
fuga()             // => ふが

できました!


ちなみにこうすることでimportした変数関数の名前を変更できます。

moduleB.js
import { hoge as h, fuga as f} from './moduleA.js'

console.log(h); // => ほげ
f()             // => ふが


オブジェクトとして格納することも可能。

moduleB.js
import *as moduleA from './moduleA.js'

console.log(moduleA.hoge); // => ほげ;
moduleA.fuga();            // => ふが;

デフォルトエクスポート

さっきまでのは名前付きエクスポートという機能でした。
デフォルトエクスポートもやってみます。

moduleA.js及びmoduleB.jsの記述を変更します。

moduleA.js
export default 'ほげ';

moduleB.js
import hoge from './moduleA.js'

console.log(hoge); // => ほげ

デフォルトエクスポートはモジュールごとにひとつしか作れません。

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

Javascriptの配列操作

Javascriptの配列まわりの関数や構文を抜粋してみました。

スプレッド構文

配列(正確にはIterableな値全般)を展開します。
単に展開したいときはもちろん、配列のコピーを作れるため配列を直接変更するメソッドも配列を変更せずに利用できます。
配列を結合するArray.prototype.concatの代わりにもなります。

const newarray = [value1, ...array, value2];
const copyarray = [...array];

Array

コンストラクタです。特定の長さの配列が作れます。
スプレッド構文かArray.prototype.fillなどで初期化しないと、Array.prototype.mapが動かないので注意が必要です。

const newarray = Array(8);

Array.prototype.fill

全要素を指定した値で埋めます。直接変更されます。
開始位置と終了位置は省略可能で、省略して場合は先頭から末尾までです。

const newarray = [...array].fill( value, start, end );

Array.prototype.reverse

配列の順序を反転させます。直接変更されます。

const reversed = [...array].reverse();

Array.prototype.find / Array.prototype.findIndex

条件を満たす要素、あるいはその要素のindexを返します。
どちらも先頭から走査して、最初に条件を満たした要素について処理します。

const value = array.find( (element, index, array) => boolean );
const index = array.findIndex( (element, index, array) => boolean );

Array.prototype.filter

findに似ています。条件を満たす全ての要素からなる新しい配列を返します。

const newarray = array.filter( (element, index, array) => boolean );

Array.prototype.join

String.prototype.splitと対になるようなメソッドです。
全要素を連結した1つの文字列にします。
区切り文字が指定でき、省略した場合は「,」(カンマ)になります。
空文字を指定することで区切り文字無しで連結することができます。

const text = array.join(separator);

String.prototype.split

Array.prototype.joinと対になるようなメソッドです。
区切り文字を省略した場合、または文字列中に区切り文字が見つからなかった場合は分割せずに1要素の配列を返します。
空文字を指定した場合は1文字ずつに分割されますが、サロゲートペアも分割してしまうので、絵文字などが含まれる場合は注意が必要です。
また、分割限界数を指定することができます。省略した場合は無制限です。

const array = string.split(separator, limit);

Array.prototype.map

全要素を変換して新しい配列を返します。

const newarray = array.map( (element, index, array) => value );

Array.prototype.reduce

全要素を変換して新しいを返します
他のメソッドの実質的な上位互換で、なんでもできます。

const newvalue = array.reduce( (accumulator, element, index, array) => newaccumulator, initialvalue );

おわりに

今回の投稿はreduce()の用途がわからなかったので、他のメソッドを調べて比較したのがきっかけです。
自分がよく使うものを中心にまとめたので、網羅はできてないかもしれませんが、みなさんの助けになれば幸いです。

参考

Array - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array

困ったらね、MDN見たらいいんですよ。:sunglasses:

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

【入門者向け】Canvas入門講座#10 円をボタンを押して動かそう【JavaScript】

問題10

中心が(150, 150)、半径50の円を塗りつぶしなさい。
塗りつぶす色はマゼンタ色(#ff00ff)であること。
rightボタン押下時に円がX方向へ-30,leftボタン押下時に円がX方向へ+30移動すること。
なお、以下のHTMLを使うこと。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題10</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {    
    // ここにプログラムを書く
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="left-button" type="button" value="right" />
<input id="right-button" type="button" value="left" />
</body>
</html>

答え

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題10</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    let moveX = 0;

    // 初期表示
    drawCircle(moveX);

    $('#left-button, #right-button').on('click', e => {
        if($(e.target).prop('id') === 'left-button') {// left-button がクリックされた
            moveX -= 30;
        } else {// right-button がクリックされた
            moveX += 30;
        }
        drawCircle(moveX);
    });

    function drawCircle(moveX) {
        // コンテキストを取得
        const ctx = $('#my-canvas')[0].getContext('2d');

        // canvasをクリア
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

        // 塗りつぶしの色をマゼンタ色にする
        ctx.fillStyle = '#ff00ff';

        ctx.beginPath();        // 現在のパスをリセットする
        ctx.arc(150 + moveX, 150, 50, 0, Math.PI*2, true); // 円を描画する
        ctx.closePath();        // パスを閉じる
        ctx.fill();           // 現在のパスを塗りつぶす
    }
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="left-button" type="button" value="left" />
<input id="right-button" type="button" value="right" />
</body>
</html>

円が移動しました!
image.png

解説

円がどれだけ動いたか変数で保持します。

let moveX = 0;

まず円を描画します。今回は円の描画を関数にしてあります。

// 初期表示
drawCircle(moveX);

ボタンクリックをまとめてハンドリングします。
left-button がクリックされたら、moveXを-30します。
right-button がクリックされたら、moveXを+30します。

$('#left-button, #right-button').on('click', e => {
    if($(e.target).prop('id') === 'left-button') {// left-button がクリックされた
        moveX -= 30;
    } else {// right-button がクリックされた
        moveX += 30;
    }
    drawCircle(moveX);
});

drawCircleは円を描画する関数です。
引数はmoveXでX方向にどれだけ移動したかを受け取ります。

canvasへ描画する前にclearRectメソッドでcanvasをクリアしています。
これはクリアしないと1つ前の円の描画そのまま残るのを防ぐためです。
clearRectの代わりにfillRectによる塗りつぶしでも構いません。

一般的にcanvasに何かを描画するときは、canvasをクリアした後に、canvasへ描画します。

function drawCircle(moveX) {
    // コンテキストを取得
    const ctx = $('#my-canvas')[0].getContext('2d');

    // canvasをクリア
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    // 塗りつぶしの色をマゼンタ色にする
    ctx.fillStyle = '#ff00ff';

    ctx.beginPath();        // 現在のパスをリセットする
    ctx.arc(150 + moveX, 150, 50, 0, Math.PI*2, true); // 円を描画する
    ctx.closePath();        // パスを閉じる
    ctx.fill();           // 現在のパスを塗りつぶす
}

補足

setTransformを使うと変数を描画処理で参照しなくてよいのですっきりします。
今回の問題は描画が単純ですので、このような工夫は必要ありませんが
setTransformは知っててほしいメソッドです。

setTransformは行列を掛けるメソッドです。
平行移動する場合はctx.setTranform(1, 0, 0, 0, x, y);のように呼び出しましょう。

今回のCanvas入門講座ではオブジェクトの変換は平行移動しかしないので、
setTransformの代わりにtranslateでもよいです。

setTranformについて詳しく知りたい方はこちらの記事をお読みください。

以下、drawCircleをsetTransformを使って書き直しました。

function drawCircle(moveX) {
    // コンテキストを取得
    const ctx = $('#my-canvas')[0].getContext('2d');

    // canvasをクリア
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    // 塗りつぶしの色をマゼンタ色にする
    ctx.fillStyle = '#ff00ff';

    ctx.setTransform(1, 0, 0, 1, moveX, 0);

    ctx.beginPath();        // 現在のパスをリセットする
    ctx.arc(150, 150, 50, 0, Math.PI*2, true); // 円を描画する
    ctx.closePath();        // パスを閉じる
    ctx.fill();           // 現在のパスを塗りつぶす
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【入門者向け】Canvas入門講座#9 画像を描画しよう【JavaScript】

問題9

画像が読み込まれたときに、その画像をアスペクト比を保ったまま
画像全体が見えるようにcanvasに貼り付けなさい。
画像の左上隅はcanvasの左上隅に一致させること。
画像が貼り付けられていない箇所は黒色(#000000)で塗りつぶすこと。
読み込んだファイルのチェックは行わないものとする。
以下のHTMLを使うこと。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題9</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {
            // ここにプログラムを書く
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="my-file" type="file" />
</body>
</html>

補足

canvasは幅が500,高さが300で固定です。
画像は幅、高さがいくつのものが来るかわかりません。
canvasが画像より横長、縦長の場合を考慮する必要があります。

答え

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題9</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {
            // コンテキストを取得
            const ctx = $('#my-canvas')[0].getContext('2d');

            // canvasを黒色で塗りつぶす
            ctx.fillStyle = '#000000';
            ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);            

            // canvasと画像のアスペクト比を求める
            const canvasWidth = $('#my-canvas').prop('width'),
                canvasHeight = $('#my-canvas').prop('height');
                canvasAspect = canvasWidth / canvasHeight;  // canvasのアスペクト比
                imgAspect = img.width / img.height; // imgのアスペクト比

            // canvasと画像のアスペクト比を比較し、貼り付ける領域を決定する
            let dstWidth, dstHeight;
            if(canvasAspect > imgAspect) {// canvasの方が横長
                dstHeight = canvasHeight;
                dstWidth = dstHeight * imgAspect;               
            } else {// canvasの方が縦長
                dstWidth = canvasWidth;
                dstHeight = dstWidth / imgAspect;
            }

            // 画像を貼り付ける
            ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dstWidth, dstHeight);
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="my-file" type="file" />
</body>
</html>

画像が縦長の場合、横長の場合でもうまく表示できました!

canvasが画像より横長の場合
image.png

canvasが画像より縦長の場合
image.png

解説

まずはファイルが読み込まれたイベントをハンドリングします。
changeイベントをハンドリングします。

$('#my-file').on('change', e => {
    // ここにファイルが読み込まれた処理を書く
});

次に画像をロードする必要があります。
Imageを作成し、srcに画像のurlを指定します。
読み込みが完了したら、loadイベントが発生するので、それをハンドリングします。

const img = new Image();
$(img).on('load', e => {
     // ここに画像読み込み完了後の処理を書く
});
img.src = URL.createObjectURL(e.target.files[0]);

さあ、画像もロードできたのでcanvasに描画しましょう。
いつもどおり、コンテキストを取得します。

// コンテキストを取得
const ctx = $('#my-canvas')[0].getContext('2d');

そして、canvasを黒色で塗りつぶしましょう。

// canvasを黒色で塗りつぶす
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);

canvasと画像のアスペクト比を求めましょう。

// canvasと画像のアスペクト比を求める
const canvasWidth = $('#my-canvas').prop('width'),
    canvasHeight = $('#my-canvas').prop('height');
    canvasAspect = canvasWidth / canvasHeight;  // canvasのアスペクト比
    imgAspect = img.width / img.height; // imgのアスペクト比

canvasに貼り付ける幅と高さを計算しましょう。
canvasが縦長か横長かで場合分けすればうまくいきそうです。

canvasが画像より横長の場合、
貼り付ける高さはcanvasの高さを採用します。
貼り付ける幅は貼り付ける高さに画像のアスペクト比を掛けることで求まります。

canvasが画像より縦長の場合、
貼り付ける幅はcanvasの幅を採用します。
貼り付ける高さは貼り付ける幅を画像のアスペクト比で割ることで求まります。

// canvasと画像のアスペクト比を比較し、貼り付ける領域を決定する
let dstWidth, dstHeight;
if(canvasAspect > imgAspect) {// canvasの方が横長
    dstHeight = canvasHeight;
    dstWidth = dstHeight * imgAspect;               
} else {// canvasの方が縦長
    dstWidth = canvasWidth;
    dstHeight = dstWidth / imgAspect;
}

あとはcanvasに画像を貼り付けましょう。
drawImageメソッドで貼り付けます。このメソッドは超超重要です。
ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
image: 画像オブジェクト or canvasオブジェクト or 動画オブジェクト
sx, sy, sw, sh: 貼り付け元の左上隅座標とサイズ
dx, dy, dw, dh: 貼り付け先の左上隅座標とサイズ

今回画像全体を表示するので
sx, sy, sw, shは0, 0, img.width, img.heightです。
画像の左上隅はcanvasの左上隅に一致するので
dx, dy, dw, dhは0, 0, dstWidth, dstHeightです。

// 画像を貼り付ける
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dstWidth, dstHeight);

最後に

今回の問題はややこしく感じたかもしれませんが、この程度の計算はできるようにしておきましょう。
canvasを扱う時に計算はつきものです。
余裕のあった人は、canvasの中央に画像を貼り付けてみましょう。
これについては答えは示しませんが、以前記事を書いたことがあるのでそれが参考になるかもしれません。

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

three.jsでcanvasを使ってテキストを表示させる方法

three.jsを使ってテキストを表示させる方法はいくつかあると思います。

  1. TextGeometryを使う
  2. Bitmap Fontsを使う
  3. canvasにテキストを描画してテクスチャとして使う

大きくこの3つの方法があるのかと思いますが今回はcanvasにテキストを描画してテクスチャとして使う方法を紹介していきます。

qiita_cap.gif
デモはこちら
ソースコードはこちら
(本編の記事と内容が若干異なります)

テキストを表示させる大まかな流れ

  • three.jsを描画するcanvasとは別にテキストを描画するcanvasを用意
  • canvasにテキストを描画しテクスチャを生成
  • テキストを描画したいメッシュを作成
  • 作成したメッシュにテクスチャを送る

ざっとこんな流れ。あと、テクスチャに格納することをよくテクスチャに焼くなんて言います。

まずは下準備。今回は平面を描画するだけなのでパースのかからないOrthographicCameraを使います。

export default class Canvas {
  constructor() {
    this.container = document.getElementById('CanvasContainer');
    this.setConfig();

    // レンダラを作成
    this.renderer = new THREE.WebGLRenderer({
      alpha: true,
      antialias: false,
    });
    this.renderer.setSize(this.Config.width, this.Config.height);
    this.renderer.setPixelRatio(this.Config.dpr);
    this.container.appendChild(this.renderer.domElement);

    // 関数をthisでバインドして持っておく
    this.resizeFunction = this.resize.bind(this);
    this.updateFunction = this.update.bind(this);

    // リサイズイベントを設定
    window.addEventListener('resize', this.resizeFunction);

    this.scene = new THREE.Scene();
    this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, -1);

    // レンダリング開始
    // 初期化
    this.init();
  }

  init() {
    this.createMesh();
    this.start();
  }

  createMesh() {
   // 後述
  }

  setConfig() {
    this.Config = {
      width: window.innerWidth, // Canvasの幅
      height: window.innerHeight, // Canvasの高さ
      cameraZ: 1000, // カメラのz座標
      dpr: 1, // device pixel ratio
      aspectRatio: 1.0, // 画面アスペクト比
    };
    // 親要素のサイズを取得
    const domRect = this.container.getBoundingClientRect();
    const width = domRect.width;
    const height = domRect.height;

    this.Config.dpr = Math.min(window.devicePixelRatio, 2);
    this.Config.width = width;
    this.Config.height = height;
    this.Config.halfWidth = this.Config.width / 2;
    this.Config.halfHeight = this.Config.height / 2;
    this.Config.aspectRatio = this.Config.width / this.Config.height;
  }

  resizeScene() {
    this.renderer.setSize(this.Config.width, this.Config.height);
  }

  start() {
    this.resize();
    this.update();
  }

  resize() {
    this.setConfig();
    this.resizeScene();
  }

  update() {
    // 最大60fpsでレンダリングをループ
    requestAnimationFrame(this.updateFunction);
    this.time = performance.now() * 0.001;

    this.material.uniforms.time.value = this.time;

    this.renderer.render(this.scene, this.camera);
  }
}

canavasにテキストを描画してテクスチャを作る

canvasを使ってテクスチャを作るにはCanvasTextureを使います。今回はmeasureTextを使って文字数に応じてcanvasの長さを可変にします。

テクスチャを作る.js
  /**
   * 2D Canvasからテクスチャを作成する
   * @param {Object} options
   * @param {stirng} options.text 描画したい文字列
   * @param {number} options.fontSize フォントサイズ
   * @return {Object} テクスチャを返す。
   * @memberof Canvas
   */
  createTexture(options) {
    // Canvas要素を作成
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // measureTextするためいったん設定
    const fontFamily = 'monospace';
    ctx.font = `bold ${options.fontSize * Config.dpr}px '${fontFamily}'`;
    const textWidth = ctx.measureText(options.text); // 文字の横幅を取得

    // dprに対応したサイズを計算
    const width = textWidth.width;
    const height = options.fontSize * Config.dpr * 0.8; // 文字に合わせて高さを調整。ここの高さは任意で
    // 幅を指定
    canvas.width = width;
    canvas.height = height;

    // 中央にテキストを描画
    ctx.font = `bold ${options.fontSize * Config.dpr}px '${fontFamily}'`;
    ctx.textAlign = 'left';
    ctx.textBaseline = 'hanging';
    ctx.fillStyle = 'rgba(255, 255, 255, 1.0)';
    ctx.fillText(options.text, -5, 0); // 文字が途切れないように調整。数値はよしなに

    // ↓canvasの文字を確認したいとき。テキストを描画したcanvasをbodyに追加しているだけです。
    // document.body.appendChild(canvas);
    // canvas.style.backgroundColor = '#933';
    // canvas.style.position = 'relative';

    // テクスチャを作成
    const texture = new THREE.CanvasTexture(canvas);
    texture.needsUpdate = false;
    // ↓ここら辺の設定をしておかないとthree.jsでエラーが出る時がある
    texture.minFilter = THREE.LinearFilter;
    texture.magFilter = THREE.LinearFilter;
    texture.format = THREE.RGBAFormat;

    return texture;
  }

テキストとcanvasのサイズについて

テキストぴったりのcanvasを作るのがなかなかうまくいかず、微調整をしています。フォントやテキストよってctx.fillText(options.text, -5, 0);ここら辺の調整が必要になってきます。

    // ↓canvasの文字を確認したいとき。テキストを描画したcanvasをbodyに追加しているだけです。
    // document.body.appendChild(canvas);
    // canvas.style.backgroundColor = '#933';
    // canvas.style.position = 'relative';

こうやってテキストを描画したcanvasを表示させてcanvasを調整しているのですがもっとうまい方法ないのかなー・・・

メッシュを作る

メッシュを作ります。今回はただテキストを表示させるだけなので、ジオメトリにはPlaneBufferGeometryを、マテリアルにはRawShaderMaterialを使用します。

three.jsのShaderMaterialについての補足

three.jsのShaderMaterialには

  • ShaderMaterial
  • RawShaderMaterial

のふたつがあります。

ShaderMaterialではthree.jsビルトインのattributesuniformsを自動で付加してくれます。自動で付加されたくない場合はRawShaderMaterialを使います。

ビルトインattributesuniformsはこちら。

builtInVertexShader.glsl
// = object.matrixWorld
uniform mat4 modelMatrix;

// = camera.matrixWorldInverse * object.matrixWorld
uniform mat4 modelViewMatrix;

// = camera.projectionMatrix
uniform mat4 projectionMatrix;

// = camera.matrixWorldInverse
uniform mat4 viewMatrix;

// = inverse transpose of modelViewMatrix
uniform mat3 normalMatrix;

// = camera position in world space
uniform vec3 cameraPosition;

///////////////////////////////////////

// default vertex attributes provided by Geometry and BufferGeometry
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

詳しくは↓に書いてあります。
ShaderMaterial

あらためてこちらがメッシュを作る部分。

メッシュを作る.js
  createMesh() {
    const segment = 1;

    this.geometry = new PlaneBufferGeometry(2, 2, segment, segment);

    // テクスチャの作成 前述の「テクスチャを作る.js」を参照
    this.texture = this.createTexture({
      text: 'HOGE', // 描画したいテキスト
      fontSize: 130, // フォントサイズ
    });

    // マテリアルの作成
    this.material = new RawShaderMaterial({
      uniforms: {
        texture: { value: this.texture },
        time: { value: 0.0 },
        speed: { value: 1.0 },
        resolution: { value: Config.aspectRatio },
      },
      vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      transparent: false,
    });

    this.mesh = new Mesh(this.geometry, this.material);
    this.scene.add(this.mesh);
  }

シェーダー

シェーダーを見ていきます。今回はfract関数を使いテキストをタイル状に表示させ左方向に流します。

frag.glsl
precision mediump float;

uniform sampler2D texture;
uniform float time;
uniform float resolution;
uniform float speed;

varying vec2 vUv;

void main() {
  float t = time * speed;

  vec2 repeat = vec2(8.0, 8.0); // (行, 列)回繰り返し
  vec2 uv = fract(vUv * repeat + vec2(t, 0.0));

  vec3 color = texture2D(texture, uv).rgb;
  gl_FragColor = vec4(color, 1.0);
}

vertexShaderは特に変わったことはしていません。

vert.glsl
precision mediump float;

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;

void main() {
  vUv = uv;
  vec3 pos = position;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}

あとは実行するだけです。

main.js
import Canvas from './Canvas/_index';

document.addEventListener('DOMContentLoaded', () => {
  new Canvas();
});

まとめ

というわけでcanvasを使ってテキストを描画する方法を紹介しました。割と簡単な方法なのですが、スマホだったり高解像度ディスプレイで見るとテキストがかなりギザギザしてしまいます。もっと綺麗に描画したい!となったらBitmap Fontaを使ったほうがいいかもしれません。

↓再掲
デモはこちら
ソースコードはこちら
(本編の記事と内容が若干異なります)


参考

techniques-for-rendering-text-in-threejs
Creating text
CanvasTexture

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

select2 is not a functionの解決策

原因

headタグ内でselect2を読み込んでた。

解決策

bodyタグを閉じる直前で、読み込むようにしました。解決。

<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
<!-- select2 -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/js/select2.min.js"></script>
</body>

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

【入門者向け】Canvas入門講座#8 円を塗りつぶそう【JavaScript】

問題8

中心が(200, 200)、半径100の円を塗りつぶしなさい。
塗りつぶす色はマゼンタ色(#ff00ff)であること。
なお、以下のHTMLを使うこと。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題8</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {
    // ここにプログラムを書く
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
</body>
</html>

答え

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題8</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {
    // コンテキストを取得
    const ctx = $('#my-canvas')[0].getContext('2d');

    // 塗りつぶしの色をマゼンタ色にする
    ctx.fillStyle = '#ff00ff';

    ctx.beginPath();        // 現在のパスをリセットする
    ctx.arc(200, 200, 100, 0, Math.PI*2, true); // 円を描画する
    ctx.closePath();        // パスを閉じる
    ctx.fill();           // 現在のパスを塗りつぶす
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
</body>
</html>

円が塗りつぶせました!
image.png

解説

コンテキストを取得します。これは問題1と同じです。

// コンテキストを取得
const ctx = $('#my-canvas')[0].getContext('2d');

次に塗りつぶしの色を指定します。
塗りつぶしの色はfillStyleで指定します。

// 塗りつぶしの色をマゼンタ色にする
ctx.fillStyle = '#ff00ff';

後はパスを作成し、最後にfillを呼びます。
円弧の描画arcメソッドについては問題7の解説をご覧ください。

ctx.beginPath();        // 現在のパスをリセットする
ctx.arc(200, 200, 100, 0, Math.PI*2, true); // 円を描画する
ctx.closePath();        // パスを閉じる
ctx.fill();           // 現在のパスを塗りつぶす
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【入門者向け】Canvas入門講座#7 円を描こう【JavaScript】

問題7

中心が(200, 200)、半径60の円を描画しなさい。
線分は青色(#0000ff)で太さが3であること。
なお、以下のHTMLを使うこと。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題7</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {
    // ここにプログラムを書く
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
</body>
</html>

答え

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題7</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {
    // コンテキストを取得
    const ctx = $('#my-canvas')[0].getContext('2d');

    // 線の色を青色にする
    ctx.strokeStyle = '#0000ff';

    // 線の太さを3にする
    ctx.lineWidth = 3;

    ctx.beginPath();        // 現在のパスをリセットする
    ctx.arc(200, 200, 60, 0, Math.PI*2, true);  // 円を描画する
    ctx.closePath();        // パスを閉じる
    ctx.stroke();           // 現在のパスを描画する
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
</body>
</html>

円が描画できました!
image.png

解説

コンテキストを取得します。これは問題1と同じです。

// コンテキストを取得
const ctx = $('#my-canvas')[0].getContext('2d');

次に線の色と太さを指定します。
線の色はstrokeStyleで指定します。
線の太さはlineWidthで指定します。

// 線の色を青色にする
ctx.strokeStyle = '#0000ff';

// 線の太さを3にする
ctx.lineWidth = 3;

後はパスを作成し、最後にstrokeを呼びます。
本問題では、パスの始点と終点が一致しているのでclosePathを呼んでも呼ばなくても結果は同じです。

円弧の書き方ですが、
ctx.arc( x, y, 半径, 開始角度, 終了角度, 回り方)のように指定します。
x, yは円の中心点の座標を指定します。
開始角度、終了角度はラジアンで指定します。
回り方はtrueで反時計回り、falseで時計回りであり、今回はどちらを指定しても結果は同じです。

ctx.beginPath();        // 現在のパスをリセットする
ctx.arc(200, 200, 60, 0, Math.PI*2, true);  // 円を描画する
ctx.closePath();        // パスを閉じる
ctx.stroke();           // 現在のパスを描画する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webアプリでカメラ操作したいときに見るメモ(javascript)

HTML

<video id="myvideo" width="160" height="160" muted autoplay playsinline></video>

・width:横幅
・height:縦幅
・muted:動画の音をでないように設定
・autoplay:動画を自動再生
・playsinline:インライン再生を可能にする

参考
【videoタグ】HTMLで動画を埋め込む方法を徹底まとめ

JS

async function main() {
    // デバイス(カメラ・マイク)からのデータ取得を試みる
    // 映像や音声が使えるデバイスが確定するまで時間がかかるためawaitを使う
    const stream = await navigator.mediaDevices.getUserMedia({
        audio: true,
        video: true,
    });
    // IDが"myvideo"であるDOMを取得
    const video = document.getElementById('myvideo');
    // myvideoなvideo要素のsrcObject(映像オブジェクトを入れるところ)にデータ(メディアストリーム)をセットする
    video.srcObject = stream;
}

// 実行する
main();

参考
javaScriptでWEBカメラの映像をブラウザで表示する(PC/iPhone)
video要素、audio要素をJavaScriptから操作する

フロントカメラとリアカメラを切り替える方法(スマホで使う)

フロントカメラ=内側のカメラ(インカメ)
リアカメラ=外側のカメラ

■フロントカメラ

navigator.mediaDevices.getUserMedia({
  audio: false,
  video: {
    width: 640, height: 480,
    facingMode: "user"
  }
})

■リアカメラ

navigator.mediaDevices.getUserMedia({
  audio: false,
  video: {
    width: 640, height: 480,
    facingMode: { exact: "environment" }
  }
})

参考
[HTML5] カメラのフロントとリアを切り替える

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

【JavaScript】splice()メソッド

プログラミング勉強日記

2020年11月16日
spliceメソッドを学習したので、記録していきます。

spliceメソッドとは?

配列の中身を削除したり、置き換えたり、新しく値を追加できるメソッドです。

構文

num.splice('start','deleteCount', item1, item2....);

1.start

配列を変更する開始位置を表すインデックス番号を記述します。

2.deleteCount

startから値を取り除く数を記述します。

記述しなかった場合、start以降(startの値も含めます)の値を全て削除します。

0の場合、値は削除されませんが、itemに新しく追加する値を記述する必要があります。

3.item1,item2....

startから配列に追加する値を記述します。

例文

インデックス番号3(50)から、2個(50と60)の値を削除して、新しく35を追加する。

script.js
const scores = [80,90,40,50,60,45];
scores.splice(3, 2, 35);
出力結果.
[80,90,40,35,45]

参考資料

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

GoogleMapに複数のマーカーを表示する

GoogleMapに対象店舗と周辺店舗を表示させたいと思ったため調べたことを記録します。
当記事は、主にJavascriptの記述についての記載になるためGoogleMapのAPIキー取得等に関する記述は省略します。
条件としては以下のとおりです。

・地図の中心に対象店舗が表示される。(マーカーを立てる)
・対象店舗を中心として半径5km以内にある店舗にもマーカーを表示する。
・対象店舗と周辺店舗はマーカーの色を分ける。
・周辺店舗のマーカーをクリックしたときには吹き出しが表示される。
・吹き出しには店舗名が入っており、店名をクリックすると該当店舗の詳細ページに遷移する。

Gif.gif

開発環境

ruby 2.6.5
Ruby on Rails 6.0.3.3
OS: macOS Catalina

はじめに

GoogleMapApiの使い方に関してこちらの記事がわかりやすくまとめてあり参考にさせていただきました。
Google Maps APIの使い方

実装コード

コードは以下のとおりです。

  let map;
  let mainMarker;
  let marker =[];
  let infoWindow = [];

function initMap(){
  let markerData = gon.places;
  let latlng = {lat: gon.latitude, lng: gon.longitude};
  // 初期位置の指定
  map = new google.maps.Map(document.getElementById('map'), {
  center: latlng,
  zoom: 14
  });

  // 初期位置にマーカーを立てる
  mainMarker = new google.maps.Marker({
    map: map,
    position: latlng
  });

  // 近隣店舗にマーカーを立てる
  for (var i = 0; i < markerData.length; i++) {
    const image = "https://maps.google.com/mapfiles/ms/icons/yellow-dot.png";
    const id = markerData[i]['id'];
    // 緯度経度のデータを作成
    let markerLatLng = new google.maps.LatLng({lat: markerData[i]['latitude'], lng: markerData[i]['longitude']});
    // マーカーの追加
    marker[i] = new google.maps.Marker({
      position: markerLatLng,
      map: map,
      icon: image,
    });

    // 吹き出しの追加
    infoWindow[i] = new google.maps.InfoWindow({
    // 吹き出しに店舗詳細ページへのリンクを追加
    content: `<a href=/laundries/${id}>${markerData[i]['name']}</a>`
    });

    markerEvent(i); 
  }

  // マーカークリック時に吹き出しを表示する
  function markerEvent(i) {
    marker[i].addListener('click', function() {
      infoWindow[i].open(map, marker[i]);
    });
  }
}

document.addEventListener('turbolinks:load', function(){
  initMap();
});

1つずつ見ていきます。
1.変数の定義

 let map;
 let mainMarker;
 let marker =[];
 let infoWindow = [];

対象店舗と周辺店舗のマーカーを分けたかったため2つ変数を用意しました。markerには周辺店舗の情報が複数入るため配列にしています。infoWindowも同様に、各店舗のマーカーに対して吹き出しを付けたいため配列にしています。

2.地図の作成〜マーカーを立てる

  let markerData = gon.places;
  let latlng = {lat: gon.latitude, lng: gon.longitude};
  // 初期位置の指定
  map = new google.maps.Map(document.getElementById('map'), {
  center: latlng,
  zoom: 14
  });

  // 初期位置にマーカーを立てる
  mainMarker = new google.maps.Marker({
    map: map,
    position: latlng
  });

latlngという変数を定義して店舗の緯度と経度を指定しています。
gonというのはgemの1つでrailsで定義した変数を、javascriptで使える変数に変換してくれます。
(https://github.com/gazay/gon)
new google.maps.Mapとすることで新しく地図を作成し、オプションで中心地やズームレベルを指定します。
地図の作成に関してはLet'sプログラミングやMaps JavaScriptAPIのドキュメントを参考にさせていただきました。

次に作成した地図にマーカーを立てていきます。
こちらもnew google.maps.Markerでマーカーを作成してどのmapにマーカーをたてるか、どこにマーカーを立てるかを指定することでマーカーを立てることができます。

3.周辺店舗にマーカーを立てる + 吹き出しを追加する

for (var i = 0; i < markerData.length; i++) {
    const image = "https://maps.google.com/mapfiles/ms/icons/yellow-dot.png";
    const id = markerData[i]['id'];
    // 緯度経度のデータを作成
    let markerLatLng = new google.maps.LatLng({lat: markerData[i]['latitude'], lng: markerData[i]['longitude']});
    // マーカーの追加
    marker[i] = new google.maps.Marker({
      position: markerLatLng,
      map: map,
      icon: image,
    });

    // 吹き出しの追加
    infoWindow[i] = new google.maps.InfoWindow({
    // 吹き出しに店舗詳細ページへのリンクを追加
    content: `<a href=/laundries/${id}>${markerData[i]['name']}</a>`
    });

    markerEvent(i); 
  }

  // マーカークリック時に吹き出しを表示する
  function markerEvent(i) {
    marker[i].addListener('click', function() {
      infoWindow[i].open(map, marker[i]);
    });
  }

行っていることとしてはlet markerData = gon.places;で定義した変数の値を1つずつ取り出してマーカーと吹き出しを追加しています。
そしてマーカーの色を変えたかったためimageという定数にアイコンを定義してicon: imageで定義したアイコンを使用しています。
new google.maps.LatLng({lat: markerData[i]['latitude'], lng: markerData[i]['longitude']});
とすることでそれぞれの要素の緯度経度に対してマーカーを立てるように指定します。

続いて吹き出しを追加します。
contentで吹き出しの中に入れる文字を指定できます。
今回は各店舗名を表示+クリックすることで詳細ページに遷移をさせたかったため、aタグを使いました。
最後に吹き出しを作成しただけでは表示されないためaddListenerを利用してマーカーがクリックされたときに吹き出しが開くようにしました。

以上、GoogleMapに複数マーカーを表示する方法でした。

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

Apollo ClientのReactive variablesが良さげ [Local resolvers deprecated] [Local-only fields]

久々にドキュメント読んだらLocal resolversがdeprecatedになっていた事件

なんてこった。
Local resolversの代わりにLocal-only fields使うといいらしい。

Local-only fields

今までのようにapollo cacheに値を保存する方法と、Reactive variablesという新しいものがあるという。
この記事ではReactive variablesのほうについて書く。

Reactive variables

apollo clientのキャッシュ以外の場所でローカル値を保有できるらしく、キャッシュと分離してるがゆえに、どんな型だろうと保存できる。
また、GraphQL queryを書かずにアクセスできるのも特徴。
うーん、良い。

作る

makeVar関数で作ることができる。

import { makeVar } from '@apollo/client';

// 空の配列を作る。
const cartItemsVar = makeVar([]);

簡単。

アクセス

関数のように呼べば値を取得でき、引数を渡せば変更できる。

// 値を出力。
console.log(cartItemsVar());

// 値を変更
cartItemsVar([...cartItemsVar(), newItem]);

簡単。

Reactive

値が変更されたらコンポーネントの更新もしてほしいのが普通。

useReactiveVar

直接値を使うときはuseReactiveVar hook。

import { useReactiveVar } from '@apollo/client';

export function Cart() {
  const cartItems = useReactiveVar(cartItemsVar);

  return (
    <div class="cart-items">
      {cartItems.map(productId => (
        <CartItem key={productId} />
      ))}
    </div>
  );
}

簡単。

GraphQL queryで問い合わせる

export const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        cartItems: {
          read() {
            return cartItemsVar();
          }
        }
      }
    }
  }
});

cacheに登録をすれば今まで通りGraphQL query投げて値を取得できる

export const GET_CART_ITEMS = gql`
  query GetCartItems {
    cartItems @client
  }
`;

export function Cart() {
  const { data, loading, error } = useQuery(GET_CART_ITEMS);

  return (
    <div class="cart-items">
      {data && data.cartItems.map(productId => (
        <CartItem key={productId} />
      ))}
    </div>
  );
}

が、果たしてこれをする必要があるのかは疑問。
特有のメリットとしては@exportが使えるようになること。

@export

↓ローカルにある値を取得し、それをサーバーに投げるクエリの引数として使うことを一つのGraphQL queryで出来る。

query CurrentAuthorPostCount($authorId: Int!) {
  # currentAuthorIdの戻り値をauthorIdとしてエクスポート。
  currentAuthorId @client @export(as: "authorId")
  # エクスポートされたauthorIdを通常のクエリの引数に使う。
  postCount(authorId: $authorId)
}

便利っちゃ便利。

感想

手軽そう。

今までの仕組み(Local resolvers)だと、ローカルのデータへのアクセスも基本的にGraphQLの仕組みで行われていて、GraphQL queryを書く必要があった。
サーバー側のデータと同じように扱えるという一貫性はあった一方で、面倒でもあった。
クエリ書かずにローカルステートの管理ができるのは非常にありがたい。

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

ブックマークレット(JavaScript)で湯婆婆を実装してみる

書いたあとで見つけたんですが、同じjs実装で圧倒的にスマートな例が別の湯婆婆のコメント欄にありました。
https://qiita.com/TD12734/items/743e90fb867fee8c562e#comment-5abe544380a4b2d29af3
上記、@shinji709様のコードが先となりますorz

はじめに

Javaで湯婆婆を実装してみる」のフォロワーネタです。
JavaScript版は既出なので、ブックマークレットにしました。

ブックマークレットは昔ゲーム・オブ・スローンズの画面が(演出上)暗くて見えにくい事にイライラして、アマプラの動画の輝度を調整するChromeExtensionを作ろうとした時にちょっと勉強した記憶があります。
(輝度調整のChrome拡張機能は既にあった)

コード

javascript:(function(){let n=window.prompt("契約書だよ。そこに名前を書きな。");if (!n.trim()) n=null;window.alert(`フン。${n}というのかい。贅沢な名だねぇ。`);n=n.substr(Math.floor(Math.random() * Math.floor(n.length)), 1);window.alert(`今からお前の名前は${n}だ。いいかい、${n}だよ。分かったら返事をするんだ、${n}!!`)})()

適当なサイトなどでブックマークを作り、リンク先を上記コードに置き換えるとブックマークレットとして動作します。
image.png
faviconを湯婆婆に変えるとよりそれっぽくなるかもしれません。

注記

ブックマークレットは、開いているページ内容(タブ)に影響します。
(文字列置換ブックマークレットとかありますよね)
Chromeの「新しいタブ」など、サイトではなくChromeシステム配下のページではJavaScriptは動作しないようです。
参考:http://mk.miko.jp/blog/archives/4497
yahooやqiitaなど適当なサイトを開き、そのタブで実行してください。

コードの解説

// ブックマークレットは今開いているサイトのスコープと干渉するので即時関数にする
(
  function(){
    let n=window.prompt("契約書だよ。そこに名前を書きな。");

    // 名無しの場合は異常終了させるためにnullを代入。Cannot read property 'substr' of nullで落ちます。
    if (!n.trim()) n=null;
    window.alert(`フン。${n}というのかい。贅沢な名だねぇ。`);

    // 抽選。先に抽選すればダイアログ2個で済みますが、あえて分けました。
    n=n.substr(Math.floor(Math.random() * Math.floor(n.length)), 1);
    window.alert(`今からお前の名前は${n}だ。いいかい、${n}だよ。分かったら返事をするんだ、${n}!!`)
  }
)()

実行結果

image.png

最後に

大昔にいじったDelphiとか引っ張り出そうかと思いましたが流石にめんどうでやめましたw

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

【JavaScript】CSV 書き出しの際に必要なエスケープ処理と二次元配列→文字列変換の方法

CSV 書き出しの際に必要なエスケープ処理と二次元配列→文字列変換の方法

こんにちは、ndj です。
二次元配列から変換した CSV(カンマと改行コードで区切ってあるアレです)をファイルなどに書き出す際に値にエスケープ処理を施す必要があったので、そのときの方法を記録しておきます。
また、合わせて二次元配列を CSV 形式の文字列へ変換する方法についても記録しておきます。

なぜエスケープ処理が必要なのか

CSV 形式のファイルを作成する際、なぜエスケープ処理が必要なのかといいますと、値の中に,(カンマ)や\n(改行)が含まれてしまっていると、Excel や他のアプリケーションなんかで CSV を読み込んだときに値中に含まれている,(カンマ)や\n(改行)と区切り文字である,(カンマ)や\n(改行)を区別できないわけです。
なので、値は「""(ダブルクォーテーション)」で囲んで値であるということを明示する必要があります。

エスケープ処理を行う方法

escape.js
// エスケープ処理
const escapeForCSV = (s) => {
    return `"${s.replace(/\"/g, '\"\"')}"`
}

正規表現を用いて、引数として与えられた文字列をダブルクォーテーションで囲っています。
引数として与えられた文字列中に、「"」が含まれていると CSV に変換した際に値がずれてしまうため、「""」としてエスケープします。

二次元配列から CSV 文字列へ変換する方法

convert.js
const arrToString = (arr, colDelimeter=',', rowDelimeter='\n') => {
    return arr.map((row) => row.map((cell) => escapeForCSV(cell)).join(colDelimeter)).join(rowDelimeter);
}

まず、二次元配列を map() メソッドで行(row)を取り出す。
row は一次元配列になっているので、さらに map() メソッドを用いてセル(cell)を取り出す。
このとき、各値にエスケープ処理を施す。
そして、row を区切り文字で連結(この場合は「,」)する。
最後に行区切り(この場合の区切り文字は「\n」)を行い、完了。

サンプルコード

先述したように、arrToString() 内で各セルに対して escapeForCSV() を実行して、エスケープ処理を施しています。
TSV 形式にしたい場合は、arrToString() に引数を渡せばOK。

sample.js
// エスケープ処理
const escapeForCSV = (s) => {
    return `"${s.replace(/\"/g, '\"\"')}"`
}
//二次元配列 -> CSV形式の文字列に変換
const arrToString = (arr, colDelimeter=',', rowDelimeter = '\n') => {
    return arr.map((row) => row.map((cell) => escapeForCSV(cell)).join(colDelimeter)).join(rowDelimeter);
}

// CSV 形式文字列に変換
let sample = [['a', 'b', 'c'], ['"a"', '"b"', '"c"']];
console.log(arrToString(sample));
// "a","b","c"
// """a""","""b""","""c"""

// TSV 形式にしたい場合
console.log(arrToString(sample, colDelimeter='\t'));
// "a"  "b" "c"
// """a"""  """b""" """c"""

最後に

どちらも汎用的な処理なので、おそらくライブラリがあると思いますが、手法を知っておくのは必要だと思ったので、残しておきます。
誤字脱字やアドバイス、ご指摘などございましたらコメントや編集リクエストなどくださると幸いです。
以上です。
ここまで読んでくださりありがとうございました。

Twitter(@teqndj)

参考

CSV について

CodeZine: CSVファイルフォーマットの解説

正規表現について

Qiita: JavaScript 正規表現まとめ @iLLviA 様

非常に参考になりました。ありがとうございました。

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

Vue.jsを使って簡単なカウンターアプリを作ってみた

最近Vue.jsの勉強を本格的に始めました。ひとまず何か簡単なものを作ろう!ということで、今回はカウンターアプリを作ってみました。良ければお試し下さい→カウンターアプリ

実現したいこと

1.ボタンをカチカチして数値を変更し、計算させる。
2.計算方法は足し算、引き算、掛け算、割り算を用意する。
3.計算方法の表記は、切り替えたタイミングで上に表示させる。

app2.gif

コード

index.html
<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>カウンターアプリ</title>
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Oswald:wght@400;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="app">
        <div id="container">
            <h1 class="title">{{title}}</h1>
            <div class="inputArea">
                <div class="top">
                    <button class="btn" @click="firstIncrement">+</button>
                    <button class="btn" @click="secondIncrement">+</button>
                </div>
                <h1 class="cal">{{first}} {{key}} {{second}} = {{resultAdd(key)}}</h1>
                <div class="bottom">
                    <button class="btn" @click="firstDecrement">-</button>
                    <button class="btn" @click="secondDecrement">-</button>      
                </div>
            </div>
            <div class="format">
                <button  class="changeBtn" @click="key = '+'">+</button>
                <button  class="changeBtn" @click="key = '-'">-</button>
                <button  class="changeBtn" @click="key = '×'">×</button>
                <button  class="changeBtn" @click="key = '÷'">÷</button>
                <button  class="changeBtn" @click="first = 0, second = 0">C</button>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
    <script src="main.js"></script>
</body>
</html>
style.css
*{
    margin: 0;
    padding: 0;
    font-family: 'Oswald', sans-serif;
    touch-action: manipulation;
}
body{
    color: white;
    background-color: black;
}
#container{
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
button{
    color:black;
    background-color: white;
    outline: none !important;
    cursor: pointer;
}
.title{
    font-size: 45px;
    margin-bottom: 60px;
}
.cal{
    font-size: 50px;
    text-shadow: 3px 3px rgb(121, 121, 121);
}
.inputArea{
    width: 100%;
    height: 250px;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    align-items: center;
    margin-bottom: 60px;
    & .btn{
        font-size: 30px;
        font-weight: 700;
        padding: 5px 10px;
        margin: 20px;
    }
    & .top,
    .bottom{
        margin-right: 70px;
    }
}
.changeBtn{
    font-size: 30px;
    font-weight: 700;
    padding: 5px 10px;
    margin: 0px 10px;
}
main.js
new Vue({
    data:{
        first:0,
        second:0,
        sum:0,
        title:'addition',
        format:['Addition','Subtraction','Multiplication','Division'],
        key:'+',  
    },
    methods:{
        firstIncrement(){
            this.first += 1;
        },
        firstDecrement(){
            if(this.first > 0){
                this.first -= 1;
            }
        },
        secondIncrement(){
            this.second += 1;
        },
        secondDecrement(){
            if(this.second > 0){
                this.second -= 1;
            }
        },
    },
    computed:{
        resultAdd(){
            return function(f){
                if(f === '+'){
                    this.title = this.format[0];
                    return this.first + this.second;
                }else if(f === '-'){
                    this.title = this.format[1];
                    return this.first - this.second;
                }else if(f === '×'){
                    this.title = this.format[2];
                    return this.first * this.second;
                }else{
                    this.title = this.format[3];
                    this.sum = this.first / this.second;
                    return Math.round(this.sum * 1000) / 1000;
                }
            }
        },
    },
}).$mount("#app");

methodsプロパティ

+ボタン、-ボタンをクリックすると、methodsプロパティのIncrementメソッド、Decrementメソッドが発火します。メソッド内ではdataプロパティに設定したfirstプロパティとsecondプロパティを参照し、値を加算、減算します。
メソッドからdataプロパティを参照する時はthisを忘れずに(よく忘れてエラーになります・・・)

computedプロパティ

computedプロパティのresultAddではfirstやsecondの変更を検出して計算結果を返してくれます。
さらに今回は計算方法が4パターン欲しいので、resultAddに引数を持たせて、条件分岐をさせてみました。例えば渡された引数の文字列が「+」だったら、足し算の結果を返す。「×」だったら、掛け算の結果を返す。という感じです。

また、計算方法の変更は、下の「+」「-」「×」「÷」のボタンで変更しますが、これらのボタンが押された際も、resultAddが発火し、表記の変更をしつつ、変更した方法で計算結果を返してくれます。(computed便利!)

割り算で四捨五入

割り算で割り切れない場合、結果が「3.33333333・・・」という感じで表示され、レイアウトが大変なことになるので、今回はMath.round関数を使って、小数点第四位を四捨五入しています。

作ってみて

という感じで、簡単なカウンターアプリを作ってみました。
JavaScriptと比べるとVue.jsはコードが短く、簡潔に記述できるので、(もっと簡潔に書けるかもしれませんが、、)改めてフレームワークすご!!と思いましたね。

引き続きVue.js頑張りたいと思います!

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

【JavaScript】CSV 書き出しの際に必要なエスケープ処理

CSV 書き出しの際に必要なエスケープ処理を記録しておく

こんにちは、ndj です。
二次元配列から変換した CSV(カンマと改行コードで区切ってあるアレです)をファイルなどに書き出す際に値にエスケープ処理を施す必要があったので、そのときの方法を記録しておきます。

注意点

今回対象となる CSV フォーマットは、
列を「,(カンマ)」、
行を「\n(改行)」
で区切っているものです。

なぜエスケープ処理が必要なのか

CSV 形式のファイルを作成する際、なぜエスケープ処理が必要なのかといいますと、値の中に,(カンマ)や\n(改行)が含まれてしまっていると、Excel や他のアプリケーションなんかで CSV を読み込んだときに値中に含まれている,(カンマ)や\n(改行)と区切り文字である,(カンマ)や\n(改行)を区別できないわけです。
なので、区切り文字ではない文字列として,(カンマ)や\n(改行)はきちんと印をつけておく(エスケープしておく)必要があります。

エスケープ処理を行う JavaScript Function

Function

escape.js
const escapeForCSV = (s) => {
    // エスケープ対象
    const targetLst = ['\"', ',', '\n', '\r'];
    // 受け取った引数で戻り値を初期化する
    let escapedValue = s;
    // エスケープ処理を行う
    targetLst.forEach((elm) => {
        let reg = new RegExp(elm, 'g');
        escapedValue = escapedValue.replace(reg, `\"${elm}\"`);
    });

    return escapedValue
}

正規表現を用いて、エスケープの対象となる文字列をダブルクォーテーションで囲っています。
「"」に関しては、値を「""」で囲む場合

使用方法と結果

変換したい文字列を引数として与えるだけです。
CSV 文字列変換後(区切り文字を追加した後)だと区切り文字か値中の文字か判断できないので注意
二次元配列→一次元配列の変換の際に使うとよい。

sample.js
// 基本
let sample1 = '\n」は、改行コードです。';
sample1 = escapeForCSV(sample1);

// 配列の場合
let sample2dArr = [['\n', '\r', '\"', ','], ['改行', '行頭復帰', 'ダブルクォーテーション', 'カンマ']];
let sampleArr = sample2dArr.map((arr) => escapeForCSV(arr.join(',')));
let csv = sampleArr.join('\n');

インプットとアウトプットを確認してみます。

sample.js
const logging = (attr, callback) => {
    let processed = callback(attr);
    console.log(attr);
    console.log(processed);
}

let sample1 = '\n」は、改行コードです。';
let sample2 = '「,」は CSV のセパレータです。';
let sample3 = '「"」はダブルコーテーションかダブルクォーテーションどちらで表記するか迷う。';

logging(sample1, escapeForCSV);
// output before:「\n」は、改行コードです。
// output  after:「"\n"」は、改行コードです。
logging(sample2, escapeForCSV);
// output before: 「,」は CSV のセパレータです。
// output  after: 「","」は CSV のセパレータです。
logging(sample3, escapeForCSV);
// output before: 「"」はダブルコーテーションかダブルクォーテーションどちらで表記するか迷う。
// output  after: 「"""」はダブルコーテーションかダブルクォーテーションどちらで表記するか迷う。

最後に

汎用的な処理として使えそうだったので、残しておきます。
アレ、呼び出しのときはどうすればいいんだ...?
おそらくすぐ必要になるかと思うので、近いうちに記事にしたいと思います。
誤字脱字やアドバイス、ご指摘などございましたらコメントや編集リクエストなどくださると幸いです。
以上です。
ここまで読んでくださりありがとうございました。

Twitter(@teqndj)

参考

CSV フォーマットについて

CodeZine: CSVファイルフォーマットの解説

正規表現について

Qiita: JavaScript 正規表現まとめ @iLLviA 様

非常に参考になりました。ありがとうございました。

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

Javascriptのコールバック関数について整理する

Javascriptを勉強中、コールバック関数を理解するのに無駄に時間がかかってしまったのでメモとして整理しておきます。私の様な初心者の方の理解の一助となれば幸いです。内容はコールバック関数の非常に初歩的な部分についてのみとなっています。

ちなみにこれがQiita初投稿でプログラミング自体も初心者なので、間違いや的外れな箇所があるかもしれません。何かあればコメントでご指摘ください。

コールバック関数とは?

定義は「他の関数の引数として渡される関数」のことです。Javascriptだけでなく他の言語でも使われます。

この定義だけみるとシンプルで何のことはないのですが、いざプログラムに書くと見た目がかなり複雑になって何をしているのかが分かりづらくなり、勉強中の私は非常に混乱しました。

単純な関数との比較

コールバック関数が出てくるとカッコの数や行の数が多くなり混乱しやすいですが、数値などの引数を持つ通常の関数との比較という形で考えると理解しやすいと思います。

例えば、以下のadd関数は二つの数値を引数に取り、その和をコンソールに表示する関数です。(なお、アロー関数に関してはこちらの記事が分かりやすいと思います。)

let add = (a, b) => {
  console.log(a + b);
};

そして add (1, 2); などと実行すると、aとbにそれぞれ1, 2が代入されその和である3がコンソールに表示されます。

同様に、以下のfuncAは、funcBという関数を引数としてとる関数です。

let funcA = (funcB) => {
  funcB('Hello!');
};

funcAは、「funcBという関数を"Hello!"という引数で実行する関数」です。このfuncBがコールバック関数に当たります。

この時点では、funcBがどんな関数なのかは分かりません。それはこれからfuncAの実行時に引数として指定します。
add関数の引数である(a, b)と同じです。

実行してみる

それでは適当にfuncBを指定して実行してみましょう。

funcA((message) => {
  console.log(message);
}); //結果: Hello!

なんだか見たことのある形になりましたね。カッコの数や行数が多くなって分かりづらいですが、add(1, 2);などとやっていることは同じです。要は

funcB = (message) => {
  console.log(message);
};

という感じで、引数であるfuncBの実体となる関数を指定し、funcAを実行しているだけです。
ここでfuncBに指定した関数は、messageという変数を引数に取り、それをコンソールに表示します。

そして下に再掲した通り、funcAというのは元々「funcBを"Hello!"という引数で実行する関数」でしたから、結果としてコンソールに"Hello!"が出力されます。

let funcA = (funcB) => {
  funcB('Hello!');
};

余談

ChromeのDeveloper Toolsでは、現在の変数の値などを逐一表示しながらプログラムを一行ずつ実行できるので、学習に非常に有効でした。

callback.gif

今後

元々PromiseやAsync, Awaitなどの非同期処理を勉強しようとしていたところだったので、そちらについてもそのうち書きたいと思っています。

参考

Javascriptの「コールバック関数」とは一体何なのか

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

Javascriptのコールバック関数でつまずいた

Javascriptを勉強中、コールバック関数を理解するのに無駄に時間がかかってしまったのでメモとして整理しておきます。私の様な初心者の方の理解の一助となれば幸いです。内容はコールバック関数の非常に初歩的な部分についてのみとなっています。

ちなみにこれがQiita初投稿でプログラミング自体も初心者なので、間違いや的外れな箇所があるかもしれません。何かあればコメントでご指摘ください。

コールバック関数とは?

定義は「他の関数の引数として渡される関数」のことです。Javascriptだけでなく他の言語でも使われます。

この定義だけみるとシンプルで何のことはないのですが、いざプログラムに書くと見た目がかなり複雑になって何をしているのかが分かりづらくなり、勉強中の私は非常に混乱しました。

単純な関数との比較

コールバック関数が出てくるとカッコの数や行の数が多くなり混乱しやすいですが、数値などの引数を持つ通常の関数との比較という形で考えると理解しやすいと思います。

例えば、以下のadd関数は二つの数値を引数に取り、その和をコンソールに表示する関数です。(なお、アロー関数に関してはこちらの記事が分かりやすいと思います。)

let add = (a, b) => {
  console.log(a + b);
};

そして add (1, 2); などと実行すると、aとbにそれぞれ1, 2が代入されその和である3がコンソールに表示されます。

同様に、以下のfuncAは、funcBという関数を引数としてとる関数です。

let funcA = (funcB) => {
  funcB('Hello!');
};

funcAは、「funcBという関数を"Hello!"という引数で実行する関数」です。このfuncBがコールバック関数に当たります。

この時点では、funcBがどんな関数なのかは分かりません。それはこれからfuncAの実行時に引数として指定します。
add関数の引数である(a, b)と同じです。

実行してみる

それでは適当にfuncBを指定して実行してみましょう。

funcA((message) => {
  console.log(message);
}); //結果: Hello!

なんだか見たことのある形になりましたね。カッコの数や行数が多くなって分かりづらいですが、add(1, 2);などとやっていることは同じです。要は

funcB = (message) => {
  console.log(message);
};

という感じで、引数であるfuncBの実体となる関数を指定し、funcAを実行しているだけです。
ここでfuncBに指定した関数は、messageという変数を引数に取り、それをコンソールに表示します。

そして下に再掲した通り、funcAというのは元々「funcBを"Hello!"という引数で実行する関数」でしたから、結果としてコンソールに"Hello!"が出力されます。

let funcA = (funcB) => {
  funcB('Hello!');
};

余談

ChromeのDeveloper Toolsでは、現在の変数の値などを逐一表示しながらプログラムを一行ずつ実行できるので、学習に非常に有効でした。

callback.gif

今後

元々PromiseやAsync, Awaitなどの非同期処理を勉強しようとしていたところだったので、そちらについてもそのうち書きたいと思っています。

参考

Javascriptの「コールバック関数」とは一体何なのか

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