20210302のJavaScriptに関する記事は26件です。

Mapbox で 地図検索 機能を付けてみた

1. はじめに

この記事は「Mapboxで地図を表示してみた」の続きになります。

  • Qiita: Mapboxで地図を表示してみた

前回は、Mapbox を使用して 地図をWebブラウザ上に表示しました。

今回は、検索機能を追加し、指定した場所の地図を表示するようにします。

2. 準備

2-1. アカウント作成・アクセストークン 取得

前回記事の [2. 準備] を参考に、
Mapboxのアカウントを作成し、アクセストークン を取得します。

  • Mapboxで地図を表示してみた > [2. 準備]

2-2. スタイル作成

日本語で 地図を表示・検索できるようにするため、日本語に対応したマップスタイルを取得します。

Mapbox では、Mapbox Studio を使ってマップスタイルを設計・管理します。

  • Mapbox | Docs: Mapbox Studio:

① Mapbox Studioを開く

以下リンク先の Mapbox Studio を開きます。

  • Mapbox Studio:

② スタイル選択

今回は、Gallery で すでに用意されたスタイルを使用します。

Mapbox Studio 画面右側の [Find inspiration in the style gallery] をクリックします。

image.png

使用可能なスタイルが 表示されます。

image.png

下にスクロールし、[Mapbox Streets Japan]スタイル を選択します。

image.png

③ スタイル追加

[Add Mapbox Streets Japan to your account] をクリックし、スタイルを 自分のアカウントに追加します。

image.png

④ Style URL取得

追加したスタイルのURLを取得します。
このURLは 後のスクリプトに使用します。

image.png

3. コーディング

今回は 地図を表示した上で、検索機能を追加し、指定した場所の地図を表示するようにします。

3-1. サンプル

・ HTMLファイル

東京駅周辺(緯度: 35.6809591、経度: 139.7673068)のマップを表示し、
さらに検索した場所周辺のマップを表示するような HTMLファイル(map.html)を作成します。

map.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>地図表示</title>
    <script src="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css" rel="stylesheet" />

<style>
    html { height: 100% }
    body { height: 100% }
    #map { height: 100%; width: 100%}
</style>
</head>

<body>
    <script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.5.1/mapbox-gl-geocoder.min.js"></script>
    <link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.5.1/mapbox-gl-geocoder.css" type="text/css">

    <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script>

    <div id='map'></div>

    <script>
        mapboxgl.accessToken = '<アクセストークン>';
        var map = new mapboxgl.Map({
            container: 'map',
            style: '<Style URL>',
            center: [139.7670516, 35.6811673],
            zoom: 15
        });

        map.addControl(
            new MapboxGeocoder({
                accessToken: mapboxgl.accessToken,
                mapboxgl: mapboxgl
            })
        );
    </script>

</body>
</html>

・ ブラウザ表示

map.html をブラウザ(ここではChromeを使用)で開いた画面を下に示します。

初期で指定した座標 (東京駅周辺) の地図が表示されます。
image.png

京都駅」で検索すると、京都駅周辺の地図が表示されます。
image.png
image.png

Mapbox | Docs :Add a geocoder

3-2. 解説

サンプル(map.html)の中身を解説します。

・JavaScript・CSSファイル 読込

head 要素に、
以下のように Mapbox を使用するための jsファイル・cssファイルを読み込みます。

    <script src="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css" rel="stylesheet" />

・style属性 指定

style 要素にて、HTMLのスタイルを指定します。

ここでは、ウィンドウの高さheight100%に設定しています。

また、マップ(id : map)のスタイルを、高さheight100%、幅 width100% に設定します。

<style>
    html { height: 100% }
    body { height: 100% }
    #map { height: 100%; width: 100%}
</style>

・mapbox-gl-geocoder 使用のためのJavaScript・CSSファイル 読込

mapbox-gl-geocoder を使用するための jsファイル・cssファイルを読み込みます。

    <script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.5.1/mapbox-gl-geocoder.min.js"></script>
    <link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.5.1/mapbox-gl-geocoder.css" type="text/css">

また、IE11でmapbox-gl-geocoderを使用する場合、
Promise polyfillも読み込みます。

    <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script>

・マップ表示

div要素にて、マップ(id : map)をブラウザ画面上に配置します。

<div id="map"></div>

・スクリプト記述

script 要素にて、マップ(id:map)を表示し、検索するためのスクリプトを記述します。

    <script>
        mapboxgl.accessToken = '<アクセストークン>';
        var map = new mapboxgl.Map({
            container: 'map',
            style: '<Style URL>',
            center: [139.7670516, 35.6811673],
            zoom: 15
        });

        map.addControl(
            new MapboxGeocoder({
                accessToken: mapboxgl.accessToken,
                mapboxgl: mapboxgl
            })
        );
    </script>

以下に、スクリプトの中身を解説します。

  • アクセストークン設定

第2項 で取得した アクセストークンを、
mapboxgl.accessToken にセット設定します。

mapboxgl.accessToken = '<アクセストークン>';
  • マップ設定

Map オブジェクトを作成し、ページにマップを表示します。

var map = new mapboxgl.Map({
  container: 'map',
  style: '<Style URL>',
  center: [139.7670516, 35.6811673],
  zoom: 15
});

オプションで、マップの細かな設定をすることができます。
ここでは containerstylecenterzoom の4項目のオプションを設定しています。

オプション 説明
container Mapbox GL JSがマップをレンダリングするHTML要素、または要素の文字列ID。
style マップのスタイル。
center 最初にマップ表示する地理的中心点。
zoom マップの初期ズームレベル。

container 項目には、’map’ を設定します。

style 項目には、第2項で取得した Style URL を指定します。

center 項目には、東京駅周辺(緯度: 35.6809591、経度: 139.7673068)のマップを表示するために、
[139.7670516, 35.6811673] を設定します。

zoom 項目には、マップの初期ズームレベルとして 15 を設定します。

  • 検索機能 追加

ジオコーダー(・・・場所から 緯度経度の値を取得する) を追加し、
指定した場所を検索できるようにします。

map.addControl(
  new MapboxGeocoder({
    accessToken: mapboxgl.accessToken,
    mapboxgl: mapboxgl
  })
);

addControl メソッドを使用して、コントロールを追加します。
ここでは、Mapbox Geocoding APIの mapbox-gl-geocoder コントロール ( MapboxGeocoder )を追加します。

MapboxGeocoder でジオコーダーを設定します。
オプションでは、accessTokenmapboxgl の2項目を設定します。

オプション 説明
accessToken アクセストークン
mapboxgl mapboxglオブジェクト

accessTokenには アクセストークン mapboxgl.accessToken を、
mapboxglには mapboxglを設定します。

  • GitHub:mapbox/mapbox-gl-geocoder

4. おわりに

今回は、Mapboxを使って 指定した場所の地図を検索 してみました。

mapbox-gl-geocoderを使用することで、
他の地図サービスと同じような検索機能を、とても簡単に追加することができました。

参考情報

  • Mapbox GL _ JS API REFERENCE

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

【JavaScript 関数】RGBメソッドの使い方

RGB 関数

これが元になって調査

// RかGかBの値が変わったら、できた色を算出する
mixColor: function() {
    var ans = "RGB("+this.R+", "+this.G+", "+this.B+")";
    return ans;
},

構文

RGB(赤、緑、青)

確認画面

3つのスライダー動かしたら指定した部分のbackgroundColorが変化します!
スクリーンショット 2021-03-02 23.28.03.png

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

【JavaScript 関数】clearIntervalメソッドの使い方

clearInterval()

setInterval()でセットしたタイマーを解除する

これが元になって調査

methods: {
    startTimer: function () {
        // 残り5秒
        this.restSec = 5;
        // タイマースタート。1秒(1000ミリ秒)ごとに、1秒減らす
        this.timerObj = setInterval(() => {
            this.restSec--;
        }, 1000)
        // console.log(this.timerObj);
    }
},
watch: {
        restSec: function() {
            // 0秒以下になったらアラート&タイマー停止
            if (this.restSec <= 0) {
                alert("制限時間です。");
                clearInterval(this.timerObj);
            }
        },

}

参考

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

【jquery,js】表示されたcsvを並べ替える[sort,join,trim,split,each] [js10_20210302]

処理の概要

テキストエリアに表示されたcsvデータを読みの順で並べ替えを行います。

処理のフロー:

 (1)初期化メソッドでテキストエリアにcsvテキストを表示します。
 (2)実行ボタンを押下するとテキストを取得します。
 (3)指定の文字列を取得し、任意のカラムで比較を行います。
 (4)テキストエリア2にソートしたテキストを表示する

画面イメージ

画像1

画像2

ソースコード

index.html
<body onload="init()">
    <!-- テキストエリア表示内容はタブ入力されるので、インデント注意 -->
    <textarea id="beforeUserInput"></textarea>
    <br>
    <input type="button" id="execSortButton" value="並べ替え">
    <br>
    <textarea id="afterUserInput"></textarea>
</body>

リファクタリングする前

main.js
function init(){
    var lobjText = ""
    var defaultStr = [
        ["蜜柑","みかん",80],
        ["西瓜","すいか",300],
        ["葡萄","ぶどう",280]
    ]

    $.each(defaultStr,function(){
        lobjText = lobjText + this + "\n";
    });
    $("#beforeUserInput").val(lobjText)
};

$(function() {
    $("#execSortButton").click(function(){
        var sortItem = $("#beforeUserInput").val();
        sortItem = sortItem.trim();
        var sortArray = sortItem.split("\n");
        // 列数を指定
        var sortColumnNum = 2;

        sortArray.sort(function(sortName1,sortName2){
            //ソートする対象となる列数を格納する
            var strNumber = sortColumnNum - 1
            var compareNameA = sortName1.split(",")[strNumber];
            var compareNameB = sortName2.split(",")[strNumber];

            if (compareNameA < compareNameB) return -1;
            if (compareNameA > compareNameB) return 1;
            return 0;
        });

        outputText = sortArray.join("\n")
        $("#afterUserInput").val(outputText);

    });
});

ポイント

html:
(1)特になし
js:
(1)sortItemの前後に無駄な改行や、タブ文字、半角スペースが存在しないように除去する。
この方法は、「trim()」を利用する
(2)ソート処理は、sort()関数の引数に関数を指定することで複雑な条件でソートが行える。
(3)join()は配列に対して、セパレーターをドッキングさせて文字列とする。
引数がない場合は、カンマでドッキング。
[a,b,c].join() => "a,b,c"
(4)split()は引数を入力すると、セパレータを排除して、配列に変換する。
"a\nb\nc".split(\n) => [a,b,c] 便利!

参考資料

JavaScript(仕事の現場でサッと使える!デザイン教科書) p210

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

CKEditor をローカルPCでHTML(WYSIWYG)エディタもどきとして使用する

はじめに

windows環境でフリーのHTMLエディタを探したところ、適当なのが見つからずにCKEditorエディタなるものが検索にヒットしたので調べてみたらwebアプリだったので、それを出来るだけwindows環境でローカルアプリのように使用出来るようにしてみた。

まずはカスタマイズを一切せずに使用してみる

以下のコードをCKEditor.htmと名前を付けて保存し、ブラウザで開いてみる

CKEditor.htm
<script src="https://cdn.ckeditor.com/4.16.0/full-all/ckeditor.js"></script>
<textarea name="editor1" id="editor1"></textarea>

<script>
const editor = CKEDITOR.replace('editor1');
</script>

image.png
メニューの左上のソースというボタンで、htmlフォーマットとソースコードを切り替えて、ソースコードをクリップボードにコピーすれば、最小限のHTMLエディタとして機能させることが出来ます。

いろいろカスタマイズして使い勝手を良くしてみる

やりたいこと

  1. 起動時に最大化して起動する
  2. ファイルを開くコマンドを追加する
  3. 保存ボタンで、ファイルを保存出来るようにする
    保存ボタンを有効にするには、<textarea>タグを<form>タグで囲みます
    保存時にダイアログを表示するには、ブラウザの設定が必要です
    例:Google Chromeの場合 image.png
  4. ウィンドウを閉じるときに確認メッセージを表示させる
  5. 自分用のcssファイルを用意して、適用できるようにする
  6. インデント(字下げ)を1文字単位に変更する(デフォルトでは40px単位)
CKEditor.htm
<script src="https://cdn.ckeditor.com/4.16.0/full-all/ckeditor.js"></script>
<form>
<textarea name="editor1" id="editor1"></textarea>
</form> 

<script>
//ファイルを開くボタン押下時のファイル読込み処理
function onOpenFileChange(event) {
  const file = event.target.files[0];
  if(file){
    filename = file.name;
    const reader = new FileReader();
    reader.readAsText(file);
    reader.onload = () => {
        editor.setData( reader.result );
    };
  }
}

//ファイルを開くボタン押下処理
function OpenFile(editor) {
  const input = document.createElement('input');
  input.type = 'file';
  input.accept = 'text/html';
  input.setAttribute('onchange', 'onOpenFileChange(event)');
  input.click();
}

//保存ボタン押下処理
function SaveFile(editor) {
  const a = document.createElement('a');
  a.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(editor.getData());
  a.download = filename;
  a.click();
}

CKEDITOR.on('instanceReady', function (event) {
    //保存ボタン置換え
    var overridecmd = new CKEDITOR.command(editor, {exec: SaveFile});
    event.editor.commands['save'].exec = overridecmd.exec;

    //最大化する
    event.editor.execCommand('maximize');
});

let filename = '';
//インデント(字下げ)の単位を1文字に変更する
CKEDITOR.config.indentUnit = 'em';
CKEDITOR.config.indentOffset = 1;
//スキンを適用し、見た目を変更する
CKEDITOR.config.skin = 'moono'; 
//ユーザ定義のCSSを適用する
CKEDITOR.config.contentsCss =  [ './style.css']; 
const editor = CKEDITOR.replace('editor1');

//エディタのプラグインが呼び出される際のcallback
editor.on('pluginsLoaded', function(event) {
  //ファイルを開くボタンを追加する
  this.ui.addButton('openFile', {
    label: 'ファイルを開く', 
    command: 'openFile', 
    toolbar: 'document,3',  //ボタンを追加するtoolbarと位置を指定
    icon: 'https://icongr.am/fontawesome/folder-open-o.svg' 
  });
  this.addCommand('openFile', {exec: OpenFile});
});

//ウィンドウを閉じる時に確認メッセージを表示させる
window.addEventListener('beforeunload', function(event) {
  event.preventDefault();
  event.returnValue = '';
});
</script>
style.css
body
    {background-color:#FEF2DB;}
p
    {margin:2px 0;
    text-align:justify;
    text-justify:inter-ideograph;
    font-size: 1.0em;
    font-family:sans-serif;}
hr
    {margin:20px 0;}
h1
    {margin:8px 0;
    text-align:justify;
    text-justify:inter-ideograph;
    font-size: 1.2em;
    font-family:sans-serif;}
a:link
    {color:blue;
    text-decoration:underline;
    font-family:sans-serif;}
a:visited
    {color:purple;
    text-decoration:underline;
    font-family:sans-serif;}

CKEditor.htm
style.css
を同じフォルダにコピーして、CKEditor.htm をブラウザで開きます

環境

OS : WINDOWS 10
ブラウザ : Google Chrome
CKEditorバージョン : CKEditor4

参考

CKEditor4 リファレンス
CKEditor4 Githubリポジトリ
ブログ記事等のエディタを実装する際はCKEditor!さらに便利でカッコよく使い易く!サンプルソースあり
javascript - CKEditorの[保存]ボタンでクリックイベントをキャプチャする方法
Webデザインで使えるWebアイコンのCDNサービス「icongram」

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

pm2でnext js を永続化させる方法について。プロダクション環境での注意点も含めてご紹介

next jsを永続化し落ちないようにするために試行錯誤したのでご紹介。
next jsをAWS ec2 のような環境に直接デプロイ(npm run start)すると安定せず突然サイトが落ちることがあります。
pm2を使用すると自動的にサーバーを再起動&監視してくれプロダクション環境でも安定して使用可能です。

Step1

pm2 を環境にインストール

npm install -g pm2

上記のコマンドをPC(もしくはクラウド環境)に打ち込むとインストールできます。

Step2

next jsをビルドする。

npm run build

ビルドすることにより、高速にページが開けるようになります。

Step3

pm2でnext jsを永続化

pm2 start npm --name "next" -- start

ちなみに以下のパスから起動すると、ビルド前の非効率なサーバーが永続化されるので要注意。
ページスピードインサイトで構築中のサイトを”startで起動したもの”と"以下のパスで起動したも"の比較すると、ページの送信サイズが10倍ほど違うという結果になりました。(当然重すぎてまともな時間で動きませんでした。)

./node_modules/next/dist/bin/next

Step4

pm2でサーバーが永続化されていることを確認

pm2 list

status がonlineになっていれば完了です。
スクリーンショット 2021-03-02 22.20.45.png

以上で完了!

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

TypeScriptをなんとなく理解する③ ~型を組み合わせる: Generics~

この記事は何?

TypeScriptとは何か、何がいいのかを伝えようと頑張った記事の第3弾です!

今回は、型の使いまわしであるGenericsについて見ていきます。

過去の記事、続編も合わせて御覧ください!
JavaScriptを知っている方がTypeScriptをなんとなく理解するための記事① ~はじめに~
TypeScriptをなんとなく理解する② ~型を組み合わせる: Union~
・ TypeScriptをなんとなく理解する③ ~型を組み合わせる: Generics~ ← この記事
・ TypeScriptをなんとなく理解する④ ~構造型~ (3/3更新予定)

型の組み合わせとは?

TypeScriptでは、型と型を組み合わせて新しく型を作り上げることが出来ます。
この型の組み合わせの方法は2つあります。
1つは、Union、もう1つは、Genericsです。

この記事では、Genericsの概要について説明していきます。

※ Unionについては、前回の記事で紹介しています。

Genericsとは?

TypeScriptにおけるGenerics(ジェネリクス)を学ぶ前に、そもそもGenericsの英単語の意味を確認してみます。

genericsとは
一般的な、ジェネリックの、属の、ノーブランド商品

なんだか、定まっていないというか、汎用的な意味合いを感じます。

実際に、TypeScriptにおける、Genericsも同じような意味です。
TypeScriptにおけるGenericsは、利用されるまで型が確定しない、クラス, 関数, インターフェイスなどなど...を実現するためのものです。

事前にガッチガチに型定義するのもいいですが、汎用的に使える型もほしいです。
そこで、Genericsが活躍します。

もう少しイメージを掴むために、実際にコードを見ていきます。

まず、今までの知識を使って、関数を作ってみます。

const returnStrArray = (value1: string, value2: string):string[] => {
    return [value1, value2];
};

const returnNumberArray = (value1: number, value2: number):number[] => {
    return [value1, value2];
};

const penguinsName = returnStrArray('コウテイペンギン','アデリーペンギン'); // ['コウテイペンギン', 'アデリーペンギン']
const penguinsWeight = returnNumberArray(1000, 50); // [1000, 50]

上のコードは、引数value1, value2を含む配列を返す関数です。
...同じようなことを2回もやるのって冗長じゃないですか...?

そこで、汎用的なことを出来るGenericsを利用します?

const returnArray = <T>(value1: T, value2: T):T[] => {
  return [value1, value2];
}

const penguinsNames = returnArray<string>('コウテイペンギン','アデリーペンギン'); // ['コウテイペンギン', 'アデリーペンギン']
const penguinsWeights = returnArray<number>(1000, 50); // [1000, 50]

Tは何にでも変わる型です。

const penguinsNames = returnArray<string>('コウテイペンギン','アデリーペンギン');

<string>のように指定すると、Tstringであることを示します。なので、以下の型を定義した関数と同義になります。

const returnArray = (value1: string, value2: string):string[] => {
  return [value1, value2];
}

アロー関数でGenericsを利用するときは、<T>()の前につけてあげる必要があるため<T>()のような形になっています。

このように、Genericsを利用することで汎用的で使いまわし可能な型定義をすることが出来ます。

TypeScriptにおけるGenericsは、利用されるまで型が確定しない、クラス, 関数, インターフェイスなどなど...を実現するためのものです。

この伏線を回収することが出来たと思います。
T<string>のように指定するまで型が確定しないことから、

今回の例では、利用するまで型が確定しない関数を実現出来たと思います。

const penguinsNames = returnArray<string>('コウテイペンギン','アデリーペンギン'); // 利用したから型が確定

他にも、

interface funcs<T> {
  add: (obj: T) => void;
  get: () => T;
}

みたいに、独自の型を宣言出来たりもします。

おわりに

できるだけわかりやすく伝えようとしましたが、理解出来そうでしょうか?
この記事だけで理解していただけましたら、是非LGTM✨いただければと思います。

理解できない方がいた場合は...もっとわかりやすく伝えられるよう精進します...!

参考文献

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

TypeScriptをなんとなく理解するための記事③ ~型を組み合わせる: Generics~

この記事は何?

TypeScriptとは何か、何がいいのかを伝えようと頑張った記事の第3弾です!

今回は、型の使いまわしであるGenericsについて見ていきます。

過去の記事、続編も合わせて御覧ください!
JavaScriptを知っている方がTypeScriptをなんとなく理解するための記事① ~はじめに~
TypeScriptをなんとなく理解するための記事② ~型を組み合わせる: Union~
・ TypeScriptをなんとなく理解するための記事③ ~型を組み合わせる: Generics~ ← この記事
・ TypeScriptをなんとなく理解するための記事④ ~構造型~ (3/3更新予定)

型の組み合わせとは?

TypeScriptでは、型と型を組み合わせて新しく型を作り上げることが出来ます。
この型の組み合わせの方法は2つあります。
1つは、Union、もう1つは、Genericsです。

この記事では、Genericsの概要について説明していきます。

※ Unionについては、前回の記事で紹介しています。

Genericsとは?

TypeScriptにおけるGenerics(ジェネリクス)を学ぶ前に、そもそもGenericsの英単語の意味を確認してみます。

genericsとは
一般的な、ジェネリックの、属の、ノーブランド商品

なんだか、定まっていないというか、汎用的な意味合いを感じます。

実際に、TypeScriptにおける、Genericsも同じような意味です。
TypeScriptにおけるGenericsは、利用されるまで型が確定しない、クラス, 関数, インターフェイスなどなど...を実現するためのものです。

事前にガッチガチに型定義するのもいいですが、汎用的に使える型もほしいです。
そこで、Genericsが活躍します。

もう少しイメージを掴むために、実際にコードを見ていきます。

まず、今までの知識を使って、関数を作ってみます。

// 引数value1, value2は文字列型で、返り値は各要素が文字列の配列
const returnStrArray = (value1: string, value2: string):string[] => {
    return [value1, value2];
};
// 引数value1, value2は数値型で、返り値は各要素が数値の配列
const returnNumberArray = (value1: number, value2: number):number[] => {
    return [value1, value2];
};

const penguinsName = returnStrArray('コウテイペンギン','アデリーペンギン'); // ['コウテイペンギン', 'アデリーペンギン']
const penguinsWeight = returnNumberArray(1000, 50); // [1000, 50]

上のコードは、引数value1, value2を含む配列を返す関数です。
...同じようなことを2回もやるのって冗長じゃないですか...?

そこで、汎用的なことを出来るGenericsを利用します?

const returnArray = <T>(value1: T, value2: T):T[] => {
  return [value1, value2];
}

const penguinsNames = returnArray<string>('コウテイペンギン','アデリーペンギン'); // ['コウテイペンギン', 'アデリーペンギン']
const penguinsWeights = returnArray<number>(1000, 50); // [1000, 50]

Tは何にでも変わる型です。

// T → string
const penguinsNames = returnArray<string>('コウテイペンギン','アデリーペンギン');

<string>のように指定すると、Tstringであることを示します。なので、以下の型を定義した関数と同義になります。

const returnArray = (value1: string, value2: string):string[] => {
  return [value1, value2];
}

アロー関数でGenericsを利用するときは、<T>()の前につけてあげる必要があるため<T>()のような形になっています。

このように、Genericsを利用することで汎用的で使いまわし可能な型定義をすることが出来ます。

TypeScriptにおけるGenericsは、利用されるまで型が確定しない、クラス, 関数, インターフェイスなどなど...を実現するためのものです。

この伏線を回収することが出来たと思います。
T<string>のように指定するまで型が確定しないことから、

今回の例では、利用するまで型が確定しない関数を実現出来たと思います。

const penguinsNames = returnArray<string>('コウテイペンギン','アデリーペンギン'); // 利用したから型が確定

他にも、

interface funcs<T> {
  add: (obj: T) => void;
  get: () => T;
}

みたいに、独自の型を宣言出来たりもします。

おわりに

できるだけわかりやすく伝えようとしましたが、理解出来そうでしょうか?
この記事だけで理解していただけましたら、是非LGTM✨いただければと思います。

理解できない方がいた場合は...もっとわかりやすく伝えられるよう精進します...!

参考文献

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

TypeScriptをなんとなく理解するための記事③ ~汎用的な型を作成する: Generics~

この記事は何?

TypeScriptとは何か、何がいいのかを伝えようと頑張った記事の第3弾です!

今回は、型の使いまわしであるGenericsについて見ていきます。

過去の記事、続編も合わせて御覧ください!

  1. JavaScriptを知っている方がTypeScriptをなんとなく理解するための記事① ~はじめに~
  2. TypeScriptをなんとなく理解するための記事② ~型を組み合わせる: Union~
  3. TypeScriptをなんとなく理解するための記事③ ~汎用的な型を作成する: Generics~ ← この記事
  4. TypeScriptをなんとなく理解するための記事④ ~構造型~

TypeScriptがなんとなく理解できたら、是非インストールして、触ってみてください!
→ 【TypeScriptを導入しよう!】エンジニアもすなるTypeScriptといふものを、我もしてみむとてするなり(3/3 作成予定)

型の組み合わせとは?

TypeScriptでは、型と型を組み合わせて新しく型を作り上げることが出来ます。
この型の組み合わせの方法は2つあります。
1つは、Union、もう1つは、Genericsです。

この記事では、Genericsの概要について説明していきます。

※ Unionについては、前回の記事で紹介しています。

Genericsとは?

TypeScriptにおけるGenerics(ジェネリクス)を学ぶ前に、そもそもGenericsの英単語の意味を確認してみます。

genericsとは
一般的な、ジェネリックの、属の、ノーブランド商品

なんだか、定まっていないというか、汎用的な意味合いを感じます。

実際に、TypeScriptにおける、Genericsも同じような意味です。
TypeScriptにおけるGenericsは、利用されるまで型が確定しない、クラス, 関数, インターフェイスなどなど...を実現するためのものです。

事前にガッチガチに型定義するのもいいですが、汎用的に使える型もほしいです。
そこで、Genericsが活躍します。

もう少しイメージを掴むために、実際にコードを見ていきます。

まず、今までの知識を使って、関数を作ってみます。

// 引数value1, value2は文字列型で、返り値は各要素が文字列の配列
const returnStrArray = (value1: string, value2: string):string[] => {
    return [value1, value2];
};
// 引数value1, value2は数値型で、返り値は各要素が数値の配列
const returnNumberArray = (value1: number, value2: number):number[] => {
    return [value1, value2];
};

const penguinsName = returnStrArray('コウテイペンギン','アデリーペンギン'); // ['コウテイペンギン', 'アデリーペンギン']
const penguinsWeight = returnNumberArray(1000, 50); // [1000, 50]

上のコードは、引数value1, value2を含む配列を返す関数です。
...同じようなことを2回もやるのって冗長じゃないですか...?

そこで、汎用的なことを出来るGenericsを利用します?

const returnArray = <T>(value1: T, value2: T):T[] => {
  return [value1, value2];
}

const penguinsNames = returnArray<string>('コウテイペンギン','アデリーペンギン'); // ['コウテイペンギン', 'アデリーペンギン']
const penguinsWeights = returnArray<number>(1000, 50); // [1000, 50]

Tは何にでも変わる型です。

// T → string
const penguinsNames = returnArray<string>('コウテイペンギン','アデリーペンギン');

<string>のように指定すると、Tstringであることを示します。なので、以下の型を定義した関数と同義になります。

const returnArray = (value1: string, value2: string):string[] => {
  return [value1, value2];
}

アロー関数でGenericsを利用するときは、<T>()の前につけてあげる必要があるため<T>()のような形になっています。

このように、Genericsを利用することで汎用的で使いまわし可能な型定義をすることが出来ます。

TypeScriptにおけるGenericsは、利用されるまで型が確定しない、クラス, 関数, インターフェイスなどなど...を実現するためのものです。

この伏線を回収することが出来たと思います。
T<string>のように指定するまで型が確定しないことから、

今回の例では、利用するまで型が確定しない関数を実現出来たと思います。

const penguinsNames = returnArray<string>('コウテイペンギン','アデリーペンギン'); // 利用したから型が確定

他にも、

interface funcs<T> {
  add: (obj: T) => void;
  get: () => T;
}

みたいに、独自の型を宣言出来たりもします。

おわりに

できるだけわかりやすく伝えようとしましたが、理解出来そうでしょうか?
この記事だけで理解していただけましたら、是非LGTM✨いただければと思います。

理解できない方がいた場合は...もっとわかりやすく伝えられるよう精進します...!

Next: TypeScriptをなんとなく理解するための記事④ ~構造型~

参考文献

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

「見える!動きが見える!」JavaScriptで残像カメラを作ってみた

俺にはおまえの動きが見えるぞ

 Webカメラの画像を、JavaScriptで処理して残像をつけてみました。ダンスや機か運動の様子を撮影して、動きを見られるようできたらと考えつくりました。フリーズさせるための「止まれ」ボタンもつけてみました。でも、できがいまいちでした。(-_-)
https://kaihatuiinkai.jp/afterimage/

「動きがブレて見える!」。動きがはっきり見えない。この教材はボツかな?
 それでは、作成ステップを説明します。データの流れとプログラム作動を確認してもらうため、ソースコードを全て貼り付けしました。長くなってしまうことをご了承願います。
 WebカメラをJavaScriptで画像表示させるための方法はこちらを参照願います。
https://qiita.com/mvm43236/items/398e92761440eb006271

残像表示(画像重ね合わせ)

Webカメラの画像を<canvas>に表示します。前の画像と重ね合わせをして、右の<canvas>に表示します。
作動例→ https://kaihatuiinkai.jp/afterimage/camera5.html

camera5.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>残像表示(画像重ね合わせ)</title>
  <style>
  canvas, video{ border: 1px solid gray; }
  </style>
</head>
<body>
<h2>残像表示(画像重ね合わせ)</h2>
video                 canvas1                 canvas2(画像重ね合わせ)<br>
<video id="video" width="300" height="200"></video>
<canvas id="canvas1" width="300" height="200"></canvas>
<canvas id="canvas2" width="300" height="200"></canvas>
<script>
var imageDelay = 10;  // 表示遅延  1秒
window.onload = () => {
  const video  = document.querySelector("#video");
  const canvas1 = document.querySelector("#canvas1");
  const canvas2 = document.querySelector("#canvas2");
  // カメラ設定  //
  const constraints = {
    audio: false,
    video: {
      width: 300,
      height: 200,
      facingMode: "user"   // フロントカメラを利用する
      // facingMode: { exact: "environment" }  // リアカメラを利用する場合
    }
  };
  // カメラを<video>と同期 //
  navigator.mediaDevices.getUserMedia(constraints)
  .then( (stream) => {
    video.srcObject = stream;
    video.onloadedmetadata = (e) => {
      video.play();
    };
  })
  .catch( (err) => {
    console.log(err.name + ": " + err.message);
  });
    // canvas2 の準備
    var picture2 = canvas2.getContext("2d");
    // canvas2に画像を貼り付ける
    picture2.drawImage(video, 0, 0, canvas1.width, canvas1.height);
  // RGBの範囲
  function set255(value){
    if (value > 255) return 255;
    if (value < 0)   return 0;
    return value;
  }
  // インターバル処理
  var intervalID = setInterval(function(){
    // canvas1 の準備
    var picture1 = canvas1.getContext("2d");
    // canvas1に画像を貼り付ける
    picture1.drawImage(video, 0, 0, canvas1.width, canvas1.height);
    // イメージデータの取得
    image1 = picture1.getImageData(0,0,canvas1.width, canvas1.height);
    // canvas2 の準備
    var picture2 = canvas2.getContext("2d");
    // イメージデータの取得
    image2 = picture2.getImageData(0,0,canvas1.width, canvas1.height);
    var afterimage =picture2.createImageData(canvas2.width, canvas2.height);     
    // 画像合成
    var cnt = 0;
    var amount = 128 / 255;
    for (var y = 0; y < canvas1.height; y++) {
      for (var x = 0;x < canvas1.width; x++) {
        afterimage.data[(cnt*4)]   = set255(Math.round((1 - amount) * image1.data[(cnt*4)]   + (amount * image2.data[(cnt*4)])));
        afterimage.data[(cnt*4)+1] = set255(Math.round((1 - amount) * image1.data[(cnt*4)+1] + (amount * image2.data[(cnt*4)+1])));
        afterimage.data[(cnt*4)+2] = set255(Math.round((1 - amount) * image1.data[(cnt*4)+2] + (amount * image2.data[(cnt*4)+2]))); 
        afterimage.data[(cnt*4)+3] = 255; 
        cnt++;
      }
    }    
    // エラー回避処理
    try {
    // canvas2 に変数の画像を貼り付ける
      document.getElementById("canvas2").getContext('2d').putImageData(afterimage, 0, 0);
    }catch(e){
      // エラー時には何もしない
    };
  }, 300);
};
</script>
</body>
</html>

残像表示(差分抽出表示)

Webカメラの画像を<canvas>に表示します。前の画像と比較して、下の<canvas>に差分を表示します。
作動例→ https://kaihatuiinkai.jp/afterimage/camera6.html

 動いた部分が表示されます。

camera6.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>残像表示(差分抽出表示)</title>
  <style>
  canvas, video{ border: 1px solid gray; }
  </style>
</head>
<body>
<h2>残像表示(差分抽出表示)</h2>
video                 canvas1                 canvas2<br>
<video id="video" width="300" height="200"></video>
<canvas id="canvas1" width="300" height="200"></canvas>
<canvas id="canvas2" width="300" height="200"></canvas>
<br>canvas2-canvas1 差分抽出画像<br>
<canvas id="canvas3" width="300" height="200"></canvas>
<script>
var imageDelay = 10;  // 表示遅延  1秒
window.onload = () => {
  const video  = document.querySelector("#video");
  const canvas1 = document.querySelector("#canvas1");
  const canvas2 = document.querySelector("#canvas2");
  const canvas3 = document.querySelector("#canvas3");
  // カメラ設定  //
  const constraints = {
    audio: false,
    video: {
      width: 300,
      height: 200,
      facingMode: "user"   // フロントカメラを利用する
      // facingMode: { exact: "environment" }  // リアカメラを利用する場合
    }
  };
  // カメラを<video>と同期 //
  navigator.mediaDevices.getUserMedia(constraints)
  .then( (stream) => {
    video.srcObject = stream;
    video.onloadedmetadata = (e) => {
      video.play();
    };
  })
  .catch( (err) => {
    console.log(err.name + ": " + err.message);
  });
    // canvas2 の準備
    var picture2 = canvas2.getContext("2d");
    // canvas2に画像を貼り付ける
    picture2.drawImage(video, 0, 0, canvas1.width, canvas1.height);
  // RGBの範囲
  function set255(value){
    if (value > 255) return 255;
    if (value < 0)   return 0;
    return value;
  }
  // インターバル処理
  var intervalID = setInterval(function(){
    // canvas1 の準備
    var picture1 = canvas1.getContext("2d");
    // canvas1に画像を貼り付ける
    picture1.drawImage(video, 0, 0, canvas1.width, canvas1.height);
    // イメージデータの取得
    image1 = picture1.getImageData(0,0,canvas1.width, canvas1.height);
    // canvas2 の準備
    var picture2 = canvas2.getContext("2d");
    // イメージデータの取得
    image2 = picture2.getImageData(0,0,canvas1.width, canvas1.height);
    var afterimage =picture2.createImageData(canvas2.width, canvas2.height);     
    // 画像合成
    var cnt = 0;
    var difference = 15;
    var cValue = 255;
    for (var y = 0; y < canvas1.height; y++) {
      for (var x = 0;x < canvas1.width; x++) {
        if((image1.data[(cnt*4)]-image2.data[(cnt*4)] > difference)||((image1.data[(cnt*4)+1]-image2.data[(cnt*4)+1] > difference))||(image1.data[(cnt*4)+2]-image2.data[(cnt*4)+2] > difference)){
            afterimage.data[(cnt*4)]   = image1.data[(cnt*4)];
            afterimage.data[(cnt*4)+1] = image1.data[(cnt*4)+1];
            afterimage.data[(cnt*4)+2] = image1.data[(cnt*4)+2]; 
        }else{
            afterimage.data[(cnt*4)]   = cValue;
            afterimage.data[(cnt*4)+1] = cValue;
            afterimage.data[(cnt*4)+2] = cValue; 
        };
        afterimage.data[(cnt*4)+3] = 255; 
        cnt++;
      }
    }    
    // エラー回避処理
    try {
    // canvas2 に変数の画像を貼り付ける
      document.getElementById("canvas2").getContext('2d').putImageData(image1, 0, 0);
    // canvas3 の準備
    var picture3 = canvas3.getContext("2d");
    // canvas3 に変数の画像を貼り付ける
    document.getElementById("canvas3").getContext('2d').putImageData(afterimage, 0, 0);
    }catch(e){
      // エラー時には何もしない
    };
  }, 300);
};
</script>
</body>
</html>

残像表示(差分抽出、重ね合わせ)

Webカメラの画像を<canvas>に表示します。前の画像と比較して、下の<canvas>に差分を重ね合わせて表示します。
作動例→ https://kaihatuiinkai.jp/afterimage/camera7.html

 動いた部分が影のようについてきます。
・カメラの前で顔を動かすと、シュールな顔になれます。
・仮○ライ○○の変身シーンの動きをすると、かっこよく見えます。
・カメラに向かって、拳を何度も降り出すと、ペガ○○流○拳、北○百○拳が再現できます。
・・・あれ?教材にならない。

camera7.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>残像表示(差分抽出、重ね合わせ)</title>
  <style>
  canvas, video{ border: 1px solid gray; }
  </style>
</head>
<body>
<h2>残像表示(差分抽出、重ね合わせ)</h2>
video                 canvas1 現在の画像          canvas2 この画像をもとにして重ね合わせ<br>
<video id="video" width="300" height="200"></video>
<canvas id="canvas1" width="300" height="200"></canvas>
<canvas id="canvas2" width="300" height="200"></canvas>
<br>canvas2-canvas1 差分抽出、重ね合わせ画像<br>
<canvas id="canvas3" width="300" height="200"></canvas>
<script>
var imageMax = 30;   // 遅延最大時間 最大3秒
var imageArray = new Array(imageMax); // 画像保存用
var imageNumber = 0;  // 現在の画像番号
var imageDisplay = 0; // 表示番号
var imageDelay = 8;  // 表示遅延  1秒
// 読み込まれたら 作動します
window.onload = () => {
  const video  = document.querySelector("#video");
  const canvas1 = document.querySelector("#canvas1");
  const canvas2 = document.querySelector("#canvas2");
  const canvas3 = document.querySelector("#canvas3");
  // カメラ設定  //
  const constraints = {
    audio: false,
    video: {
      width: 300,
      height: 200,
      facingMode: "user"   // フロントカメラを利用する
      // facingMode: { exact: "environment" }  // リアカメラを利用する場合
    }
  };
  // カメラを<video>と同期 //
  navigator.mediaDevices.getUserMedia(constraints)
  .then( (stream) => {
    video.srcObject = stream;
    video.onloadedmetadata = (e) => {
      video.play();
    };
  })
  .catch( (err) => {
    console.log(err.name + ": " + err.message);
  });
    // canvas3 の準備
    var picture3 = canvas3.getContext("2d");
    // canvas2に画像を貼り付ける
    picture3.drawImage(video, 0, 0, canvas3.width, canvas3.height);
    // イメージデータの取得
    var afterimage =picture3.createImageData(canvas3.width, canvas3.height);
    // エラー回避のため、事前に画像を入れる
    for(var i= 0; i <= imageMax; i++){
      imageArray[i] = picture3.getImageData(0,0,canvas1.width, canvas1.height);
    };
  // インターバル処理
  var intervalID = setInterval(function(){
    // 画像記録番号
    imageNumber = imageNumber + 1;
    if(imageNumber >= imageMax){
      imageNumber =0;
    }
    // 表示番号
    imageDisplay = imageNumber - imageDelay;
    if(imageDisplay <0 ){
      imageDisplay = imageDisplay + imageMax; // マイナスの処理
    }
    // canvas1 の準備
    var picture1 = canvas1.getContext("2d");
    // canvas1に画像を貼り付ける
    picture1.drawImage(video, 0, 0, canvas1.width, canvas1.height);
    // イメージデータの取得
    imageArray[imageNumber] = picture1.getImageData(0,0,canvas1.width, canvas1.height);
    // canvas2 の準備
    var picture2 = canvas2.getContext("2d");
    try {
        // canvas2 に変数の画像を貼り付ける
        document.getElementById("canvas2").getContext('2d').putImageData(imageArray[imageDisplay], 0, 0);
    }catch(e){
        // エラー時には何もしない
    };
    // 画像合成
    imageDisplay = imageNumber - imageDelay;
    if(imageDisplay <0 ){
      imageDisplay = imageDisplay + imageMax; // マイナスの処理
    }
    afterimage = imageArray[imageDisplay];
    for(var i= 1; i <= imageDelay; i++){
      funcComposition(imageArray[setImageNumber(imageNumber - imageDelay + i)],imageArray[imageDisplay]);
    };
    // エラー回避処理
    try {
        // canvas3 の準備
        var picture3 = canvas3.getContext("2d");
        // canvas3 に変数の画像を貼り付ける
        document.getElementById("canvas3").getContext('2d').putImageData(afterimage, 0, 0);
    }catch(e){
        // エラー時には何もしない
    };
  }, 50);
// ImageNumberの範囲
function setImageNumber(value){
    if (value > imageMax) return (value - imageMax);
    if (value < 0)   return (value + imageMax);
    return value;
}
  function funcComposition(image1,image2){  // 画像合成
    var cnt = 0;
    var difference = 4;
    var cValue = 255;
    for (var y = 0; y < canvas1.height; y++) {
      for (var x = 0;x < canvas1.width; x++) {
        if((image1.data[(cnt*4)]-image2.data[(cnt*4)] > difference)||((image1.data[(cnt*4)+1]-image2.data[(cnt*4)+1] > difference))||(image1.data[(cnt*4)+2]-image2.data[(cnt*4)+2] > difference)){
            afterimage.data[(cnt*4)]   = image1.data[(cnt*4)];
            afterimage.data[(cnt*4)+1] = image1.data[(cnt*4)+1];
            afterimage.data[(cnt*4)+2] = image1.data[(cnt*4)+2]; 
        };
        afterimage.data[(cnt*4)+3] = 255; 
        cnt++;
      }
    }
  };
};
</script>
</body>
</html>

 以前、Windows用に画像重ね合わせソフト「多重露光」を作りました。
http://www1.iwate-ed.jp/tantou/joho/material/tajuurokou/index.html
.NETの機能を使って、Windowsネイティブのソフトで処理していたことが、今は、ブラウザで表示できることに進歩を感じます。ハードとソフトの進化ですね。
 1990年代、CPUがV30のPC9801で「気象衛星画像」のビットマップを1ドットごとに処理をして表示する理科の教材を作りました。でも、400×400を終えるのに1分30秒かかり、授業で使うことをあきらめました。→事前に処理して保存しておきました。それを今では、0.05秒で処理してしまう。コンピュータの能力の進化はすばらしいですね。

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

TypeScriptをなんとなく理解する② ~型を組み合わせる: Union~

この記事は何?

TypeScriptとは何か、何がいいのかを伝えようと頑張った記事の第2弾です!

今回は、型を組み合わせて新たな型を生成する、
Unionについて見ていきます。

過去の記事、続編も合わせて御覧ください!
JavaScriptを知っている方がTypeScriptをなんとなく理解するための記事① ~はじめに~
・ TypeScriptをなんとなく理解する② ~型を組み合わせる: Union~ ← この記事
TypeScriptをなんとなく理解する③ ~型を組み合わせる: Generics~
・ TypeScriptをなんとなく理解する④ ~構造型~ (3/3更新予定)

型の組み合わせとは?

TypeScriptでは、型と型を組み合わせて新しく型を作り上げることが出来ます。
この型の組み合わせの方法は2つあります。
1つは、Union、もう1つは、Genericsです。

この記事では、Unionの概要について説明していきます。

Unionによる型の作成

unionの基本

まずは、Unioinを使わずに型を作ってみます。
型はtypeを利用して、以下のように作成します。

type Fish = "sanma";

新しくFish型を作成しました。
このFish型は、文字列(string)のsanmaのみを許容する型です。
そのため、この型を使って以下のように、定数lunchを定義できます。

type Fish = "sanma";
const lunch: Fish = "sanma";

lunchFish型なので、"sanma"のみ許容します。
つまり、ランチにはサンマしか食べることが出来ません。
実際にlunchにサンマ以外を定義しようとすると...

const lunch: Fish = "iwana"; // タイプ '"iwana"'はタイプ '"sanma"'に割り当てることができません。ts(2322)

Fish型のlunchには、"iwana"を定義することが出来ませんでした。
ランチには、サンマもイワナも食べるので、どちらも許容したいです。
そこで、Unionを利用します。Union|を利用して次のように記述します。

type Fish = "sanma" | "iwana";
const lunch: Fish = "iwana";

このように|を利用してFish型に"sanma", "iwana"に絞る事ができます。

ちなみに、"sanma", "iwana"以外は許容されません。

type Fish = "sanma" | "iwana";
const lunch: Fish = "iwana";
const dinner: Fish = "sanma";
const breakfirst: Fish = "saba"; // タイプ '"saba"'はタイプ 'Fish'に割り当てることができません。

もちろん数値も扱うことができます。

type AndroidVersion = 9 | 10 | 11;
const myAndroidOsVersion: AndroidVersion = 11;

ちょっと発展した型の作成

以上では、明確な文字や数値に限定して、型を作成しました。
ここまで厳格にしなくても、「文字列stringはOK!」「文字列の入った配列string[]はOK!」ということも出来ます。

具体的には、以下のような記述方法です。

type Food = string | string[];

const branch: Food = 'cake';
const nightMeal: Food = ['ramen', 'onigiri', 'potechi'];
const theLastSupper: Food = ['ramen', 'onigiri', 1498]; // タイプ「number」はタイプ「string」に割り当てることができません。ts(2322)

レオナルド・ダ・ヴィンチ作の最後の晩餐(theLastSupper)は、1498年に完成しましたが、これは数値であり、文字列でないのでエラーが起きます。

ちなみに、このようなUnionで型を定義した際に便利なのが型のチェックです。

以下のように、型のチェックをすることができます。

タイプ 述語
文字列 typeof s === "string"
数値 typeof n === "number"
ブール値 typeof b === "boolean"
未定義 typeof undefined === "undefined"
関数 typeof f === "function"
配列 Array.isArray(a)

これを使って、型によって条件分岐をすることが出来ます。

const healthCheck = (food: Food): string => {
  if (typeof food === "string") {
    return `it's OK`;
  } else {
    return `it's Bad`;
  }
};

console.log(healthCheck(["ramen", "onigiri", "potechi"])); // it's Bad

おわりに

今回はUnionを使って、型を結合してみました!
型を組み合わせて、自分だけのユニークな型を作ってみましょう!

「そういえば、前回、Interfaceが出てきたけどtypeと何が違うの...?」と思った方もいるかもしれません。今後、この違いに関しても書く予定ではあります。が、既に記事があるので一旦こちらにご案内します...! → TypeScript の Interface と Type Alias の違い

参考文献

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

TypeScriptをなんとなく理解するための記事② ~型を組み合わせる: Union~

この記事は何?

TypeScriptとは何か、何がいいのかを伝えようと頑張った記事の第2弾です!

今回は、型を組み合わせて新たな型を生成する、
Unionについて説明していきます。

過去の記事、続編も合わせて御覧ください!

  1. JavaScriptを知っている方がTypeScriptをなんとなく理解するための記事① ~はじめに~
  2. TypeScriptをなんとなく理解するための記事② ~型を組み合わせる: Union~ ← この記事
  3. TypeScriptをなんとなく理解するための記事③ ~汎用的な型を作成する: Generics~
  4. TypeScriptをなんとなく理解するための記事④ ~構造型~

TypeScriptがなんとなく理解できたら、是非インストールして、触ってみてください!
→ 【TypeScriptを導入しよう!】エンジニアもすなるTypeScriptといふものを、我もしてみむとてするなり(3/3 作成予定)

型の組み合わせとは?

TypeScriptでは、型と型を組み合わせて新しく型を作り上げることが出来ます。
この型の組み合わせの方法は2つあります。
1つは、Union、もう1つは、Genericsです。

この記事では、Unionの概要について説明していきます。

※Genericsについては次回の記事で説明しています。

Unionによる型の作成

unionの基本

まずは、Unioinを使わずに型を作ってみます。
型はtypeを利用して、以下のように作成します。

type Fish = "sanma";

新しくFish型を作成しました。
このFish型は、文字列(string)のsanmaのみを許容する型です。
そのため、この型を使って以下のように、定数lunchを定義できます。

type Fish = "sanma";
const lunch: Fish = "sanma";

lunchFish型なので、"sanma"のみ許容します。
つまり、ランチにはサンマしか食べることが出来ません。
実際にlunchにサンマ以外を定義しようとすると...

const lunch: Fish = "iwana"; // タイプ '"iwana"'はタイプ '"sanma"'に割り当てることができません。ts(2322)

Fish型のlunchには、"iwana"を定義することが出来ませんでした。
ランチには、サンマもイワナも食べるので、どちらも許容したいです。
そこで、Unionを利用します。Union|を利用して次のように記述します。

type Fish = "sanma" | "iwana";
const lunch: Fish = "iwana";

このように|を利用してFish型に"sanma", "iwana"に絞る事ができます。

ちなみに、"sanma", "iwana"以外は許容されません。

type Fish = "sanma" | "iwana";
const lunch: Fish = "iwana";
const dinner: Fish = "sanma";
const breakfirst: Fish = "saba"; // タイプ '"saba"'はタイプ 'Fish'に割り当てることができません。

もちろん数値も扱うことができます。

type AndroidVersion = 9 | 10 | 11;
const myAndroidOsVersion: AndroidVersion = 11;

ちょっと発展した型の作成

以上では、明確な文字や数値に限定して、型を作成しました。
ここまで厳格にしなくても、「文字列stringはOK!」「文字列の入った配列string[]はOK!」ということも出来ます。

具体的には、以下のような記述方法です。

type Food = string | string[];

const branch: Food = 'cake';
const nightMeal: Food = ['ramen', 'onigiri', 'potechi'];
const theLastSupper: Food = ['ramen', 'onigiri', 1498]; // タイプ「number」はタイプ「string」に割り当てることができません。ts(2322)

レオナルド・ダ・ヴィンチ作の最後の晩餐(theLastSupper)は、1498年に完成しましたが、これは数値であり、文字列でないのでエラーが起きます。

ちなみに、このようなUnionで型を定義した際に便利なのが型のチェックです。

以下のように、型のチェックをすることができます。

タイプ 述語
文字列 typeof s === "string"
数値 typeof n === "number"
ブール値 typeof b === "boolean"
未定義 typeof undefined === "undefined"
関数 typeof f === "function"
配列 Array.isArray(a)

これを使って、型によって条件分岐をすることが出来ます。

const healthCheck = (food: Food): string => {
  if (typeof food === "string") {
    return `it's OK`;
  } else {
    return `it's Bad`;
  }
};

console.log(healthCheck(["ramen", "onigiri", "potechi"])); // it's Bad

おわりに

今回はUnionを使って、型を結合してみました!
型を組み合わせて、自分だけのユニークな型を作ってみましょう!

「そういえば、前回、Interfaceが出てきたけどtypeと何が違うの...?」と思った方もいるかもしれません。今後、この違いに関しても書く予定ではあります。が、既に記事があるので一旦こちらにご案内します...! → TypeScript の Interface と Type Alias の違い

Next: TypeScriptをなんとなく理解するための記事③ ~汎用的な型を作成する: Generics~

参考文献

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

JavaScript

スキャナと同じ操作をjavascriptで行う方法

KeyboardEvent
キーボード入力と同じイベント発行できるメソッド

dispathEvent
あたかもイベント発行したかのように振る舞うメソッド

refs
https://ameblo.jp/personwritep/entry-12456996738.html
https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget-dispatchEvent

WebAPI

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

JS配列簡単生成

[…Array(20)].map(() =>{
    return {text:’’,point:’’}
})

のように書くと配列を20個生成できる

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

【初学者向け】WAI-ARIAを使ってモーダルウィンドウを作る

今まで私はjQueryでモーダルウィンドウを実装していたのですが、最近jQueryを使わないことが増えたのでJavaScriptで実装した際の備忘録です。

使用するWAI-ARIAの属性

aria-controls

指定した要素が値に指定した要素を制御することを示すWAI-ARIAの属性です。
(※HTMLリファレンスから引用)

今回の場合は、モーダルウィンドウを開閉するボタンに使用します。
対応するモーダルウィンドウのidと同じ文字列を、開閉するボタンのaria-controlsの値に設定することで、「このボタンとこのモーダルウィンドウが対応している」という設定をします。

aria-expanded

要素の開閉の状態を示すためのWAI-ARIAの属性です。
(※HTMLリファレンスから引用)

こちらも今回の場合はモーダルウィンドウを開閉するボタンに使用します。
対応する要素が閉じている場合はfalse、開いている場合はtrueを設定します。
例えば、モーダルウィンドウを開くボタンを押下するときは、対応するモーダルウィンドウは閉じているはずです。
そのため、モーダルを開くボタンにはaria-expanded="false"、モーダルを閉じるボタンにはaria-expanded="true"を設定します。

aria-hidden

ユーザーエージェントに認識させたくない要素に指定するためのWAI-ARIA属性です。
(※HTMLリファレンスから引用)

display:none;visibility:hidden;で隠れている要素に指定する属性です。
今回の場合、モーダルウィンドウは最初非表示になっているので、モーダルウィンドウの大枠の要素に
aria-hidden="true"を設定します。

HTMLの準備

modal.html
<!-- モーダルウィンドウ -->
<div class="p-modal" id="modal_01" aria-hidden="true">
  <div class="p-modal__wrap">
   <!-- ↓モーダルを閉じるボタン↓ -->
     <button class="js-modalClose" aria-controls="modal_01" aria-expanded="true"></button>
      <div class="p-modal__contents">
        モーダルの中身
      </div>
   </div>
</div>

モーダルウィンドウ側のHTMLには、まず大枠の要素にidを付与します。
開いたモーダルウィンドウ内に、そのモーダルウィンドウを閉じるボタンを設置する場合は、
js-modalCloseといった閉じる用のクラスを付与し、aria-controlsには閉じる対象のモーダルウィンドウに付与されているidと同じものを記述します。

index.html
<!-- モーダルを開くためのボタン -->
<button class="js-modalOpen" aria-controls="modal_01" aria-expanded="false"></button>

モーダルウィンドウを開くボタンには、js-modalOpenといったクラスを付与し、
aria-controlsには開く対象となるモーダルウィンドウに付与されているidと同じものを記述します。

CSSで整形

デザイン面は除き、モーダルウィンドウの表示非表示に関わる部分だけ記載します。

style.scss
.p-modal {
  &[aria-hidden="true"] {
    visibility: hidden;
    opacity: 0;
    transition: .4s;
  }
  &[aria-hidden="false"] {
    visibility: visible;
    opacity: 1;
    transition: .4s;
  }
}

aria-hidden属性がtrue(要するに非表示)の場合はvisibility: hidden;で隠しています。
display: none;でもいいのですが、transitionが効かなかったりするので今回はvisibilityを使用しました。
反対にaria-hidden属性がfalse(要するに表示)の場合はvisibility: visible;で表示させ、
opacitytransitionでフェードインするような表示の仕方をさせています。

実際にJavaScriptで表示非表示を制御してみる

さて、ここからが本題です。
細かい説明は、各所にコメントアウトで記載しています。

modal.js
function modalWindow() {

  // ここで、HTML上にあるモーダル開閉用のクラスを全て取得
  const modalOpenBtn = document.querySelectorAll('.js-modalOpen');
  const modalCloseBtn = document.querySelectorAll('.js-modalClose');

  // 取得したモーダルを開くボタン一個一個に対しforEachで処理
  modalOpenBtn.forEach((elm) => {
    // ボタンがクリックされたら
    elm.addEventListener('click', () => {
      // ボタンに付与されているaria-controlsの値を取得し、それと同じidが付与されているモーダルウィンドウを取得する
      let targetId = elm.getAttribute('aria-controls');
      let target = document.getElementById(targetId);

      // モーダルが非表示だった場合
      if (target.getAttribute('aria-hidden') === 'true') {
        // ボタンのaria-expanded属性を変更し、対応するモーダルが開いたと設定する
        elm.setAttribute('aria-expanded', 'true');
        // モーダルのaria-hidden属性を変更し、モーダルが開いたと設定する
        target.setAttribute('aria-hidden', 'false');
        // 現在のスクロール位置を取得した後、bodyを固定させる
        const scrollY = window.scrollY;
        document.body.style.position = 'fixed';
        document.body.style.top = -scrollY + 'px';
        document.body.style.left = '0';
        document.body.style.right = '0';
      }
    });
  });

  // 取得したモーダルを閉じるボタン一個一個に対しforEachで処理
  modalCloseBtn.forEach((elm) => {
    // ボタンがクリックされたら
    elm.addEventListener('click', () => {
      // ボタンに付与されているaria-controlsの値を取得し、それと同じidが付与されているモーダルウィンドウを取得する
      let targetId = elm.getAttribute('aria-controls');
      let target = document.getElementById(targetId);

      // モーダルが表示されている場合
      if (target.getAttribute('aria-hidden') === 'false') {
        // 開くボタンクリック時に設定されたbodyのtopを取得し、bodyの固定を解除
        const scrollY = document.body.style.top;
        document.body.style.position = '';
        document.body.style.top = '';
        document.body.style.left = '';
        document.body.style.right = '';
        // スクロール位置をモーダルを開いた時と同じ位置に戻す
        window.scrollTo(0, parseInt(scrollY || '0') * -1);
        // ボタンのaria-expanded属性を変更し、対応するモーダルが閉じたと設定する
        elm.setAttribute('aria-expanded', 'false');
        // モーダルのaria-hidden属性を変更し、モーダルが閉じたと設定する
        target.setAttribute('aria-hidden', 'true');
      }
    });
  });
};

modalWindow();

固定する背景に色を付けたいとか、固定背景クリックでモーダルを閉じるようにしたい場合もあると思いますが、それに関してはまたの機会に…

さいごに

こういった構成にすることで、モーダルが何個に増えても簡単に対応でき、後の作業が楽になったかなと思います。
慣れたらもっとスマートなコードが書けるようになると思うので、その際はまた加筆修正行いたいと思います。

読んでいただきありがとうございました!

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

divをクリックしてタイムピッカーを起動させたい

type="time"

<input type="time">

input要素のtype属性にtimeを指定することで、入力される値は時間の形式になる。
このインプットをクリックするとタイムピッカーが起動する。
step01_pc.jpg

Androidだとこんな感じ。
step01_mobile_1.jpg

いろんなとこでタイムピッカーを使いたい

Windowsはさておき、Androidの方は直感的に操作できてけっこう便利。
という訳で<div>とか、なにかしら要素をクリックしたときにタイムピッカーが起動して時刻が入力できるようにしたい。
もちろんJavaScriptで値も取得したい。

作りたいもの

今回は<div>で作ったボタンをクリックするとタイムピッカーが起動して、入力した時刻がボタンに表示される。っていうのをつくる。
機能としては<input>と変わらないけどデザインの自由度は<input>よりも大きいはず。

作成方法

jQueryとかは別として、どうやらタイムピッカーを<input type="time">以外で起動させる方法はなさそう。
最初は、何か要素をクリックしたらJavaScriptで強制的に<input>をクリックさせてタイムピッカーを起動させようとしたもののWindowsの方で失敗。Androidの場合は<input>をタップするとタイムピッカーが起動するけど、Windowsだと時計のアイコンをクリックしないといけないので、そこで躓いた。
いろいろ調べてみると、<input>の中のアイコンは操作できるらしいので、<input>をボタン内部いっぱいにして透明にして隠しておく方針に変更。
アイコンの操作は以下二つのサイトを参考にした。

https://blog.tmyt.jp/entry/2020/06/24/012442
https://itokoba.com/archives/301

ボタンを作成

ボタンとして<div>を設置。<input>は中に入れて見えないように透明に。CSSはボタンぽくサクッと整える。

html
<div id="button">
 <input type="time">
</div>
css
#button {
 width: 400px;
 height: 100px;
 border-radius: 20px;
 background-color: #2dbed5;
 box-shadow: 0 5px 10px #09262a;
}

/*クリック時の動き*/
#button:active {
 box-shadow: none;
 background-color: #2397aa;
 top: 5px;
}

input {
 height: 100%;
 width: 100%;
 border: none;
 opacity: 0;
}

/*どこをクリックしてもタイムピッカーが起動するようにする*/
input[type="time"]::-webkit-calendar-picker-indicator {
 margin: 0;
 padding: 0;
 height: 100%;
 width: 100%;
}

時刻を表示する

ボタンに時刻を表示するために<span>を追加。
JavaScriptで値を受け取って表示させれば完成。
このとき、ボタンや<input>にもCSSを加えて、要素の重なりを指定しておかないと<input>より<span>が上になってしまい、<input>がクリックできなくなるので注意。
以下のサイトを参考にした。

https://www.codegrid.net/articles/z-index-1

html
<div id="button">
 <input type="time">
 <span>00:00</span> <!-- 追加 -->
</div>
css
/*cssに追加*/

#button {
 position: relative;
 z-index: 1;
}

input {
 position: absolute;
 z-index: 2;
}

span {
 font-size: 80px;
 user-select: none;
 position: absolute;
 z-index: 1;
 top: 50%;
 left: 50%;
 transform: translateY(-50%) translateX(-50%);
 -webkit-transform: translateY(-50%) translateX(-50%);
}
js
let input = document.querySelector('input');
let time = document.querySelector('span');
input.addEventListener("input",  () => time.innerHTML = input.value, false);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryで上からslideToggleでメッセージを出す

はじめに

slideToggleを使ってニュルっとメッセージを出す方法の記録です。

jQueryのコード

以下のコードでメッセージの出す、戻すを行う。
表示するメッセージを受け取った場合は空白を全て取り除く。
そしてslideToggleで表示し、5秒後にslideToggleで戻す。

let $slideMsg = $('#js-slide-msg');
      let msg = $slideMsg.text();
      if(msg.replace(/\s+/g, "")){
        $jsShowMsg.slideToggle().show();
        setTimeout(function(){
          $jsShowMsg.slideToggle().show();
        }, 5000);
      }

表示について

$msgに表示するメッセージが入っていれば表示させる。

<div id="js-slide-msg">
    <?php if(!empty($msg) echo $msg; ?>
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

intra-mart を利用したjsp画面でClandar付入力項目のonChange動作

個人用の技術メモ

そもそもこれを書く理由は、im:calendarと関連する入力項目にだけonChangeをかけると、calendarによる入力時は動いてくれないので、それの対応策。

まず、入力項目のonChange動作は通常通り実装(一応手入力もできるので)

   //IE11前のIEバージョン
   if(window.attachEvent){
     document.getElementById("id").attachEvent( 'onchange', function(){//処理内容function})
   } else {
   //IE11,mozilla系(firefox,chromeなど)ブラウザ
     document.getElementById("id").addEventListener( 'change', function(){//処理内容function})
   }

で、im:calendarの属性onSelectを利用して、同じ内容を実装する

   //IE11前のIEバージョン
   <im:calendar onSelect = "//処理内容function"/>

めんどくさく感じますが、ほかのいい方法思いつかないので、これで。intra-martさん提供してる情報あまり詳細がないので困るわ。

因みに、calendarで日付を選択する瞬間、フォーカスアウト動作になるので、通常のフォーカスアウト動作を実装した場合、思うように動かないでしょう。

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

【JavaScript】submitボタンをonClick()でdisabledするとonSubmit()が実行されない件【二重送信防止】

問題

どうも。みるみっとです。
フロントエンドエンジニアをやっています。

業務中にお問い合わせフォームを作っていたところ、
「できれば二重防止送信機能を作って欲しい」との要望が。

単純に「送信ボタンをクリックした後にdisabledするかぁー」と思っていたのですが、
どうやらsubmitボタンのclickイベントでdisabledにするとonSubmitが実行されないということが分かりました。

※ちなみにonSubmit内でdisabledにしてもダメでした。

別にonSubmitまでdisabledしなくても良いのに...(´ω`)

動作環境

OS: macOs 10.15.6
ブラウザ: Google Chrome ver88.0

解決方法

disabledではなく、CSSの「pointer-events:none」を使うと上手くいきました。

pointer-events:noneにすることで、ポインターイベントが無効されます。
つまり、その要素のクリック・タッチが効かなくなります
ユーザーにとっては実質disabledです。

form.html
<form name="myForm" action="" method="get" onSubmit="return submitForm()">
    <p class="title">メッセージ</p>
    <textarea id="message"></textarea>

    <input type="submit" id="submit_pointer" value="送信" onClick="buttonDisabled()">
</form>
form.js
//submitボタンのクリックイベント
function buttonDisabled(){
    //pointer-eventsをnoneに
    $('#submit_pointer').css("pointer-events", "none");

    //disabledしてるっぽい色に変更
    $('#submit_pointer').css("background", "#636363");
}

//フォームのsubmitイベント
function submitForm(){
    return true;
}

実装例

下記に、
①二重送信防止していない送信ボタン
②送信後にdisabledする送信ボタン
③送信後にpointer-events:noneする送信ボタン

の3パターンを実装した礼を載せておきます。

disabledボタンはフォームが送信されず、
pointer-eventsボタンだとフォームが送信されることが分かると思います。


See the Pen
submitボタンをdisabledするとsubmitできない件
by mykz (@mykzreas)
on CodePen.


※フォームの送信に成功すると(onSubmitが動作すると)アラートが表示されるようにしています。
※ページ遷移を防ぐためonSubmitはreturn falseしています。

終わりに

disabledするとonSubmitが動作しないのはいまいちピンと来ないですが、pointer-events:noneの方が動作的のも軽そうですね。

視覚的にdisabledっぽいボタンの色に変更するのもUX的にも良さそうです。

参考

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

【Django】JavaScriptで動的に作成されたformにcsrf_tokenを追加する

概要

htmlファイル内で簡単な処理を行った後、formを経由してviews側に情報を返す方法に3日ほど詰まったので、備忘録代わりに書き残しておきます。

参考サイト様

手法

formタグを利用した取得が基本となり、ここは公式のチュートリアルにも記載があります。(参照: はじめての Django アプリ作成、その 4)

ただ、単純にJavaScriptでform要素を追加してsubmitしようとすると、「csrf_tokenないよ」みたいなエラーを吐きます。そこで、csrf_tokenを手動で追加する必要があります。以下サンプルです。

htmlファイル
<!--関数起動用のボタン-->
<input type="button" value="集計" onclick="aggregate()">
function aggregate() {
    //form作成、メソッドとリンク先の追加
    let form = document.createElement('form');
    form.method = "POST";
    form.action = "{% url 'myapp:feedback' %}";
    form.id = "aggregated-form";

    //送信したい情報を格納したinput要素作成、type,valueの追加など
    let input_container = document.createElement('input');
    input_container.type = 'hidden'; //入力フォームの非表示
    input_container.name = 'aggregated-information';

    //何かしらの処理を施した情報
    let information_to_post = .....
    //情報の追加
    input_container.value = information_to_post;

    //csrf_tokenの追加手動
    let csrf_element = document.createElement('input');
    csrf_element.type = 'hidden';
    csrf_element.name = 'csrfmiddlewaretoken';
    csrf_element.value = '{{ csrf_token }}';

    //csrf_tokenと送信したい情報の追加
    form.appendChild(csrf_element);
    form.appendChild(input_container);

    //文書への追加
    document.body.appendChild(form);

    //送信
    form.submit();
}

あとはurls.pyでfeedbackとviews.pyの紐付けを行い、返ってきた情報をrequest.POSTで取得すれば利用できます。

まとめ

csrf_tokenの正体がinput要素であると知ることができました。JSONとして受け取るなどの工夫を行えばもっと使い勝手が良さそうです。集合知に感謝。

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

railsアプリのデプロイエラーの原因が、gemのuglifierのバージョンに依るものだった話

railsアプリのデプロイがこけたので、その原因を追求してみた。

デプロイ時のエラーメッセージを読むと、以下の様なメッセージが頻出していた。

Caused by:
V8::Error: SyntaxError: Unexpected token: name (jsの変数名)
at js_error (<eval>:3623:12167)
at croak (<eval>:3623:22038)
at token_error (<eval>:3623:22175)
at unexpected (<eval>:3623:22263)
at semicolon (<eval>:3623:22781)
at simple_statement (<eval>:3623:25959)
at <eval>:3623:23747
at <eval>:3623:22954
at block_ (<eval>:3623:28083)
at ctor.body (<eval>:3623:27686)
at function_ (<eval>:3623:27782)
at <eval>:3623:24469
at <eval>:3623:22954
at block_ (<eval>:3623:28083)
at <eval>:3623:23857
at <eval>:3623:22954
at <eval>:3624:3759
at parse (<eval>:3624:3999)
at parse (<eval>:3958:22)
at uglifier (<eval>:4003:13)

下記の記事で、jsでletを使用している場合varに変更すると良い、というアドバイスがあったので、変更してみた結果、デプロイ成功。
どうやら、gemのuglifierのバージョンが足りず、ES6をサポートしていなかったため、letという新しい書き方を許容していなかったため、エラーが起きたらしい。
https://stackoverflow.com/questions/39221152/rails-5-heroku-deploy-error-execjsprogramerror-syntaxerror-unexpected-token

尚、ES6の書き方を許容するのはUglifier 3.2.0以降からだそう。

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

【JavaScript 関数】substrメソッドの使い方

substr()

String.prototype.substr()

文字列の一部を、指定した位置から後方向指定した文字数だけ返す

//公式
str.substr(start[, length])

//サイト記事
var str = 文字列

str.substr( 開始位置, 文字数 )

これが元になって調査

watch: {
    // 入力された文字列を監視する
    inputText: function() {
        var pos = this.inputText.indexOf(this.forbiddenText); // indexOf:指定された値が最初に現れたインデックスを返す
        if (pos >= 0) {
            alert(this.forbiddenText + "は、入力できません。");
            // 入力文字列から禁止文字を削除する
            this.inputText = this.inputText.substr(0, pos); //str.substr( 開始位置, 文字数(indexOfででたインデックス) )
        }
    }
}

参考

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

[js]繰り返し処理いろいろ

メモ帳に書いてたやつをQiitaに 其の1

  • メモってるのをわりとそのまま
  • 「○」とか「□」とかの記号部分は自分にとって分かりやすいメモの取り方だったのでそれはそのままにしてるのであしからず
    • 好き勝手に付けていい=変更可能なやつのこと ※変数のことや
    • code関連は一言一句ちがったらいかんやつもあるので、個人的にこういう書き方が分かりやすかった

for文:
    for ( let ◯=0; ◯< □; ◯++){ /*処理*/ }
         ↑超一般的なあれ     

    for ( let ◯ of □){ /*処理*/ }
         ↑配列に使う

    for ( let ◯ in △){ /*処理*/ }
         ↑オブジェクトに使う       

while文:while、do while:◎実行速度早い ⚠︎無限roopに陥りがち要注意

forEachとか:forEach 配列の先頭から順番に要素が渡されて実行される


各構文の簡単なまとめ

for文:一般的に繰り返し回数の指定に使われる@超一般的なあれ
    →繰り返し回数が決まっている場合はfor文
    →途中でループを抜けることが想定されるのであればfor文
for-of文:配列やMapや文字列使える、比較的新しい構文 ECMAScript2015
    →イテラブルなオブジェクトに対するループ
for-in文:オブジェクトに使える、比較的新しい(以下同文)
while文:配列や値に条件を指定して処理を行いたい時
    →繰り返しに配列や値に条件を指定したい場合はwhile文
    →処理速度は基本的に最速≒パフォーマンス重視ならこれ
    └→【!!】for文とwhile文は誤差程度の違いという計測結果・私見のものもある
forEach:配列の要素のみ
    →関数コストが高いためたの繰り返し文と比較すると遅い
ループ処理を抜ける方法:<使用可:for文,while文>
  continue; //処理を中断して条件式に戻る/スキップする
  break; //処理中の文から抜けて次の文へ移行. 繰り返し終了 @for-inなど


for文書き方 速度について
JavaScriptのループはどれが一番高速なのか - Qiita
(forEachが比較対象に入ってない、while文も比較対象に入ってない)

最速結論これ

for (let i=0; i<●; i=(i+1)|0){ /*処理 */ }

i++i = i+1
    すべてのブラウザで、i = i+1 / i = i-1が最速
    最速コードを++/--にしたコードは、約3倍遅い
|0 で型を認識
    すべてのブラウザで、|0でtypedを施したコードが最速
    使わない場合2〜3倍の差


for文

  ↓初期値; ↓条件式; ↓増減式
for ( let =0; < □; ++){ //□は配列名
/* 処理 */
}
 ↓初期値; ↓条件式; ↓増減式 (最速結論)
for (let =0; < □; =(i+1)|0){ //□は配列名
/* 処理 */
}

for(let i=△; i—){/*処理*/}という書き方もある


while文

const i=0; //←while外に条件式に使う実行回数の初期値を書かなアカン
while ( i <10){ //←この例は 1〜10の繰り返し処理
    /* 実行文 */
}
  • 条件式がtrueである限り反復処理する
    • └繰り返し回数が決まっていないループ処理に最適
  • 最初から条件式がfalseなら何も実行せず終わる
  • ⚠︎条件式に直接trueを記述すると無限ループに!ダメ絶対ダメ
  • 実行速度が速いといわれてるが誤差範囲という見方もあり
//while : 1〜10の繰り返し処理
var i=0;
while ( i <10) {
    console.log(i);
    i++; 
    //or以下のような書き方
    //conlole.log(i++);
}

//while : coutinueを使って処理をスキップ(条件式に戻る)
var i=0;   ┏━━━━━━━━━━━━━━━━┓
           ↓                ┃
while ( i <10 ) {           ┃ ⚠︎if文で偶数の時だけcontinue
    i++;                    ┃  以降の処理を中断し条件式に戻る
    if(i %2===0) continue; ━┛
    conlole.log(i++); //←結果的に奇数だけがconsole.logに表示
}

//while : breakを使って繰り返し処理終了
var i=0; 
while ( i <10 ) {
    i++; 
    if(i %2===0) break;  ┓
    conlole.log(i++);   ┃//←結果的に最初の「1」だけ表示
}  ┏━━━━━━━━━━━━━━━━━━━━━┛ ⚠︎if文で偶数の時だけbreak
   ↓      ⚠︎while文を途中で終了(繰り返し処理終了)、 次のコードに
//次のコード
//while - 配列の繰り返し処理
var arr=[‘a’,b’’,’c’], i=0, len=arr.length; //←配列, iとは, lenとは;
while(i<len){
    console.log(arr[i++]);
}

//while - イテレータの繰り返し処理
var mapIterator =new Map([[0,’a’],[1,’b’],[2,’c’]]).entries();
var iteratorResult;
while (iteratorResult =mapIterator.next(), !iteratorResult.done){
    console.log(eteratorResult.value);
}

for-of文

配列やMapで使う処理

for (let ● of □){ //□は配列名かMap
    /*処理*/
} 
  • 「for…of」は主に配列やMapで使える.「values」(=値そのもの) をとってくる
  • 指定された配列の中身(各要素)を取り出しながら繰り返し処理
  • ⚠︎順番通りではないので注意が必要
  • 配列,文字列,マップ(Map),DOMなど
  • イテラブルなオブジェクトに対するループ(オブジェクトには利用できない)
  • 比較的新しい構文 ECMAScript2015
//例1
let 〇〇 =[10,20,30];
for(let e of 〇〇){
    console.log(e); //結果 10,20,30 ←valuesを取得
} 
//例2
var arr=[0,1,2,3,4];
for(var val of arr){
      console.log(val); //
}
console.log(Array.from(arr)); //配列として出力

for-in文

オブジェクトで使う処理

for (let ● in □){ //□は配列名
    /*処理*/
} 
  • 「for…in」は主にオブジェクトで使う.「プロパティ」 をとってくる
  • 指定された配列のプロパティを順に取り出しながら繰り返し処理
  • オブジェクト=名前と値で管理する
  • オブジェクトに含まれるプロパティの数だけ繰り返す=条件式や変化式が不要
  • 比較的新しい構文 ECMAScript2015

forEach文

□.forEach( v =>    /*1行なら*/ ); //アロー関数, 1行, {(大カッコ)}不要
□.forEach( v => {   /*処理*/ }); //アロー関数, 1行で
□.forEach( (v,i,a) => {//アロー関数で
    /*処理*/
});
□.forEach( function(val,idx,arr){ //functionで
    /*処理*/
});
  • 配列の繰り返し処理にだけ使える構文
  • 配列の要素としてオブジェクトを格納しているような配列操作も可
  • breakやcontinueは使えない
  • 類似メソッド:
    • map:返り値がある=新たな配列として作成可
    • forEach:返り値がない=新たな配列としては作れない
      • 外側に予め空配列を作ってpushするなら可(map使ったらよか)

do while文

do{/*実行文*/}while(条件式);
  • 最初から条件を満たさない場合falseでも初回の実行文が処理される
  • ⚠︎条件式に直接trueを記述すると無限ループに!ダメ絶対ダメ
//例
const x=1000;
do{
  /*実行文*/
  console.log(x); //
} while (x < 1000);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

addEventListenerが追加したDOM要素等の影響でうまく設定ができない時の対応

今回の経緯

今回fullcalendarというカレンダーライブラリーで日付部分にボタンを追加、そのボタンにクリックイベントを仕込む、という実装が必要であったが、ライブラリーが吐き出したDomに対して直接イベントを設定する必要があり少し変わった実装方法をしたのでそのメモ

      const ele = document.querySelector('.fc-scroller-harness .fc-scroller .fc-col-header tbody');
      ele.addEventListener('click', (e) => {
        const x = e.target;
        if (Array.from(document.querySelectorAll('.label-btn')).includes(x)) {
          // .label-btn をクリックした時にイベントクリックイベントが発火する
        }
      });

これ結構便利です!!たまにこのようにして使いたいパターンが出てくるので忘れないようにこちらメモとして残しておこう。

おしまい

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

ScriptAutoRunner を使ってインプレスの恥ずかしい記事リンクを消しました

素晴らしい記事がありました。

Amazonの商品ページから、「人気のインディーズマンガ」を消したい(chrome) - Qiita
https://qiita.com/iteyan/items/78bcad46ae43616eeb0f

コメント欄に書きましたが
Chrome の Adblock Plus という拡張つかって、とりあえず画像だけを消していました。

が、画像しか選択できなくて不便でしたが、この仕組をつかえば記事タイトルもまるごと消せますね。

ということで、以前から気になっていた
窓の杜とか、PCWatchの肌色成分が激しい記事リンクを消してみました。
記事タイトルの一部文字で消すことができます。

const pcWatch = () => {
const excludeTextArray = [
  'バウヒュッテ',
  'シリコンで人肌の',
  'ノーフューチャー',
  'ウィザードリィスキーマ',
  'ナイトピットウォーク',
  '人間の肌のような',
  '攻殻機動隊の実写版',
  'グラビア',
  'あの太ももが',
  '恥じらい顔',
  '恋人たち',
  'この綾波、大人だ',
  '写真のキモチ',
];
excludeTextArray.forEach((excludeText) => {
  targetTitles = document.querySelectorAll('span');
  targetTitles.forEach((targetTitle) => {
    const title = targetTitle.getAttribute('title');
    if (title) {
      // console.log('ScriptAutoRunner', title)
      if (title.includes(excludeText)) {
        console.log('ScriptAutoRunner', 'Hit', title)
        targetTitle.parentNode.remove();
      }
    }

  });
});
};

setTimeout(pcWatch, 5000);
setTimeout(pcWatch, 10000);
setTimeout(pcWatch, 15000);
setTimeout(pcWatch, 20000);
setTimeout(pcWatch, 25000);
setTimeout(pcWatch, 30000);
setTimeout(pcWatch, 35000);
setTimeout(pcWatch, 40000);
setTimeout(pcWatch, 45000);
setTimeout(pcWatch, 50000);
setTimeout(pcWatch, 55000);
setTimeout(pcWatch, 60000);

とりあえず動くの対応で、パフォーマンスは後回しです。

これ、インプレス系のページだけで動くようにしたいですが、やり方わかんなかったです。

誰か組み方を洗練させていってみてください。

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

ScriptAutoRunner を使ってインプレスの肌色成分多め記事リンクを消しました

素晴らしい記事がありました。

Amazonの商品ページから、「人気のインディーズマンガ」を消したい(chrome) - Qiita
https://qiita.com/iteyan/items/78bcad46ae43616eeb0f

コメント欄に書きましたが、いくつかのサイトで肌色成分多め画像は、Chrome の Adblock Plus という拡張つかって、とりあえず目立つものを個別に画像だけを消していました。Adblock Plus は全体的に広告が消えますし、個別の画像を指定することもできるからです。

別に広告業界に反対しているわけでもなんでもないのですが、肌色系の画像がちらつくのが嫌だから導入しています。

ですがこの Adblock Plus では個別には画像しか選択できなくて不便でした。

ScriptAutoRunner の仕組をつかえば記事タイトルもまるごと消せるのでありがたい。

ということで「Amazonのインディーズマンガ」は非表示にさせてもらうとともに、

以前から気になっていた、窓の杜とか、PCWatchとか、インプレス社サイトの肌色成分が多めの記事リンクを消してみました。画像は今までも消していたのですが、記事タイトル文字が残ってしまっていたので、そちらも ScriptAutoRunner によって文字指定して消すことができました。

const pcWatch = () => {
const excludeTextArray = [
  'バウヒュッテ',
  'シリコンで人肌の',
  'ノーフューチャー',
  'ウィザードリィスキーマ',
  'ナイトピットウォーク',
  '人間の肌のような',
  '攻殻機動隊の実写版',
  'グラビア',
  'あの太ももが',
  '恥じらい顔',
  '恋人たち',
  'この綾波、大人だ',
  '写真のキモチ',
];
excludeTextArray.forEach((excludeText) => {
  targetTitles = document.querySelectorAll('span,img');
  targetTitles.forEach((targetTitle) => {
    const title = targetTitle.getAttribute('title');
    if (title) {
      // console.log('ScriptAutoRunner', title)
      if (title.includes(excludeText)) {
        console.log('ScriptAutoRunner', 'Hit', title)
        targetTitle.parentNode.remove();
      }
    }

  });
});
};

setTimeout(pcWatch, 5000);
setTimeout(pcWatch, 10000);
setTimeout(pcWatch, 15000);
setTimeout(pcWatch, 20000);
setTimeout(pcWatch, 25000);
setTimeout(pcWatch, 30000);
setTimeout(pcWatch, 35000);
setTimeout(pcWatch, 40000);
setTimeout(pcWatch, 45000);
setTimeout(pcWatch, 50000);
setTimeout(pcWatch, 55000);
setTimeout(pcWatch, 60000);

指定文字列が実際にあわせているので、なんだか卑猥感丸出しの指定ですが、これ、ターゲット記事リンクで俺が以前みたことがあるから出ているとかではないですよね。全く恥ずかしい。

こんな恥ずかしい文字列をプログラムコードを書いたのは、初めてかもしれません。やれやれです。プログラマとして肌色成分多めなお仕事はしたくないものですよ。

さて、とりあえず動くの対応で、パフォーマンスは後回ししています。リンク記事画像読み込みがけっこう遅延して実行されるみたいなので、単にsetTimeoutでぐるぐる回してみました。新しいのが読み込まれたらそれをイベントで拾って随時消すとか、そういうのやりたければ誰か書いてください。

また、これだとブラウザで見る全てのページに負担がかかってしまうので、URLがインプレス系のページだけで動くようにしたいですが、今の所まだ実装してないです。

ところで動作確認してたら、PCWatchとかでは肌色成分多めなリンク記事がバンバンでるんだけど、家電Watchでは全くでないから、いろいろ文句言われないように調整しているんですね。インプレスさん。なかなかロジックを苦労されているみたいで、w苦笑です。

そういう知りたくもない分岐処理ロジックとかを見抜いたりとか、ため息ついてしまいます。一生懸命、どこかのだれかのエンジニアが調整して「よーし、おじさん、PCWatchやAKIBA PC HOTLINE では、ターゲットが雄ばかりだから、メスフェロモン増加しちゃうぞー」的記事リンクを作成するようなコードとか設定を書いていたんですよね。やれやれ。そうすると記事自体に、エロフラグとか付帯しているってことですよね。ユーザーには見えないけれども、AIに判定させるためにとか。そういう仕事したくないなあ。

終わりに

ところでこれ応用したら、Qiitaページだけで、特定ユーザーの記事やコメントを消すってのも簡単にできそうです。応用範囲が広くて楽しみ。

追記:改良

こんな感じかな。特定サイト向けだから負荷高くてもいいだろうというのにしておきました。

const targetUrls = [
  '.impress.co.jp',
];

if (targetUrls.some(url => location.href.includes(url))) {
  console.log('impress site !');

  const notObsceneImpress = () => {
    const excludeTextArray = [
      'バウヒュッテ',
      'シリコンで人肌の',
      'ノーフューチャー',
      'ウィザードリィスキーマ',
      'ナイトピットウォーク',
      '人間の肌のような',
      '攻殻機動隊の実写版',
      'グラビア',
      'あの太ももが',
      '恥じらい顔',
      '恋人たち',
      'この綾波、大人だ',
      '写真のキモチ',
    ];
    excludeTextArray.forEach((excludeText) => {
      targetTitles = document.querySelectorAll('span,img');
      targetTitles.forEach((targetTitle) => {
        const title = targetTitle.getAttribute('title');
        if (title) {
          // console.log('ScriptAutoRunner', title)
          if (title.includes(excludeText)) {
            console.log('ScriptAutoRunner', 'Hit', title)
            targetTitle.parentNode.remove();
            // targetTitle.style.display = 'none';
          }
        }

      });
    });
  };

  for (let i = 1; i < 60; i += 1) {
    setTimeout(notObsceneImpress, i * 1000)
  }
}

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