20210314のJavaScriptに関する記事は24件です。

Action Cableでできることと実装方法

Action Cableは、Railsのアプリケーションと同様の記述で、WebSocket通信という双方向の通信によるリアルタイム更新機能を実装できるフレームワークで、Rails5から実装されました。

Action Cableを利用することで、たとえばリアルタイムで更新されるチャット機能を実装することができます。

チャネルの作成

channel

リアルタイム更新機能を実現するサーバー側の仕組みでデータの経路を設定したり、送られてきたデータを画面上に表示させたりします。

チャネルを作成するコマンド

ターミナル

rails g channel message #チャネル名
# ↓実行結果↓
Running via Spring preloader in process *****
      invoke  test_unit
      create  test/channels/message_channel_test.rb
      create  app/channels/message_channel.rb
   identical  app/javascript/channels/index.js
   identical  app/javascript/channels/consumer.js
      create  app/javascript/channels/message_channel.js

生成されるファイルのうち以下の2つが重要です。
app/controller/message_controller.rb → クライアントとサーバーを結びつけるためのファイル
app/javascript/channels/message_channel.js → サーバーから送られてきたデータをクライアントに描画するためのファイル

クライアントとサーバーを結びつける

stream_fromメソッド

サーバーとクライアントを関連づけるメソッドでAction Cableにあらかじめ用意されています。

app/controller/messages_controller.rb
class MessageChannel < ApplicationCable::Channel
  def subscribed
    stream_from "message_channel" #記述箇所
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end

broadcast

stream_fromメソッドで関連付けられるデータの経路で、サーバーから送られるデータの経路です。broadcastを介してデータをクライアントに送信します。

broadcastを使ってコントローラーへ以下のように記述します。

app/controller/messages_controller.rb
class MessagesController < ApplicationController
  def new
    @messages = Message.all
    @message = Message.new
  end

  def create
    @message = Message.new(text: params[:message][:text])
    if @message.save
      ActionCable.server.broadcast 'message_channel', content: @message
    end
  end
end

broadcastという経路を通して、message_channelに向けて@messagecontentに入れてデータを送信しています。

JavaScriptへの記述

サーバーから送信されたデータはdataの中にcontentという名称で入っているので、data.content.textで使用できます。

app/javascript/channels/message_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("MessageChannel", {
  received(data) {
    // 以下で使用
    const html = `<p>${data.content.text}</p>`;
    const messages = document.getElementById('messages');
    const newMessage = document.getElementById('message_text');
    messages.insertAdjacentHTML('afterbegin', html);
    newMessage.value='';
  }
});

ちなみにビューはこんな感じです。

app/views/messages/new.html.erb
<h3>mini-talk-app</h3>
<%= form_with model: @message do |f| %>
  <%= f.text_field :text %>
  <%= f.submit '送信' %>
<% end %>
<div id="messages">
  <% @messages.reverse_each.each do |message| %>
    <p><%= message.id %></p>
    <p><%= message.text %></p>
  <% end %>
</div>

content以外もクライアントに送信できる

app/controller/messages_controller.rbでは保存したメッセージの値をcontentに入れて送信していましたが、設定すれば他の値も送ることができます。

ぼくが今開発しているアプリでは、こんな感じで情報を追加してクライアント側に送信しています。

app/controllers/chats_controller.rb
class ChatsController < ApplicationController  def create
    @chat = Chat.new(chat_params)
    if @chat.save
      # userとtimeにそれぞれ@userと@timeを入れている
      @user = current_user
      @time = @chat.created_at.strftime("%Y-%m-%-d %-H:%-M")
      ActionCable.server.broadcast 'chat_channel', content: @chat, user: @user, time: @time
    end
  end

  private
  def chat_params
    params.require(:chat).permit(:text).merge(
      user_id: current_user.id,
      group_id: params[:group_id]
    )
  end
end

クライアント(JavaScript)側ではこのようにデータを利用しています。JavaScript側で投稿日時の表示形式を変更する方法が分からなかったので、事前にサーバー(Ruby)側で表示形式を変更してから送信しています。

app/javascript/channels/chat_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("ChatChannel", {
  received(data) {
    // 送信されたデータを以下で使用
    const html = `<div class="each-chat">
                    <p class="chat-time">${data.time}</p>
                    <p class="chat-text">${data.content.text}</p>
                    <p class="chat-user">${data.user.name}</p>
                  </div>`;
    const chats = document.getElementById("chats");
    chats.insertAdjacentHTML("afterbegin", html);
    const newChat = document.getElementById("chat_text");
    newChat.value="";
  }
});

参考資料

【Rails6.0】ActionCableを使用したライブチャットアプリを実装する手順を解説

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

HTMLで円周率計算を可視化(モンテカルロ法)

今日は3月14日、πの日ということで円周率の計算をするプログラムを作ろう、という趣旨の記事です。

使ったもの

HTMLとタイトルにありますが、HTMLの記述は数行でほとんどがJavaScriptです。HTMLのcanvas要素をJavaScriptでゴリゴリいじって描画します。
canvasについては、このサイトを参考にしました。

計算方法

数値計算ではなく、確率(乱数)を用いるモンテカルロ法で円周率を求めます。

tanien.png
こんな感じの円と四角形に、ランダムに点を打ち込みます。

pi.png

青色の点は円の内側、赤色の点は円の外側にある点です。
均一に点がばらまかれていれば、青と青+赤の点の数の比は、円の面積と四角形の面積比と等しいはずです。
青色の点の数をa、赤色の点の数をbとすると、
$\pi r^2:4r^2 = a:a+b$
$(a+b)\pi r^2 = 4ar^2$
$\pi = \frac{4a}{a+b}$
となり、上記式を用いて円周率を求めることができる。

実装

HTML

canvas要素にidをつけて置いておくだけでいいです。

HTMLのソースコード

pi.html
<!DOCTYPE html>
<html>
    <head>
        <title>円周率</title>
        <script type="text/javascript" src="./pi.js"></script>
    </head>
    <body onload="init();">
        <h1>モンテカルロ法による円周率の計算</h1>
        <canvas id="pi"></canvas>
    </body>
</html>

JavaScript

コードが長いので詳しい説明は省きます。今日中に終わりそうにないので。
ただ、やってることは、
1.背景の描画
2.乱数で点の生成
3.$\pi$の計算
4.2,3の繰り返し
と簡単なので、頭のいい人はもう少し短いコードで書けると思います。

ベクトルクラスを実装する際はこちらの記事を参考にさせて頂きました。

JavaScriptのソースコード

pi.js
//canvas要素の大きさ
const CANVAS_WIDTH = 400;
const CANVAS_HEIGHT = 400;

//変数の準備
var ctx;
const FPS = 1000;
const TIME = 1000/FPS;
var total = 0;
var In = 0;

//ベクトルクラスの実装
class vec2{
    //
    constructor(x=0, y=0){
        this.x = x;
        this.y = y;
    }

    //足し算メゾット
    add(v){
        this.x += v.x;
        this.y += v.y;
        return this;
    }

    //ベクトルを複製するメゾット
    clone(){
        return new vec2(this.x, this.y);
    }

    //静的足し算メゾット
    static add(v1, v2){
        return v1.clone().add(v2);
    }
}

//原点の位置を定義
var origin = new vec2((CANVAS_WIDTH/2),(CANVAS_HEIGHT/2));

//bodyが読み込まれたときに実行される関数
//
function init(){
    //canvas要素を取得
    let pi = document.getElementById("pi");
    if(pi.getContext){
        ctx = pi.getContext("2d");

        //canvasのサイズを指定
        pi.width = CANVAS_WIDTH;
        pi.height = CANVAS_HEIGHT;

        //背景の描画
        draw_init();

        //点を打ちまくる
        setInterval(draw,TIME);
    }
}

//繰り返し実行される関数
function draw(){
    //文字を消す処理
    ctx.clearRect(0,360,CANVAS_WIDTH,400);

    //乱数の代入と変数の準備
    let tempX = getRandomInt(200) - 100;
    let tempY = getRandomInt(200) - 100;
    let pi;

    //円の中か外の判定
    ctx.fillStyle = "rgb(255,0,0)"
    if((tempX**2)+(tempY**2) <= 100**2){
        In++;
        ctx.fillStyle = "rgb(0,0,255)"
    }
    total++;

    //piの計算
    pi = 4*In/total;

    //点の描画処理
    let v1 = new vec2(tempX,tempY);
    v1.y *= -1;
    let screen = vec2.add(v1,origin);
    ctx.fillRect(screen.x,screen.y,1,1);


    //piとtotalの表示
    ctx.fillStyle = "rgb(0,0,0)"
    ctx.fillText(pi,0,380);
    ctx.fillText(total,0,400);
}

//背景を描画する関数
function draw_init(){
    //x軸
    ctx.beginPath();
    ctx.moveTo(0,CANVAS_HEIGHT/2);
    ctx.lineTo(CANVAS_WIDTH,CANVAS_HEIGHT/2);
    ctx.stroke();
    ctx.font = "20px serif";
    ctx.fillText("x",CANVAS_WIDTH-20,(CANVAS_HEIGHT/2)+20);

    //y軸
    ctx.beginPath();
    ctx.moveTo(CANVAS_WIDTH/2,CANVAS_HEIGHT);
    ctx.lineTo(CANVAS_WIDTH/2,0);
    ctx.stroke();
    ctx.fillText("y",(CANVAS_WIDTH/2)+10,15);

    //原点
    ctx.fillText("O",(CANVAS_WIDTH/2)-20,(CANVAS_HEIGHT/2)+20);

    //四角形
    ctx.beginPath();
    ctx.moveTo(100,100);
    ctx.lineTo(100,300);
    ctx.lineTo(300,300);
    ctx.lineTo(300,100);
    ctx.lineTo(100,100);
    ctx.stroke();

    //円
    let t;
    for(t=0;t<=2*Math.PI;t+=0.01){
        let v2 = new vec2(100,t);
        let v1 = new vec2(v2.x*Math.cos(v2.y),v2.x*Math.sin(v2.y));
        v1.y *= -1;
        let screen = vec2.add(v1, origin);
        ctx.fillRect(screen.x, screen.y, 1, 1);
    }
}

//乱数を返す関数
function getRandomInt(max) {
    return Math.floor(Math.random() * Math.floor(max));
}

実行結果

スクリーンショット 2021-03-14 午後11.07.12.png

いい感じに近似出来ているのではないだろうか?

あとがき的なもの

初めて記事を書きました。なので少し言い訳と予防線を貼ったりします。
マークダウンの書き方などわからないことが多く、見苦しい記事になっていたら申し訳ないです。
間違いなどがありましたら、コメントで指摘していただけたら修正いたします。

もうすぐ3月14日が終わってしまう。はたして、3月14日に見られた人はいるのだろうか。

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

npmの復習

目次

1.この記事の目的
2.概要
3.パッケージ管理システム
4.もしnpmがなかったら
5.npmの使い方
6.便利なコマンド
7.最後に
8.参考記事


1. この記事の目的

  • npmに関する知識の復習

    • もやっとした理解だったため記事を読んで理解を深め言語化する
  • 実務でも使えそうな+αの知識を紹介
     - 調べる中で、便利なコマンドなどを学べたので紹介

  • 読んでくれる人の理解の手助けになれば


2. 概要

npmとは?

「Node Package Manager」の略。
Node.jsのパッケージ管理を行うツール。

Node.js

サーバーサイドで動くJavaScript。

特徴

  • 大量のデータ処理が可能
    • C10K問題に対応
      • Apache HTTP Serverなどウェブサーバーとクライアント通信においてクライアントが約1万台に達するとウェブサーバーのハードウェア性能に余裕があるにもかかわらず、レスポンス機能が大きく下がる問題
  • メモリ消費が少ない
    • イベントループ(詳細省略)
  • 処理速度が速い
    • Google V8 JavaScript Engine
      • Googleが開発したコンパイルエンジン
      • Chromeなどで使用
      • 中間コード、インタープリタを搭載せず、最初の実行時からコンパイルされる
      • JIT(Just-In-Time Compiler)
        • ソフトウェアの実行時にコードのコンパイルを実行。実行速度の向上を図るコンパイラ
        • (AOTコンパイラ)
          • 事前コンパイラ
  • サーバーサイドでJavaScriptが使える
    • 「サーバーサイドは新しい言語」ではなく、クライアント側で使用するJavaScriptをそのままサーバーサイドで使用できるため基礎的な書き方は同じ。

パッケージ

あらかじめ用意された便利な機能をまとめたもの

代表例

  • Express
    • Node.jsのMVCフレームワーク
  • promiss、async
    • 非同期処理

など


3. パッケージ管理システム

一つの環境で各種ソフトウェアの導入や削除、依存関係を管理するシステムのこと。

機能

主に4つの機能がある!

  • リポジトリの購読

    • インターネット上のパッケージリポジトリを指定し、必要に応じてパッケージリストを更新
  • ソフトウェアパッケージのインストール・削除

    • ソースコードや設定ファイルをパッケージ化してインストール可能
  • 依存関係の解決

    • あるパッケージに必要なソフトウェアを自動的にインストールしたり更新することができる
    • 同時にインストール、更新できないソフトウェアパッケージのインストールや更新を回避できる
  • 設定管理

    • 設定スクリプトを使って自動的に設定を行える
      • npmならpackage.json、composerならcomposer.jsonとか
    • 競合する機能を持つソフトウェアで、どちらを優先して使うかを設定できる

言われてみたら「確かにな」って感じるけど、言語化するとしっくりくる。


4. もしnpmがなかったら

開発するのに色々手間暇時間がかかります。
→「手間暇かけるのが愛情」が正だとすると、別のところに愛情を使えるようになる

  • 使用するパッケージ、ライブラリなどをいちいち公式サイトなどからダウンロードしないといけない

  • 作ったnpmパッケージをチームで共有したいが共有や更新などの管理などが面倒

つまり、楽に速くコードを書くことができるようになりサービスの開発時間の短縮や他のことに時間をかけられるようになる。


5. npmの使い方

Node.jsのダウンロード

公式サイトからダウンロード。
Node.jsをダウンロードしたらnpmもダウンロードされます

バージョンの確認

ダウンロードしたら

terminal
npm --version

でnpmのバージョンを確認します
(npmのパスが通ってなかったら通してください)

使用するパッケージをインストールする

ここでは例でjQueryをインストールします

プロジェクトルート配下
npm install jquery -g

※ 「-g」などのオプションは後述

既にpackage.jsonがあるプロジェクトの場合

package.jsonに記述されているパッケージを復元(インストール)します
(package.jsonはパッケージ管理スクリプトの一つ。詳細は後述)

package.jsonがディレクトリ
npm install

パッケージの更新

  • 未更新パッケージの確認
未更新パッケージを確認
npm outdated

※パッケージの最新バージョンを使用するにはpackage.jsonに記述が必要※

パッケージを更新する際、いちいちpackage.jsonに内容を確認、記述するのが面倒。
→パッケージのバージョンを一括で最新にしてくれるパッケージnpm-check-updatesが便利!

npm-check-updatesをインストール
npm install -g npm-check-updates
npm-check-updates -u
インストール後、アップデートで最新になる
npm update

これでnpmパッケージのバージョン管理が楽になる。


オプションについて

たくさんあるけど、この資料で使う分を紹介。

-g

グローバルインストール。どのディレクトリでも使えるようにパッケージをインストールする
(グローバルインストールについては後述)

-g
npm install jquery -g

--save

package.jsonのdependenciesに依存関係が追加される。
要は「このpackage.jsonが入ったプロジェクトに必要なパッケージ」
(dependenciesについては後述)

--save
npm install jquery --save

--save-dev

package.jsonのdevDependenciesに依存関係が追加される。
要は「パッケージの開発をするときに必要なパッケージ」。テストするためのパッケージとか。
(devDependenciesについても後述)


ここでnpmの説明から離れた小休憩。

グローバルインストールとローカルインストールの違い

恥ずかしながら、二つの違いをはっきり理解できていなかったため復習。

グローバルインストール

npm installするときに「-g」オプションをつけることで実行可能。
「npm」のルート配下にあるnode_modulesにパッケージがインストールされるため、一度グローバルインストールしたパッケージはどのディレクトリのプロジェクトでも使用することができる

ローカルインストール

npm installした時に何もオプションを付けなかった時はデフォルトでローカルインストールされる。
インストールされるパッケージの場所はプロジェクトルート配下にあるnode_modules。

従って、どのプロジェクトでも使いそうなパッケージをインストールするときはグローバルインストール、既存のプロジェクトを始めるとき(package.jsonが既にあったら)ローカルインストールの方が良い…のか?
※要確認


小休憩になるかわからないがnpmから直接離れてもう一度休憩。

package.jsonのdependenciesとdevDependencies

dependencies

npmパッケージを使うときに必要なパッケージ。
これがないとこのパッケージが動かない。
必要なパッケージ=依存しているパッケージ

dependenciesはインストールされる
npm install [パッケージ名]

これでもインストールされる。

devDependencies

npmパッケージを開発する時に必要なパッケージ。
package.jsonがあるディレクトリでnpm installをしないとインストールされない。
例えば
「jQuery」のパッケージをnpm installでインストールした時は「jQuery」を開発する上でのテスト関係のパッケージなどはインストールされない。
(パッケージをテストするためのパッケージ)


パッケージのアンインストール

グローバルインストールされたパッケージのアンインストール
グローバルインストールされたパッケージ
npm uninstall jquery -g
ローカルインストールされたパッケージのアンインストール
ローカルインストールされたパッケージ
npm uninstall jquery
package.jsonのdependenciesから依存関係を削除する場合
依存関係を削除
npm uninstall jquery --save
package.jsonのdevDependenciesから依存関係を削除
devDependenciesから削除
npm uninstall jquery --save-dev
ちなみに※uninstallは省略記号も使える
uninstallを省略
npm un jquery

6. 便利なコマンド

npm dedupe

パッケージの依存関係を整理してくれる.

  • モジュールBがCに依存

  • モジュールDがCに依存

    • 上記がそれぞれ別々の記述だった場合、記述が重複してバージョンが異なると不整合が起きる
  • これらを「モジュールB、DがCに依存」というふうに書き換えてくれるのがnpm dedupe

→パッケージをいくつか個別でinstallした後にやっておくと便利?
→npmに依存関係解決してくれる機能が既にあるから今はあまり使わない?

david

モジュールの最新安定バージョンをとってきてくれるツール
※依存モジュールの依存モジュールは最新にしない

david
npm install david -g
david update

オプションに-uを付けたら安定バージョンではない、最新のバージョンをとってきてくれるツール.

audit

依存関係でトラブった時にterminalでこのコマンド叩いてくださいって言われるやつ。

依存関係でトラブった時に解決手段を表示してくれる

npm audit 

依存関係でトラブった時に解決してくれる

npm audit fix

7. 最後に

参考記事を読んで僕なりにまとめてみましたが、もやっとしたところが言語化されて理解がスッキリした…かも。笑
特にグローバルインストールとローカルインストール、dependenciesとdevDependenciesははっきり理解できてなかったからとても勉強になりました。
あと便利コマンドも役に立ちそうだ!積極的に使って身につけていこう。

僕が下記の参考記事によって助けられたように
この記事が少しでも誰かの手助けになりますように。
ありがとうございました。


8. 参考記事

Wikipedia
- npm
- パッケージ管理システム

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

【React Native】バージョン0.64を使ってみる【ダークモードも】

React Native 0.64 がリリース

2021年3月12日、React Native 0.64 が正式にリリースされました。

大きな変更点は以下の通りです。

  • iOSでのHermesエンジンサポート
  • Hermesがプロキシをサポート(非対応だったライブラリが少なくなった)
  • Reactのバージョンが 17へ(機能的な変更はなし)

使ってみる

npx react-native init projectname
cd ios && pod install

# [!] `React` requires CocoaPods version `>= 1.10.1`, which is not satisfied by your current version, `1.10.0`.

CocoaPodsのバージョンが低く、ポッドインストールできない。

Homebrewで入れていたので、確認してアップグレードする。

brew outdated
# cocoapods (1.10.0) < 1.10.1
brew upgrade cocoapods
# ==> Upgrading cocoapods 1.10.0 -> 1.10.1

再度、Pod Installする

cd ios && pod install

起動する

yarn ios
yarn android

Hermesの有効化

Podfile
use_react_native!(
    :path => config[:reactNativePath],
    # to enable hermes on iOS, change `false` to `true` and then install pods
+   :hermes_enabled => true
)
cd ios && pod install
# Installing hermes-engine (0.7.2)

完了。

スクリーンショット 2021-03-14 13.15.34.png

ダークモード

何気にダークモードがデフォルトで入っている。

設定から Dark Appearance を ONにする。

スクリーンショット 2021-03-14 13.31.51.png

自動でアプリ画面に反映される。

スクリーンショット 2021-03-14 13.31.31.png

API自体は以前からあったもの(useColorSchemeフック)と同じ。

import { useColorScheme } from 'react-native';

const App: () => Node = () => {
+ const isDarkMode = useColorScheme() === 'dark';

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

ベンチマーク

Hermesは開発モードでは有効になていないので、ipa作成して配布しないとと分からない。

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

express Form作成

Package.json のdependencies

  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "ejs": "~2.6.1",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1"
  }

expressのForm作成について

expressで入力フォームを(ログイン、新規登録)作成する際にform入力を利用しました。
expressを用いたフォーム作成の忘備録を残します。
参考サイト:
Node.js + Express で 登録画面 を作る
Node.js+Expressでpostを使う!
Express v4.xでJSONのリクエスト/レスポンスを取得する正しい方法

フォーム取得側の設定について

POSTのRequestの取得について

今まではbaby-parserをインストールして対応していましたが、現在はExpress本体に用意されているので追加でインストールは不要です。(4.x系で対応)

const express = require('express')
const app = express()
app.use(express.json())

app.post('/', function (req, res) {
  console.log(req.body);
  res.send('Got a POST request')
})

上記内容を記述しておくことで、POSTでの"req.body"でJSON受け取りが可能です。

フォームのejsについて

<form action="/" method="post">
    <input type="text" name="name" placeholder="name">
    <input type="text" name="age" placeholder="age">
    <input type="submit" value="送信!">
</form>

実際の出力は下記のようになります。

スクリーンショット 2021-03-14 21.23.26.png

設定のポイントとしては、


action : データの送信先(受け取り側と一致するように)
method : 送信方法(今回はpost送信、受け取り側も一致するように)

name属性に設定されたものが
送信されるオブジェクトのkeyとなります。
出力結果(console.log(req.body))は下記のようになります。
[Object: null prototype] { name: 'test', age: '55' }


こちらのsubmitと設定されたボタンを押すことでPOST送信が実行されます。

後は、よしなにして頂ければということです。

以上、簡単にですがフォームの忘備録でした。

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

npm

npmとは?【フロントエンド?パッケージ管理】

Node.js のパッケージ(Package)を管理するツール

パッケージとは、Node.jsで利用できるJavaScriptの「ライブラリ」や「フレームワーク」などのこと

  • 例えば
    • CSSフレームワークのbootstrap
    • jsフレームワークのvue.js
  • あとはよくあるちょっとしたjsモジュールの

    • シャドーボックス
    • スライダーとか
    • Node Package Managerの略
    • package.jsonに必要なパッケージ、バージョンを記述して管理する
    • node_modulesディレクトリ配下に、インストールしたパッケージのソースが配置されていく

参考

とりあえずこれだけでOK
フロントエンド開発の3ステップ(npmことはじめ)
https://qiita.com/hashrock/items/15f4a4961183cfbb2658

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

Promise.allとPromise.allsettedの違いを教えて

これまで自分は、Promise.allを使ったことがなくPromise.allsettedに関しては名前すら聞いたことがありませんでした・・・そこで、自分と同じような人が少しでも早く理解を深められたらいいなと思いPromise.allとPromise.allsettedについての記事を書いてみました!

Promiseについて簡単に

JavaScriptの特性として、「非同期処理」というものがあります。同期処理は書かれたコードが上から順番に必ず実行されるのに対し、非同期処理は上から順番に実行されない時もあります。例えば、外部連携などの実行に時間がかかる処理の場合、実行が完全に完了する前に次の処理に走ってしまうことがあります。そのため、

let res = dynamodb.get(params)
console.log(res)

上記のような流れの場合に、非同期処理だと①の処理が最後まで終わり変数に代入される前に②の処理が実行されエラーになるというパターンが発生することがあります。
ただ、Promiseを使うことで実行する順番を指定することが可能です。
そのため、Promiseにより先ほど説明したようなエラーを防ぐことができます。

Promise.allを使用するメリット

Promise.allを使うメリットとして、時間のかかる非同期処理をたくさん行わないといけない場合に、「無駄なく効率よく並列で実行できる」ことが挙げられます。

例えば、下記のように時間のかかる処理を複数行うとします。

let res_1 = await dynamodb.get(params_1) // 実行を完了するのに1秒
let res_2 = await dynamodb.get(params_2) // 実行を完了するのに2秒
let res_3 = await dynamodb.get(params_3) // 実行を完了するのに3秒

この場合だと、全て実行し終えるのに6秒もかかります。

一方で実装したい機能の関係上、毎回6秒も待ってられない場合を考えてみます。
その場合、下記のようにします。

let  [ res_1 , res_2,  res_3] = await Promise.all(
dynamodb.get(params_1), dynamodb.get(params_2), dynamodb.get(params_3)
)

そうすると、3つの処理が順番に行われるのではなく、並行で実行されます。
よって、全て実行し終えるのに3秒しかかからないのです。
Promise.all()を使うことで、複数の非同期処理を最速で終わらせることができます。

Promise.allとPromise.allsettedの違いとは

両者の違いに触れる前に、Promiseは成功した際にはresolveが、失敗した際にはrejectが返されます。(地味にポイントかもです笑)

では、違いについて見ていきましょう。
Promise.allは、配列内の複数あるPromiseがたった1つでも「reject」を返すと、即終了いたします。たとえ、それ以外のPromiseが全て順調にうまくいっており、それらの実行が最後まで完了していなかったとしても、途中で終了します。初めはなんで???って感じでした(笑)
それに比べて、Promise.allsettedは配列内のPromiseが「resolve」でも「reject」でも関係なく、全ての処理が終わって初めて完了します。
そのため、「とりあえず、成功・不成功気にせず全てのPromiseを実行したいんだ!」(あ、自分はこれでした笑)と考える方は、Promise.allsettedでその悩みは解決できると思います。

最後に

あまりQiitaなどを書いたことがないため、拙い文章ではあったと思いますが最後まで読んでくださりありがとうございました!これからも月に2回ほど学んだことをアウトプットしていきます。もう少し文章が上手くなれるように頑張ります笑

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

Promise.allとPromise.allsettledの違いを教えて

これまで自分は、Promise.allを使ったことがなくPromise.allsettledに関しては名前すら聞いたことがありませんでした・・・そこで、自分と同じような人が少しでも早く理解を深められたらいいなと思いPromise.allとPromise.allsettledについての記事を書いてみました!

Promiseについて簡単に

JavaScriptの特性として、「非同期処理」というものがあります。同期処理は書かれたコードが上から順番に必ず実行されるのに対し、非同期処理は上から順番に実行されない時もあります。例えば、外部連携などの実行に時間がかかる処理の場合、実行が完全に完了する前に次の処理に走ってしまうことがあります。そのため、

let res = dynamodb.get(params)
console.log(res)

上記のような流れの場合に、非同期処理だと1行目の処理が最後まで終わり変数に代入される前に2行目の処理が実行されエラーになるというパターンが発生することがあります。
ただ、Promiseを使うことで実行する順番を指定することが可能です。
そのため、Promiseにより先ほど説明したようなエラーを防ぐことができます。

Promise.allを使用するメリット

Promise.allを使うメリットとして、時間のかかる非同期処理をたくさん行わないといけない場合に、「無駄なく効率よく並列で実行できる」ことが挙げられます。

例えば、下記のように時間のかかる処理を複数行うとします。

let res_1 = await dynamodb.get(params_1) // 実行を完了するのに1秒
let res_2 = await dynamodb.get(params_2) // 実行を完了するのに2秒
let res_3 = await dynamodb.get(params_3) // 実行を完了するのに3秒

この場合だと、全て実行し終えるのに6秒もかかります。

一方で実装したい機能の関係上、毎回6秒も待ってられない場合を考えてみます。
その場合、下記のようにします。

let  [ res_1 , res_2,  res_3] = await Promise.all(
dynamodb.get(params_1), dynamodb.get(params_2), dynamodb.get(params_3)
)

そうすると、3つの処理が順番に行われるのではなく、並行で実行されます。
よって、全て実行し終えるのに3秒しかかからないのです。
Promise.all()を使うことで、複数の非同期処理を最速で終わらせることができます。

Promise.allとPromise.allsettledの違いとは

両者の違いに触れる前に、Promiseは成功した際にはresolveが、失敗した際にはrejectが返されます。(正確にはPromiseのステータスをpendingからfullfilledに変更するために、resolve()を呼び出しrejectedに変更するためにreject()を呼び出します)

では、違いについて見ていきましょう。
Promise.allは、配列内の複数あるPromiseがたった1つでも「reject」を返すと、即終了します。たとえ、それ以外のPromiseが全て順調にうまくいっており、それらの実行が最後まで完了していなかったとしても、途中で終了します。初めはなんで???って感じでした(笑)
それに比べて、Promise.allsettledは配列内のPromiseが「resolve」でも「reject」でも関係なく、全ての処理が終わって初めて完了します。
そのため、「とりあえず、成功・不成功気にせず全てのPromiseを実行したいんだ!」(あ、自分はこれでした笑)と考える方は、Promise.allsettledでその悩みは解決できると思います。

最後に

あまりQiitaなどを書いたことがないため、拙い文章ではあったと思いますが最後まで読んでくださりありがとうございました!これからも月に2回ほど学んだことをアウトプットしていきます。もう少し文章が上手くなれるように頑張ります笑

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

vue-cliで外部JSファイルを読み込む方法

vue-cliで外部JSファイルを読み込む方法

背景

Vueコンポーネントの年月日のプルダウンを外部ファイルのjsで実装したい

理由

可読性やメンテナンス性が向上するため
大規模なプロジェクトになるほど、こういった構成にしたときのメリットを感じやすい

準備

vueCLIのディレクトリのsrcフォルダ直下にutilesという名前のフォルダを作成
その中にdefinition.jsとういう外部JSファイルを作成

実装

外部js

  1. 外部ファイル内に年月日の値を生成する(関数を作成、ループ関数を配列に格納する←どちらでも可能だが、definitionsの役割は値の生成なので、jsファイル側で格納する)
  2. 年月日それぞれの値をexport
definition.js
var this_year, today;
today = new Date();
this_year = today.getFullYear();

// 配列を変数に格納
const yearList = []
const monthList = []
const dayList = []

// 第一引数〜第二引数までの年月日を生成し、配列(list)に追加する関数を作成
const optionLoop = (start, end, list) => {
    for( let i = start; i <= end; i++)
        list.push(i)
}

optionLoop(1950, this_year, yearList);
optionLoop(1, 12, monthList);
optionLoop(1, 31, dayList);

// 年月日の値のforループを配列に格納し、vueコンポーネントにexportする
export {yearList, monthList, dayList}

読み込む側のVueコンポーネント

  1. import { yearList, monthList, dayList } from '@/utiles/definition';で外部jsファイルを読み込む
  2. dataプロパティ部分でv-forで回す際の配列(years, months, days)に値をセットする

※yearList, monthList, dayListはそれぞれ年月日の値が入っている配列である

BasicForm.vue

<template>
<div>
  <div class="form">
    <div class="header">
      <p id="step">STEP1</p>
      <p id="inst">お客様の情報を入力してください</p>
    </div>
    <div class="body">
      <p class="genre">-性別-</p>
        <label><input type="radio" name="gender" value="男性" @change="updateGender">男性</label>
        <label><input type="radio" name="gender" value="女性" @change="updateGender">女性</label>
      <p class="genre">-生年月日-</p>
      <select name="year" id="id_year" @change="updateYear">
        <option v-for="(year, key) in years" :key="key">{{ year }}</option>
      </select><select name="month" id="id_month" @change="updateMonth">
        <option v-for="(month, key) in months" :key="key">{{ month }}</option>
      </select><select name="day" id="id_day" @change="updateDay">
        <option v-for="(day, key) in days" :key="key">{{ day }}</option>
      </select></div>
  </div>
  <div class="button-group">
    <router-link to="/questionnaire" class="button">次へ進む ></router-link>
  </div>
</div>
</template>

<script>
import { yearList, monthList, dayList } from '@/utiles/definition';

export default {
  data() {
    return {
      years: yearList,
      months: monthList,
      days: dayList,
    }
  },
  methods: {
    updateGender (e) {
      this.$store.commit('updateGender', e.target.value)
    },
    updateYear (e) {
      this.$store.commit('updateYear', e.target.value)
    },
    updateMonth (e) {
      this.$store.commit('updateMonth', e.target.value)
    },
    updateDay (e) {
      this.$store.commit('updateDay', e.target.value)
    },
  }
}
</script>
  <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
* {
  // outline: auto;
    margin:0; padding:0;        /*全要素のマージン・パディングをリセット*/
  max-width: 100%;
}

#step{
  background: #1e90ff;
  color: #fff;
  font-size: 2px;
  margin: 0;
  max-width: 40px;
  padding: 2px;
  text-align: center;
}

#inst {
  font-size: 12px;
  color: #696969;
  text-align: center;
  padding-bottom: 10px;
}

.header {
  background: #afeeee;
  margin: 0%;
  padding: 0%;
  line-height: 10px;
  border-bottom: solid 1px #48d1cc;
}

.body {
  font-size: 9px;
  margin: 10px;
  padding-bottom: 10px;
  line-height: 30px;
  text-align: left;
}

.body input {
  vertical-align:middle;
}

.genre {
  color: #1e90ff;
}

select {
  padding: 5px 10px 5px 0px;
  font-size: 9px;
  border: 1px solid #dcdcdc;
}

.form{
  max-width: 80%;
  margin-top: 80px;
  margin-left: auto;
  margin-right: auto;
  border: 1px solid #48d1cc;
  border-bottom: 0.5px solid #000;
}

.button-group{
  text-align: center;
}

.button {
  background-color: #40e0d0;
  color: #fff;
  border: solid 1px #48d1cc;
  margin-top: 1rem;
  padding: 5px 10px;
  border-radius: 0.5rem 0.5rem;
  text-decoration: none;
  display:inline-block;
  margin: 10px;
}
</style>

おまけ

プルダウンは、コンポーネント内だけで完結させる()こともできるが、可読性やメンテナンス性向上のため、値を生成するjsファイルを用意するという方が適切だと判断し、今回実装を行った。

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

【Vue.js】vue-cliで外部JSファイルを読み込む方法

vue-cliで外部JSファイルを読み込む方法

背景

Vueコンポーネントの年月日のプルダウンを外部ファイルのjsで実装したい

理由

可読性やメンテナンス性が向上するため
大規模なプロジェクトになるほど、こういった構成にしたときのメリットを感じやすい

準備

vueCLIのディレクトリのsrcフォルダ直下にutilesという名前のフォルダを作成
その中にdefinition.jsとういう外部JSファイルを作成

実装

外部js

  1. 外部ファイル内に年月日の値を生成する(関数を作成、ループ関数を配列に格納する←どちらでも可能だが、definitionsの役割は値の生成なので、jsファイル側で格納する)
  2. 年月日それぞれの値をexport
definition.js
var this_year, today;
today = new Date();
this_year = today.getFullYear();

// 配列を変数に格納
const yearList = []
const monthList = []
const dayList = []

// 第一引数〜第二引数までの年月日を生成し、配列(list)に追加する関数を作成
const optionLoop = (start, end, list) => {
    for( let i = start; i <= end; i++)
        list.push(i)
}

optionLoop(1950, this_year, yearList);
optionLoop(1, 12, monthList);
optionLoop(1, 31, dayList);

// 年月日の値のforループを配列に格納し、vueコンポーネントにexportする
export {yearList, monthList, dayList}

読み込む側のVueコンポーネント

  1. import { yearList, monthList, dayList } from '@/utiles/definition';で外部jsファイルを読み込む
  2. dataプロパティ部分でv-forで回す際の配列(years, months, days)に値をセットする

※yearList, monthList, dayListはそれぞれ年月日の値が入っている配列である

BasicForm.vue

<template>
<div>
  <div class="form">
    <div class="header">
      <p id="step">STEP1</p>
      <p id="inst">お客様の情報を入力してください</p>
    </div>
    <div class="body">
      <p class="genre">-性別-</p>
        <label><input type="radio" name="gender" value="男性" @change="updateGender">男性</label>
        <label><input type="radio" name="gender" value="女性" @change="updateGender">女性</label>
      <p class="genre">-生年月日-</p>
      <select name="year" id="id_year" @change="updateYear">
        <option v-for="(year, key) in years" :key="key">{{ year }}</option>
      </select><select name="month" id="id_month" @change="updateMonth">
        <option v-for="(month, key) in months" :key="key">{{ month }}</option>
      </select><select name="day" id="id_day" @change="updateDay">
        <option v-for="(day, key) in days" :key="key">{{ day }}</option>
      </select></div>
  </div>
  <div class="button-group">
    <router-link to="/questionnaire" class="button">次へ進む ></router-link>
  </div>
</div>
</template>

<script>
import { yearList, monthList, dayList } from '@/utiles/definition';

export default {
  data() {
    return {
      years: yearList,
      months: monthList,
      days: dayList,
    }
  },
  methods: {
    updateGender (e) {
      this.$store.commit('updateGender', e.target.value)
    },
    updateYear (e) {
      this.$store.commit('updateYear', e.target.value)
    },
    updateMonth (e) {
      this.$store.commit('updateMonth', e.target.value)
    },
    updateDay (e) {
      this.$store.commit('updateDay', e.target.value)
    },
  }
}
</script>
  <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
* {
  // outline: auto;
    margin:0; padding:0;        /*全要素のマージン・パディングをリセット*/
  max-width: 100%;
}

#step{
  background: #1e90ff;
  color: #fff;
  font-size: 2px;
  margin: 0;
  max-width: 40px;
  padding: 2px;
  text-align: center;
}

#inst {
  font-size: 12px;
  color: #696969;
  text-align: center;
  padding-bottom: 10px;
}

.header {
  background: #afeeee;
  margin: 0%;
  padding: 0%;
  line-height: 10px;
  border-bottom: solid 1px #48d1cc;
}

.body {
  font-size: 9px;
  margin: 10px;
  padding-bottom: 10px;
  line-height: 30px;
  text-align: left;
}

.body input {
  vertical-align:middle;
}

.genre {
  color: #1e90ff;
}

select {
  padding: 5px 10px 5px 0px;
  font-size: 9px;
  border: 1px solid #dcdcdc;
}

.form{
  max-width: 80%;
  margin-top: 80px;
  margin-left: auto;
  margin-right: auto;
  border: 1px solid #48d1cc;
  border-bottom: 0.5px solid #000;
}

.button-group{
  text-align: center;
}

.button {
  background-color: #40e0d0;
  color: #fff;
  border: solid 1px #48d1cc;
  margin-top: 1rem;
  padding: 5px 10px;
  border-radius: 0.5rem 0.5rem;
  text-decoration: none;
  display:inline-block;
  margin: 10px;
}
</style>

おまけ

プルダウンは、コンポーネント内だけで完結させる()こともできるが、可読性やメンテナンス性向上のため、値を生成するjsファイルを用意するという方が適切だと判断し、今回実装を行った。

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

JavaScriptの配列のループ処理(for/map/reduce)の計測

JavaScriptの配列のループ処理を計測する。

今回計測したのは以下。
- for
- for of
- for in
- forEach
- reduce
- map

測定方法

ループ処理内では何も実行しない。
1度の実行だとはじめに実行されるループ処理が最も遅くなっため、計測対象のループ処理自体を数回実行し実行時間の平均を算出する。

const { performance } = require("perf_hooks");

// 1回の実行だと実行順序によって結果にブレがあったため何回か実行した結果の平均を取る
function measure(f, t) {
  const n = 100000;
  const s = performance.now();
  for (let i = 0; i < n; i++) {
    f(a);
  }
  // 少数第五位まで表示
  const d = 100000;
  console.log(t, " avg: ", Math.floor(((ms = performance.now() - s) / n) * d) / d);
}

// 配列の長さ - 測定に応じて数値を変える
const l = 10;
const a = [...Array(l)];

// 各loop処理の実行
measure(function (a) {for (let i = 0; i < a.length; i++) {}}, "for\t");
measure(function (a) {for (const i of a) {}}, "for of\t");
measure(function (a) {for (const i in a) {}}, "for in\t");
measure(function (a) {a.forEach(() => {});}, "forEach\t");
measure(function (a) {a.reduce(() => {});}, "reduce\t");
measure(function (a) {a.map(() => {});}, "map\t");
$ node loop.js
for       avg:  0.00005
for of    avg:  0.0001
for in    avg:  0.00048
forEach   avg:  0.00007
reduce    avg:  0.00006
map       avg:  0.0001

結果

数値はミリ秒。

処理\配列の長さ 10 100 1000 10000
for 0.00005 0.00009 0.00047 0.00417
for of 0.0001 0.00027 0.00146 0.01424
for in 0.00048 0.00286 0.02671 0.27616
forEach 0.00007 0.00025 0.00148 0.02653
reduce 0.00006 0.00031 0.00149 0.02717
map 0.0001 0.00057 0.00401 0.06101

パフォーマンスが良い順番は以下。

  1. for
  2. for of
  3. forEach reduce
  4. map
  5. for in
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

イベントリスナーが即時実行されていた

たいしたことないんだけど地味にはまってしたまったのでメモ。

イベントリスナーがクリックの時だけ動いていて欲しいのになぜかロードのタイミングで実行される:confounded:

用意していたイベントリスナーは以下の通り。
クリックしたら動いてね~ってやつ。

【修正前】clickEventListener.js
document.addEventListener("click",displayAlert(),false);

呼び出している関数displayAlertは以下の通り。
呼び出されたらダイアログ表示しますってやつ。

displayAlert.js
function displayAlert(){alert("execute function")};

なんで思い通りに動いてくれないの???思っていたら、
displayAlertがJSが読み込まれた時点で実行されていただけでした。
なのでclickしたタイミングで動くように
displayAlert()の()を外してあげればOK。

【修正後】clickEventListener.js
document.addEventListener("click",displayAlert,false);

はい。想定通りのタイミングで動きました:relaxed:
JavaScript書き方に自由度があるところが難しい。

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

「AIR DATEPICKER」の使い方 ~ライブラリを直接いじってしまえ 編~

基礎編はこちら

どうも。kapyです。
先述の基礎編は、前座です。
気持ちとしてはこちらが書きたい内容でした。
「AIR DATEPICKER」はJQuery環境下なので、昔からあるページなどで自前のプラグイン化していると、うまく動きませんました。
そんなときは諦めて、ライブラリを直接編集しましょう。

動作確認した環境

Google Chrome 88.0.4324.150 / firefox 81.0.2 / IE 11 / safari 14.0.3 /

AIR DATEPICKERについて

公式ドキュメント
ダウンロード

何が原因で動かなかったか

軽く、今回なぜライブラリの直接編集に至ったのか、書いておきます。
直接の原因は、よくある$マークのコンフリクトです。
noConflictやJQuery直打ちなど、ググって試しては見たものの、一向に動作する気配がなく、
さらには、animate関数など他の便利な関数達も使えない不便な状況でした。
かと言って、消すと、また別のファイルでが起こってしまい、編集可能な範囲での解決は困難だと、判断しました。
(外部案件だと、よくある。。?)

そうか。ライブラリに直接書けばいい

まずは、規約を確認

今回のライブラリは、MITライセンスです。商用ライブラリだと、改変不可の場合もあるようなので、一応注意しておきましょう。

最低限の保守性

さて、ライブラリの編集に踏み切るとしても、軽量化は、そのままにしておきたい。。。
運用面も考慮し、以下の3点に注意をおきました。

  1. 一旦、min.jsを整形プラグインで整形しなおして、あとで、改行などをとる
  2. 後から見た人もわかるよう、ファイル名の変更
  3. 出来る限り、自前の変数と関数は、他のファイルで参照しておく

① 一旦、min.jsを整形ライブラリで整形しなおして、あとで、改行などをとる

筆者は整形にvscodeのbeautifyという拡張機能を使っています。
まあ、インデントわけが見れればいいので、chrome開発者ツールなどでも、可能です。
Chromeデベロッパーツールで圧縮(minified)されたJavaScriptを読みやすく表示する

軽量化し直すときは、こちらを使いました。
compress.com

② 後から見た人もわかるよう、ファイル名の変更

一応、編集してしまっているので、名前を変えて、保存しておきます。
誰かがコピーしようとしても、これは弄くり回してるよ、ということで、今回は「datepicker.min.local.js」とします。

③ 出来る限り、自前の変数と関数は、他のファイルで参照しておく

最終的に、編集後のライブラリは軽量化しなおして、元に戻しておきたいので、運用上修正が発生しうるところは、別ファイルで管理することにします。
今回は「datepicker.options.js」という名前で管理します。

実際に書いてみる

今回、手を入れたのは以下です。それぞれ解説します。

  • minDate/maxDateを外部変数により制御
  • onSelectイベント時に値を取得、外部関数で処理
  • onHideイベント時に、データ処理用の関数を忍ばせる
  • 閉じるボタンを無理やり追加
  • クリック時にスマホだと画面にカレンダーが収まらないので、スクロール関数を付与

minDate/maxDateを外部変数により制御

今回、日付の選択できる期間を、絞りたかったので、予め用意した変数を差し込みます。

datepicker.options.js
var minDateInput = new Date(2020,10,1)//2020/11/1から
var maxDateInput = new Date(2021,2,31//2021/3/31まで

次に変数をライブラリの初期設定のところにぶち込みます。
ちなみに、clearButtonやautoCloseなど、boolean型で設定されるプロパティは、全てこの「l」の中に初期設定されているので、そこを編集すれば、動きます。

datepicker.min.local.js
! function(){
  // 略
  l = {
    // 略
    minDate:minDateInput,
    maxDate:maxDateInput,
  }
  //略
}

onSelectイベント時に値を取得、外部関数で処理

jsで値をリアルタイムに処理したいので、値を取得する関数を、ぶち込みます。
もし、rangeをtrueにしてるときなどは、変数を二つ用意するか、配列にして取得してください。

datepicker.options.js
var getSelectedDate;

var _getDateValue = function _getDateValue(v){
  getSelectedDate = v;
  return getSelectedDate;

}

引数「t」がどうやら、選択された日付のようです。

datepicker.min.local.js
selectDate: function (t) {
  _getDateValue(t);
  //略
}

onHide,onShowイベント時にも、データ処理用の関数を忍ばせる

もし、表示,非表示の時に処理を追加したいときは、こちらをお使いください。
ただ、重すぎる処理を走らせると、ラグが生じてなんともUIよろしくない感じになりますので注意。

datepicker.min.local.js
hide: function () {
  //適当な関数
}

閉じるボタンを無理やり追加

AIR DATEPICKERは、クリアボタンは設定できますが、閉じるボタンは筆者が探した感じ、ありません。
閉じるイベントはあるのですが、「ボタンもDatePickerのなかに入れたい」という人は、
素直にぶち込みましょう。
「i」のところに、buttonsというdivがあり、その中に別の変数で、clearbuttonが挿入されているようなので、中に予め一つ追加しておきます。クラス名やidは適宜つけてください。

datepicker.min.local.js
function () {
  var //略,
      i = '
         <div class="datepicker--buttons">
           <button type="button" id="js-dp-close" class="js-dp-close datepicker--button">閉じる</button>
         </div>';
  //略
}

ただ、掲題の通り、js制御不可な状態なので、css周りをいじって、むりくり消す仕様を作ります。
まず、ライブラリのHideイベントの記載されている箇所から、cssを直接いじってるプロパティを削除します。

datepicker.min.local.js
hide: function () {
  //略
  this.$datepicker.removeClass("active").css({
    //left: "-100000px" <- 削除
  }),
  //略

ここで、「active」というクラス名の着脱が行われていることがわかるので、cssで上書きしていきます。
「pointer-events」と「visibility」で、ポインター(クリック・タッチ)とフォーカス両方、打ち消しておくと、アクセシビリティてきに、良いかと思います。

style.css
.datepicker{
  /* 略 */
  left:-100000px;
  pointer-events: none;
  visibility: none;
  opacity: 0;  
}
.datepicker.active{
  /* 略 */
  pointer-events: fill;
  visibility: visible;
  opacity: 1;
}

クリック時にスマホだと画面にカレンダーが収まらないので、スクロール関数を付与

スマートフォンとかだと、inputをタッチすると、画面の外にDatePickerが起動してしまうことがあるので、位置までスクロールするようにします。
スクロールはcssだけで実装できますが、polyfillだけ、注意してください。筆者はこれを使っています。
smoothscroll.js

datepicker.min.local.js
setPosition: function (t) {
  //略
  window.scrollTo({top:e-80,left:0,behavior:'smooth'});
  //e はDatePicker上部のY座標。-80しているのは、マージンです。
}

終わりに

以上、今回の対応まとめでした。ライブラリは、予期しない挙動をする時があるので、時間がある時orどうしてもやらなきゃいけないときなど、見てみるといいです。勉強し始めの時だと、作者にもよりますが、かなり勉強にもなりました。

コメントいただければ、適宜改訂します。
こういうのを実装したい、などもできるだけ一緒に考えるので、連絡ください。
もし、参考になったよ、というかたがおられましたら、twitterのDMでも、LGTMでも反応いただけると、次回記事のモチベーションになります。

最後までお付き合いいただき、ありがとうございました。

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

useReducer を書きながら学ぶ [ React Hooks 入門 ]

Reactの組み込みフックであるとuseReducerの説明をします。

useReducer とは

  • useStateと同じ様な状態管理用のhook
    • 基本的に、useStateでできることはuseReducerにもできます
  • statedispatch(actionを送信する関数)を返す

構文

const [state, dispatch] = useReducer(reducer, initialState)
  • reducer: state を 更新するための関数
  • dispatch: reducer を 実行するための呼び出し関数  - dispath には action(何を実行するかをまとめたオブジェクト) を引数を渡す

実践1...カウントアプリ(state 1つ)

useStateでも実装できるカウントアプリを今回はuseReducerを使って実装していきましょう。

手順

  • initialStateの定義
  • reducerの定義
  • useReducerの定義
  • JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)

サンプルコード
import React, { useReducer } from "react";

const initialState = 0;

const reducer = (state, action) => {
  switch (action) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <p>{state}</p>
      <button onClick={() => {dispatch('increment')}} >+</button>
      <button onClick={() => {dispatch('decrement')}} >-</button>
      <button onClick={() => {dispatch('reset')}} >reset</button>
    </div>
  );
};

export default App;

275b5aeca20974dab556afc12e2e19a7.gif

initialStateが0で、各reducerがちゃんと機能してることがわかります。

実践2...カウントアプリ(複数のstate)

実践1を少し発展させた内容で、複数のstateを持たせていきましょう。

usereduceruseStateより好ましいのは、複数の値を管理する複雑なステートロジックがある場合や、前のステートに基づいて、次のステートを決める必要がある場合です。

これを頭に入れておけば最適な使い分けの一助になるでしょう。

手順

  • initialStateの定義
  • ② JSXにinitialStateの配置
  • ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる
  • reducerの定義
    • 今回はactionにtypeとvalueをセットしているので、Switch分のcaseの宣言はactionaction.typeに変更
    • secondCounter用にcaseを増やす
    • 複数のstateを含むので、spread構文を用いてprevStateを展開し、更新します
  • useReducerの定義(前と同じ)
  • JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)

サンプルコード
import React, { useReducer } from "react";

// ① `initialState`の定義
const initialState = {
  firstCounter: 0,
  secondCounter: 10
};

const reducer = (state, action) => {
  // ④ actionにtypeとvalueをセットしているので、Switch分のcaseの宣言は`action`→`action.type`に変更
  switch (action.type) {
    case "increment1":
      return { ...state, firstCounter: state.firstCounter + action.value }; // 複数のstateを含むので、spread構文を用いて`prevState`を展開し、更新します
    case "decrement1":
      return { ...state, firstCounter: state.firstCounter - action.value }; // 上に同じ
    case "increment2":
      return { ...state, secondCounter: state.secondCounter + action.value }; // 上に同じ
    case "decrement2":
      return { ...state, secondCounter: state.secondCounter - action.value }; // 上に同じ
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      {/* ② JSXにinitialStateの配置 */}
      <p>カウント1: {state.firstCounter}</p>
      <p>カウント2: {state.secondCounter}</p>
    {/* ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる */}
      {/* ③ action 内を Object にして、typeとvalueをセットする */}
      <button onClick={() => {dispatch({ type: "increment1", value: 1 })}}>
        +increment1
      </button>
      <button onClick={() => {dispatch({ type: "decrement1", value: 1 })}}>
        -decrement1
      </button>
      <button onClick={() => {dispatch({ type: "increment2", value: 10 })}}>
        +increment2
      </button>
      <button onClick={() => {dispatch({ type: "decrement2", value: 10 })}}>
        -decrement2
      </button>
      <button onClick={() => {dispatch({ type: "reset" })}}>
        reset
      </button>
    </div>
  );
};

export default App;

eca7242260f0b62c9a0b9ed96826c0e5.gif

実践3...useReducer × useContext

useReducer で(ローカルに)扱ってきたStateを useContext を使って (グローバルに)Stateを扱っていきたいと思います。

IMG_7DA18755378F-1.jpeg

今回も前回の実践2と同様カウントアプリを作ります。(グローバルに)Stateにカウント値を持たせて各、子コンポネントで値の参照・更新ができるようにします。

29f613e15e8518fdecc5d72a170823e6.gif

手順

  • 3つの子コンポネント A〜Cの作成
  • 親コンポネントから子コンポネント3つをimport
  • reducerinitialStateの定義
  • useReducerreducerinitialStateを渡すことでcountStateとdispatch関数を作成
  • createContextを使ってcountContextを作成(exportする)
  • countContextを使って Provider を用意して valueには countとdispatchをセットする
  • 各子コンポネントでは
    • 渡されたcountContextをuseContextしてもちいる
    • valueからcountを表示させとdispatch関数からincrement, decrementを使えるようにする

サンプルコード
App.js
import React, { useReducer, createContext, createElement } from "react";
import { ComponentA } from "./components/ComponentA";
import { ComponentB } from "./components/ComponentB";
import { ComponentC } from "./components/ComponentC";

export const CountContext = createContext();

const initialState = {
  firstCounter: 0
};

const reducer = (state, action) => {
  switch (action.type) {
    case "increment1":
      return { ...state, firstCounter: state.firstCounter + action.value };
    case "decrement1":
      return { ...state, firstCounter: state.firstCounter - action.value };
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [count, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <h1>カウント{count.firstCounter}</h1>
      <CountContext.Provider
        value={{ countState: count, countDispatch: dispatch }}
      >
        <ComponentA />
        <ComponentB />
        <ComponentC />
      </CountContext.Provider>
    </div>
  );
};

export default App;
components/ComponentA
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentA = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentA</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};
components/ComponentB
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentB = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentB</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};
components/ComponentC
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentC = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentC</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};

実行結果

29f613e15e8518fdecc5d72a170823e6.gif

それぞれの子コンポネントの動作が確認できました。こうすることで、useReduceruseContextを使ってグローバルにstateを管理することができます。

実践4...useReducer × 非同期処理(useEffect)

useReduceruseEffectを使って非同期処理させて外部APIからデータ(記事の擬似データ)を取得・表示させてみましょう。

b2e512d2cb1ee20debac0bb334563ba6.gif

手順

  • useReduceruseEffectのimport
  • 非同期処理はaxiosを使う
  • initialStateの定義(loading, error, post)
  • reducerの作成
    • 引数(state,action
    • return 新しいstate
    • switch ケースは HTTPリクエストの成功時(FETCH_SUCCESS) or 失敗時(FETCH_ERROR)で処理を分けます
  • useReducerinitialStatereducer関数を読み込ませて、statedispatch関数を利用できるようにします
  • useEffect内にHTTPリクエスト非同期処理を記載する
  • JSXに、FETCH_SUCCESS時とFETCH_ERROR時の場合のコンポーネントを書く

サンプルコード
App.js
import React, { useEffect, useReducer } from "react";
import axios from "axios";

const initialState = {
  loading: true,
  error: "",
  post: {}
};

const reducer = (state, action) => {
  switch (action.type) {
    case "FETCH_SUCCESS":
      return {
        loading: false,
        error: "",
        post: action.payload
      };
    case "FETCH_ERROR":
      return {
        loading: false,
        error: "データ取得に失敗",
        post: {}
      };
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/posts/1")
      .then((res) => {
        dispatch({ type: "FETCH_SUCCESS", payload: res.data });
      })
      .catch(() => {
        dispatch({ type: "FETCH_ERROR" });
      });
  }, []);

  return (
    <div>
      <h1>{state.loading ? "Loading..." : state.post.title}</h1>
      <p>{state.error ? state.error : null}</p>
    </div>
  );
};

export default App;

実行結果

b2e512d2cb1ee20debac0bb334563ba6.gif

非同期処理なので、時間差で 「Loading」がtitleに変わっていることがわかります。

シーン別 useState と useReducer 使い分け

結論、シンプルなstateにはuseStateが向いていて、複雑なstate管理にはuseReducerがいいでしょう。

表にして、特徴をまとめました。

useState useReducer
扱うといい state の型 Number, String, Bool などのプリミティブ型を更新する時 Array, Object の更新に使う時
どんなシーンで使うといい? 単独のstate(複数でもできるっちゃできる) 複数のstateを同時に更新したい時
stateの更新に複雑なビジネスロジックがある場合 好ましくない 好ましい
ストアーの種類 ローカルな管理に向いてる useContextと合わせてグローバルに管理するとよい

以上です!

参考

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

useReducer を書きながら学ぶ(React Hooks 入門シリーズ 6/6)

ロードマップ

React 16.8 で追加された機能であるReactのHooksについて書いてあります。

書きながら学ぶ React Hooks 入門シリーズとして書き下ろしました。

これが最後です。

はじめに

Reactの組み込みフックであるとuseReducerの説明をします。

useReducer とは

  • useStateと同じ様な状態管理用のhook
    • 基本的に、useStateでできることはuseReducerにもできます
  • statedispatch(actionを送信する関数)を返す

構文

const [state, dispatch] = useReducer(reducer, initialState)
  • reducer: state を 更新するための関数
  • dispatch: reducer を 実行するための呼び出し関数  - dispath には action(何を実行するかをまとめたオブジェクト) を引数を渡す

実践1...カウントアプリ(state 1つ)

useStateでも実装できるカウントアプリを今回はuseReducerを使って実装していきましょう。

手順

  • initialStateの定義
  • reducerの定義
  • useReducerの定義
  • JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)

サンプルコード
import React, { useReducer } from "react";

const initialState = 0;

const reducer = (state, action) => {
  switch (action) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <p>{state}</p>
      <button onClick={() => {dispatch('increment')}} >+</button>
      <button onClick={() => {dispatch('decrement')}} >-</button>
      <button onClick={() => {dispatch('reset')}} >reset</button>
    </div>
  );
};

export default App;

275b5aeca20974dab556afc12e2e19a7.gif

initialStateが0で、各reducerがちゃんと機能してることがわかります。

実践2...カウントアプリ(複数のstate)

実践1を少し発展させた内容で、複数のstateを持たせていきましょう。

usereduceruseStateより好ましいのは、複数の値を管理する複雑なステートロジックがある場合や、前のステートに基づいて、次のステートを決める必要がある場合です。

これを頭に入れておけば最適な使い分けの一助になるでしょう。

手順

  • initialStateの定義
  • ② JSXにinitialStateの配置
  • ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる
  • reducerの定義
    • 今回はactionにtypeとvalueをセットしているので、Switch分のcaseの宣言はactionaction.typeに変更
    • secondCounter用にcaseを増やす
    • 複数のstateを含むので、spread構文を用いてprevStateを展開し、更新します
  • useReducerの定義(前と同じ)
  • JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)

サンプルコード
import React, { useReducer } from "react";

// ① `initialState`の定義
const initialState = {
  firstCounter: 0,
  secondCounter: 10
};

const reducer = (state, action) => {
  // ④ actionにtypeとvalueをセットしているので、Switch分のcaseの宣言は`action`→`action.type`に変更
  switch (action.type) {
    case "increment1":
      return { ...state, firstCounter: state.firstCounter + action.value }; // 複数のstateを含むので、spread構文を用いて`prevState`を展開し、更新します
    case "decrement1":
      return { ...state, firstCounter: state.firstCounter - action.value }; // 上に同じ
    case "increment2":
      return { ...state, secondCounter: state.secondCounter + action.value }; // 上に同じ
    case "decrement2":
      return { ...state, secondCounter: state.secondCounter - action.value }; // 上に同じ
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      {/* ② JSXにinitialStateの配置 */}
      <p>カウント1: {state.firstCounter}</p>
      <p>カウント2: {state.secondCounter}</p>
    {/* ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる */}
      {/* ③ action 内を Object にして、typeとvalueをセットする */}
      <button onClick={() => {dispatch({ type: "increment1", value: 1 })}}>
        +increment1
      </button>
      <button onClick={() => {dispatch({ type: "decrement1", value: 1 })}}>
        -decrement1
      </button>
      <button onClick={() => {dispatch({ type: "increment2", value: 10 })}}>
        +increment2
      </button>
      <button onClick={() => {dispatch({ type: "decrement2", value: 10 })}}>
        -decrement2
      </button>
      <button onClick={() => {dispatch({ type: "reset" })}}>
        reset
      </button>
    </div>
  );
};

export default App;

eca7242260f0b62c9a0b9ed96826c0e5.gif

実践3...useReducer × useContext

useReducer で(ローカルに)扱ってきたStateを useContext を使って (グローバルに)Stateを扱っていきたいと思います。

IMG_7DA18755378F-1.jpeg

今回も前回の実践2と同様カウントアプリを作ります。(グローバルに)Stateにカウント値を持たせて各、子コンポネントで値の参照・更新ができるようにします。

29f613e15e8518fdecc5d72a170823e6.gif

手順

  • 3つの子コンポネント A〜Cの作成
  • 親コンポネントから子コンポネント3つをimport
  • reducerinitialStateの定義
  • useReducerreducerinitialStateを渡すことでcountStateとdispatch関数を作成
  • createContextを使ってcountContextを作成(exportする)
  • countContextを使って Provider を用意して valueには countとdispatchをセットする
  • 各子コンポネントでは
    • 渡されたcountContextをuseContextしてもちいる
    • valueからcountを表示させとdispatch関数からincrement, decrementを使えるようにする

サンプルコード
App.js
import React, { useReducer, createContext, createElement } from "react";
import { ComponentA } from "./components/ComponentA";
import { ComponentB } from "./components/ComponentB";
import { ComponentC } from "./components/ComponentC";

export const CountContext = createContext();

const initialState = {
  firstCounter: 0
};

const reducer = (state, action) => {
  switch (action.type) {
    case "increment1":
      return { ...state, firstCounter: state.firstCounter + action.value };
    case "decrement1":
      return { ...state, firstCounter: state.firstCounter - action.value };
    case "reset":
      return initialState;
    default:
      return state;
  }
};

const App = () => {
  const [count, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <h1>カウント{count.firstCounter}</h1>
      <CountContext.Provider
        value={{ countState: count, countDispatch: dispatch }}
      >
        <ComponentA />
        <ComponentB />
        <ComponentC />
      </CountContext.Provider>
    </div>
  );
};

export default App;
components/ComponentA
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentA = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentA</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};
components/ComponentB
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentB = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentB</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};
components/ComponentC
import React, { useContext } from "react";
import { CountContext } from "../App";

export const ComponentC = () => {
  const countContext = useContext(CountContext);
  return (
    <div>
      <p>ComponentC</p>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "increment1", value: 1 });
        }}
      >
        +increment1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "decrement1", value: 1 });
        }}
      >
        -decrement1
      </button>
      <button
        onClick={() => {
          countContext.countDispatch({ type: "reset" });
        }}
      >
        reset
      </button>
    </div>
  );
};

実行結果

29f613e15e8518fdecc5d72a170823e6.gif

それぞれの子コンポネントの動作が確認できました。こうすることで、useReduceruseContextを使ってグローバルにstateを管理することができます。

実践4...useReducer × 非同期処理(useEffect)

useReduceruseEffectを使って非同期処理させて外部APIからデータ(記事の擬似データ)を取得・表示させてみましょう。

b2e512d2cb1ee20debac0bb334563ba6.gif

手順

  • useReduceruseEffectのimport
  • 非同期処理はaxiosを使う
  • initialStateの定義(loading, error, post)
  • reducerの作成
    • 引数(state,action
    • return 新しいstate
    • switch ケースは HTTPリクエストの成功時(FETCH_SUCCESS) or 失敗時(FETCH_ERROR)で処理を分けます
  • useReducerinitialStatereducer関数を読み込ませて、statedispatch関数を利用できるようにします
  • useEffect内にHTTPリクエスト非同期処理を記載する
  • JSXに、FETCH_SUCCESS時とFETCH_ERROR時の場合のコンポーネントを書く

サンプルコード
App.js
import React, { useEffect, useReducer } from "react";
import axios from "axios";

const initialState = {
  loading: true,
  error: "",
  post: {}
};

const reducer = (state, action) => {
  switch (action.type) {
    case "FETCH_SUCCESS":
      return {
        loading: false,
        error: "",
        post: action.payload
      };
    case "FETCH_ERROR":
      return {
        loading: false,
        error: "データ取得に失敗",
        post: {}
      };
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/posts/1")
      .then((res) => {
        dispatch({ type: "FETCH_SUCCESS", payload: res.data });
      })
      .catch(() => {
        dispatch({ type: "FETCH_ERROR" });
      });
  }, []);

  return (
    <div>
      <h1>{state.loading ? "Loading..." : state.post.title}</h1>
      <p>{state.error ? state.error : null}</p>
    </div>
  );
};

export default App;

実行結果

b2e512d2cb1ee20debac0bb334563ba6.gif

非同期処理なので、時間差で 「Loading」がtitleに変わっていることがわかります。

シーン別 useState と useReducer 使い分け

結論、シンプルなstateにはuseStateが向いていて、複雑なstate管理にはuseReducerがいいでしょう。

表にして、特徴をまとめました。

useState useReducer
扱うといい state の型 Number, String, Bool などのプリミティブ型を更新する時 Array, Object の更新に使う時
どんなシーンで使うといい? 単独のstate(複数でもできるっちゃできる) 複数のstateを同時に更新したい時
stateの更新に複雑なビジネスロジックがある場合 好ましくない 好ましい
ストアーの種類 ローカルな管理に向いてる useContextと合わせてグローバルに管理するとよい

以上です!これで最後です。

連載の最後に

シリーズ全部読んでくれた方へ。

どうもありがとうございました。

参考

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

React DnDライブラリを使ってみた

現職でReactとJS(今はTS)だけであらゆる機能を実装してきましたが、最近アサインされる機能がライブラリやAPI抜きでは太刀打ちできないケースが増えてきていて、実際去年の秋に担当したある機能は外部APIを使って実装しました。

ライブラリを選定して目指す機能が実現できるかの可否を判断する「技術検証」を頼まれる事が増えてきていますが、如何せんライブラリを導入した経験がまだ浅いので、選定→インストール→実装までを一度自分で経験してライブラリを扱う勘所を掴んでおきたいと思いました。

そこで目をつけたのが「ドラッグ&ドロップ」

どんなアプリでも幅広く使われるこのUIは3年前プログラミングの勉強してる時にプレーンなJSで実装した事もありましたが(記事)開発現場ではライブラリを使う事がほとんどだと思います。

前置きが長くなりましたが、本題に入ります。

まずドラッグ&ドロップを実装する上でどのライブラリがいいかの選定、そのライブラリでやろうとしてる事が実現できるかの検証をこの記事で紹介します。

ライブラリの選定:

まずライブラリはドキュメントが豊富か、と関連する記事が多いかで判断し、このReact DnDを選定しました。
https://react-dnd.github.io/react-dnd/about

react-beautiful-dndも候補にありましたが、情報量ではReact DnDには敵わなかったです。

今回実現したい操作:

今回自分がやろうとしたのはソリティアでカードをドラッグして移動したいカードにドロップした時に何がどこにドロップされたのか?のデータを受け取り、それを元に移動の許可判定をするという処理です。ただ単にリストをソートして順番を入れ替えるとか、カラムに移動させるだけだったら公式ドキュメントのDemoに載ってるコードをそのまま使えば事足りますが、こういう実装のサンプルはなかなか無いので、公式ドキュメント、参考になりそうな記事を漁って見つけた点の情報を徐々につないでいき、なんとかこのライブラリの使い方を掴みました。GitHubにサンプルが上がっていてもバージョンが古かったりして苦戦しました。。

まずこのライブラリの登場人物は3つ。

1. DnD Provider

Drag SourceとDrop Targetをラップする大元の親Component

2. Drag Source

ドラッグして動かすもの

3. Drop Target

ドロップを受け付ける場所。

実際のコードはこんな感じです。細かいところは省いてます。
大元の親となるDnD ProviderがApp.js

import React from 'react';
import { HTML5Backend } from 'react-dnd-html5-backend'
import { DndProvider } from 'react-dnd'

const App = () => {
  return (
    <DndProvider backend={HTML5Backend}>
      <>
        //この中にDragSourceとDropTragetを置く
      </>
    </DndProvider>
  )
};

動かすDrag SourceのCard.js (これでDraggableになる)

import React, { Component } from 'react';
import { DragSource } from 'react-dnd';

const itemSource = {
  beginDrag(props) {
    return {
      card: props.card,
      parentIndex: props.parentIndex,
      isFromFoundation: props.isFromFoundation
    };
  },
  canDrag(props) {
    retrun props.card.face;
  }
}

function collect(connect, monitor) {
  return {
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging(),
  }
}

class Card extends Component {
  render() {
    const { isDragging, connectDragSource, card, index } = this.props;
    const opacity = isDragging ? 0 : 1;

    //returnする大元の親は必ずプレーンなdivでくくる必要がある、StyledComponentなどは不可
    return connectDragSource(
      card.face ? (
        <div style={{ opacity }}><CardImage index={index} src={`${process.env.PUBLIC_URL}/${card.pattern}/${card.number}.png`} /></div>
      ) : (
        <div><CardImage index={index} src={`${process.env.PUBLIC_URL}/cardFace.png`} /></div>
      )
    );
  }
}

export default DragSource('item', itemSource, collect)(Card);

Drop TargetのSingleColumn.js

import React, { Component } from 'react';
import { DropTarget } from 'react-dnd';

const itemSource = {
  drop(props, monitor) {
    const dragProps = monitor.getItem();
    //↑でCard.jsでbeginDragでreturnしたオブジェクトが取れる
    return props.onDrop(dragProps, props.index);
  }
}

function collect(connect, monitor) {
  return {
    connectDropTarget: connect.dropTarget(),
    hovered: monitor.isOver(),
    item: monitor.getItem(),
  }
}

class SingleColumn extends Component {
  renderCards = (column, parentIndex) => {
    return column.map((card, index) => {
      return <Card index={index} parentIndex={parentIndex} key={card.id} card={card} />
    })
  }
  render() {
    const { connectDropTarget, column, index } = this.props;

    //returnする大元の親は必ずプレーンなdivでくくる必要がある、StyledComponentなどは不可
    return connectDropTarget(
      <div>
        <Column index={index}>
          {this.renderCards(column, index)}
          <ColumnFrame />
        </Column>
      </div>
    );
  }
}

export default DropTarget('item', itemSource, collect)(SingleColumn);

SingleColumn.jsをまとめるColumn.js

const Columns = () => {
  const { state, dispatch } = useContext(store);

  const onDrop = (dragProps, targetIndex) => {
    //dragPropsにはCard.jsのbeginDragでreturnしたオブジェクトが入ってます
    {
      card,
      parentIndex,
      isFromFoundation
    };
   //これらの情報を元に判定処理をここで行います
  }

  const renderColumns = () => {
    return state.columns.map((column, index) => {
      return <SingleColumn index={index} key={index} column={column} onDrop={onDrop} />
    })
  }

  return (
    <>
      {renderColumns()}
    </>
  )
};

export default Columns;

DragSourceとDropTargetの関係を図にまとめるとこんな感じです。
スクリーンショット 2021-03-14 15.12.18.png

この仕組みを応用すれば画像、動画などを管理するページとかで、フォルダAをフォルダBに移動とかという実装にも応用できそうです。

ちなみにこちらが今回このドラッグ&ドロップを組み込んだソリティアです。
https://upbeat-panini-974f90.netlify.app/
まだバグありますが、ドラッグドロップでカードの移動はできます。

ソースコードはこちら
https://github.com/eiciemm/react-solitarire/tree/main/src

まとめ:

ライブラリはフレームワーク程じゃ無いにしても使いこなすのにルールを覚える点が少し似てると思いました。そしてルールを完璧に掴めば応用が効いて変わった使い方やカスタマイズも容易にできる。もちろんも限度があるでしょうが。

ライブラリは力が無いエンジニアが使うものというイメージがありました。ドキュメントで公開されてるDemoと全く同じように使うのであればそれはラッキーで、浮いた工数を他の部分の作り込みに時間を割けばいいと思うし、Demoと違う使い方をする場合はライブラリのルールを理解しないといけないので、これが容易い作業では無いので、ライブラリ=手抜きとは一概には言えないかなと思いました。

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

JavaScriptでクリップボードにコピーする機能を作成

概要

JSでクリップボードにコピーする機能を作成する方法を調べた際、navigatorのwriteTextメソッドを使用すると可能とあったのですが、HTTP環境だと使用できないので別の方法を調べました。
またVue.jsで処理している値を取得したかったので、どちらも可能な方法を調べました。
そもそもクリップボードにコピーする機能はライブラリを導入する方法もあったのですが、今回はライブラリを導入しない方法を探しました。

環境

PHP:v7.3.11
Vue:v2

実装

  • クリップボードにコピーするメソッド(ViewModelクラスに記載)
public copyClipBoard(str: string) {
    const targetSentence = str;
    let input = document.createElement('input');
    input.readOnly = true;
    document.body.appendChild(input);
    input.value = targetSentence;
    input.select();
    document.execCommand('copy');
    document.body.removeChild(input);
}
  • 今回はボタンを押した時にコピーできるようにしたかったので、トリガー用のメソッドを記載
doCopy() {
    _viewModel.copyBoard("コピーしたいテキスト");
}
  • HTMLの実装
<button class="copy-clipboard" @click="doCopy">クリップボードにコピー</button>

まとめ

  • 今回はJSでクリップボードにコピーする方法についてまとめました。
  • 2行くらいでシンプルにかけるかと思っていたのですが、意外と量が多かったため記事にしました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

useStateとか、Hookとかに拒否反応を示していた私が開眼した件?

useStateってよく目にするけどよく分からない(嫌い?)

私はプログラミング初学者です。現在はReactでTODOリストなど簡単なコードの実装をして勉強しています。
勉強する中で、「useState」という文字をよくコード内で目にするのですが、正直この子の正体がよく分かっていません。よく分かっていないまま勉強を進めてきたら、useStateさんを目にする度に拒否反応を示す私がいることに気づきました^^;
ここで克服しなければ!useStateさんと仲良くならなければ!と思い、今自身で調べながらQiitaへ投稿してみます。同じようにuseStateさんとまだ仲良くなれていない初学者の方の手助けになれば幸いです。
本投稿は、私個人の理解と偏見と感覚で作成していますので、多少単語の意味合いに相違があるかもしれませんが、ご理解ください?

開発環境

  • MacOS

  • VScode

  • React

  • Github

  • Chrom

useStateって一体何者なのか

Hook(フック)です。フックはいろんな種類があるのですが、そのうちの一つです。
この記事の下の方にHookをいくつか書きましたので参照ください。

Hook(フック)って一体何者なのか

フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。
フックとは、関数コンポーネントに state やライフサイクルといった React の機能を “接続する (hook into)” ための関数です。フックは React をクラスなしに使うための機能ですので、クラス内では機能しません。
要は、useStateはフックなんです、フックはいろんな種類があります。

  • 基本のフック
    useState
    useEffect
    useContext

  • 追加のフック
    useReducer
    useCallback
    useMemo
    useRef
    useImperativeHandle
    useLayoutEffect
    useDebugValue

とりあえず、初学者は上記のフックが存在するということだけ何となく分かっていればいいのではないでしょうか。私は今のところ、useStateしか知りません?

クラスコンポーネント と 関数コンポーネント

  1. まず、コンポーネントって何?
    コンポーネントとは、「(何かの)部品」。難しく考えず、そう信じていれば大丈夫。混乱するから初学者はこれ以上突き詰めなくてよし。
    関数コンポーネントは、簡潔なコードが書ける。だからクラスコンポーネントより好まれる傾向にある。

  2. クラスコンポーネント
    昔(そんなに昔でもないけど)は主流だったが、今はサブキャラとなったコンポーネント。

  3. 関数コンポーネント
    近年登場したコンポーネント。クラスコンポーネントに代わり今の主流となっている。useStateは、関数コンポーネントの派閥に属する。
    (関数コンポーネントは、Hookを使える⇨useStateはHookの一つ。)

  4. とりあえずの結論
    関数コンポーネントをどんどん使お!

useStateを使った実装例(超簡単なコード)

  • つべこべ言わずに公式を覚えましょう

【公式1】 setState関数をインポートする

import React, {useState} from 'react';

【公式2】useState関数を定義する

const [name, setName] = useState('yui');

【公式3】useState関数の呼び出し

<input /**中略**/ onClick = {()=> setName(name)} />
  • コード(useStateを使って適当にコードを書いてみました。)
import React, {useState} from 'react';
import "./styles.css";

const App = () => {
const [name, setName] = useState('yui');
  return (
    <div className="App">
      <h1>こんにちは、{name}さん</h1>
      <button onClick = {()=> setName(name + 'i')}>ボタン</button>
    </div>
  );
};

export default App;

  • ボタンを押すと名前の「yui」の「i」の字が増えるというなんの役にも立たないやつ(笑)です。

image.png

  • ボタンを1回押すとこんな感じ

image.png

  • ボタンを連打するとこんな感じ?

image.png

タイトルには「開眼した!」って書きましたが、まだ目は半開きです。もっと勉強します!!?

参考文献

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

【クソサイト】世界一情報量が多いサイト【個人開発】

スクリーンショット 2021-03-13 200346.png

はじめに

情報量が多い画像、情報量が多い動画などはたくさんあるのに「情報量が多いサイト」というのは見つからなかったので作ってみました。

概要

為替
ニュース
名言
じゃんけん
投稿
情報量の多い画像
日付け
イカれた鳥
などの情報が載ってます。

あと隠しページがありますので暇な人は探してみてください。そこそこむずいです。

工夫した点

APIを大量に使う

大量と言っても3つですが
NewsAPI...ニュース
exchangeratesapi...為替
名言教えるよ...名言

を使いました。

色使い

原色、反対色、相性の悪い色
をあえて大量に使いまとまりをなくしました。

画像

あえてリサイズをせずサイズをバラバラにしてみました。

動き

スライドショーやイカれた鳥を使い動きを持たせさらに混乱するようにしました。

まとめ

深夜テンションで決めたネタを続投しないよう法が良い

ツイッターやってます
https://twitter.com/yamada1531

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

【初心者でもわかる】jQueryで作る「再生停止ボタン付き自動スクロール機能」

どうも7noteです。再生停止ボタン付き画面の自動スクロール機能の実装方法

jQueryを使った、画面の自動スクロールを作っていきます。
シンプルな機能ですが、再生停止を操作できるような作りなので、サイトをみた人が自分で自動スクロールさせるかどうかを決めることができます。

画面自動スクロールの作り方

index.html
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>

<div class="btn" id="stop">STOP!</div>
<div class="btn" id="start">START!</div>
style.css
.box {
  width: 100%;        /* 横幅いっぱい */
  height: 100px;      /* 高さを300pxに指定 */
  background: #aaa;   /* 背景色を灰色にする */
  margin: 50px 0;     /* 上下に適度な余白 */
  position: relative; /* 相対位置の設定 */
}

.btn {
  color: #FFF;        /* 文字色を白に指定 */
  width: 80px;        /* 適当な大きさに指定 */
  height: 80px;       /* widthと同じ高さを指定 */
  text-align: center; /* 文字を中央揃えにする */
  line-height: 80px;  /* 行間を高さと一緒にして上下中央に文字を配置 */
  border-radius: 50%; /* 角丸で円形にする */
  position: fixed;    /* 絶対位置に指定 */
  right: 10px;        /* 画面の右から10pxの位置に指定 */
  bottom: 10px;       /* 画面の下から10pxの位置に指定 */
  cursor: pointer;    /* ホバー時のカーソルを指の形にする */
}

/* スタート、ストップのボタンの色指定 */
#start {background: #6c6;}
#stop {
  display: none;  /* stopははじめ非表示に指定 */
  background: #f66;
}
#start:hover {background: #8e8;}
#stop:hover {background: #f99;}
script.js
// 初期値を設定
var speed = 1; //スクロール量(1 = 1px)
var interval = 50; //スクロール間隔(1000 = 1秒)
var scrollTop = $(window).scrollTop(); // 現在のスクロール量を計測

// スタートボタンがクリックされた時
$('#start').off('click');  // on clickの重複防止のため
$('#start').on('click', function(){

  /* スタートボタンを非表示にして、再生ボタンを表示 */
  $(this).hide();
  $('#stop').show();

  var scroll = scrollTop + speed; // 次の移動先までの距離を指定
  var scroll = setInterval(function() {
    window.scrollBy(0, scroll);   // スクロールさせる
    $('#stop').off('click');      // on clickの重複防止のため

    //スクロール中に停止ボタンが押された時
    $('#stop').on('click', function(){  
      clearInterval(scroll);      // setIntervalの処理を停止
      $(this).hide();             // 停止ボタンを非表示にして、
      $('#start').show();         // 再生ボタンを表示
    });

  },interval);  // setIntervalを変数intervalの間隔で繰り返す。
});

解説

大きく分けると2つのプログラムが動いています。
自動スクロールを行う部分と、その自動スクロールをボタンで制御する部分に分かれており、これらのプログラムの動作の順序を整理して1つのjavascriptにしているようなイメージです。

流れを書くと、

①再生ボタンを押す
②自動スクロールが始まり、ボタンが停止ボタンに切り替わる
③停止ボタンを押す
④自動スクロールを停止させ、ボタンが再生ボタンに切り替わる

これを繰り返している状態です。

まずはじめはスクロールがされていない状態です。このときに初期値が設定されます。
次に再生ボタンが押された場合、という処理を書きます。on()メソッドを使い処理を書いています。
ボタンが押されたら、ボタンの表示を切り替えたのち、自動スクロールを始めます。
自動スクロールの処理はsetInterval()をつかっており、これは処理を一定速度で繰り返すことができます。これを使い0.X秒ごとにXpxうごかすという処理を連続して行うことで自動スクロールが行われているような状態になります。
そして、自動スクロールを起こしているsetInterval()を無効化させるにはストップボタンを押します。ストップボタンが押された場合、ボタン表示を切り替えつつclearInterval()を使うことでsetInterval()の処理を停止させることができます。

まとめ

onの処理だとクリックイベントが重複してしまうので、ofF()を使い重複対策をしています。
写真などをゆっくり眺めるようなサイトにはこういう自動スクロール機能とかがあってもいいかもしれませんね。

参考:https://ti-tomo-knowledge.hatenablog.com/entry/2018/05/08/083500

おそまつ!

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

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

難読化とMinifyについて

はじめに(調べるに至った経緯)

ども!ワンピースの光月おでんがカッコよすぎて日々アニメを漁りながら自宅待機中の光月ギノキンです。

(・・・ではでは)

さっそく調べるに至った経緯ですが、近頃はフロント業務をやらせてもらっていて、Sentry(監視ツール)に届いたエラーをchromeのdevツールで検証しているのですが、devツール上でmain.jsの中身を見てみるとソースコードの記述とは違う乱数があり、

なんだこれ。。エラー追えないやん。。。

ってなったので調べてみると、「難読化」、「Minify」を行っているらしかったので調べるに至りました。

難読化とは

プログラマーがソースコードなどを意図的に読みにくく記述することで、可読性を著しく下げたり、解析し辛くする技術を「難読化」と呼びます。プログラムやアルゴリズムを隠したり不明瞭にすることにより、攻撃者にソフトウェアプログラムをリバースエンジニアリングされることを防止できます。
参照元:「難読化」でリバースエンジニアリングを阻止しよう!

メリット

  • 可読性を下げることよってリバースエンジニアリングを防止することができる
  • 盗難防止のためにソースコードの公開を避ける
  • サイズが小さくなるのでデータの読み込み速度が向上する
  • 変数名が小さくなるのでローディング速度が向上する
  • メモリ使用量が減少するため、キャッシュ回数が減り、速度が向上する

リバースエンジニアリング・・・ソフトウェアを厳密に分析しソースコードなどの情報を明らかにする行為のこと

デメリット

  • デバックが複雑になる
    • JSファイル全て難読化せず、関数の中だけを難読化する =>function毎に分けて難読化することで、それぞれ管理しやすくなり、デバックもしやすくなる。

Minify(ミニファイ)とは

JavaScript(以下、JSと略記)やCSSのコード内の不要な改行やインデントを削除して、動作はそのままの状態で、圧縮・軽量化することをいいます。
参照元:Minify(ミニファイ)とは何か?圧縮方法とJS・CSSの軽量化のメリットを解説

メリット

  • webページ速度が上がる
  • ソースコードの解読を難しくし、盗用防止

デメリット

  • デバック時の可読性が下がる

難読化とMinifyの違い

調べたが大きな違いは無いようで、理解としては下記の様な理解で問題なさそう。

難読化とは
変数名を乱数で短縮したり、可読性を下げて盗用を防止する

minifyとは
人間用のコメントやインデントなどを消して無駄を無くす

最後に

難読化については、関数単位で難読化すればデバックしやすくなるみたいなので、もうちょい調べてみようと思います。
セキュリティ面やパフォーマンスを維持しつつ、保守性も考えて開発をしていけたらみんなハッピー!!

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

JavaScriptでrange関数を書く! ~連番と逆順~

はじめに

前回こんな記事を書きました。

  • 最短コードでrange(start,stop,step)を実装する。
  • 出力はリスト・イテレータを問わない
  • 負の値は考慮しない(start, stop, step, 出力する連番すべて0より大きいとする)

実装でかなりいろんなアイデアを出せたんですが、どれもPythonの負のステップを実現できていません。

Pythonのrange関数はかなり便利で、特にstepに-1を入れると連番リストを逆順にして返してくれるという機能があります(正確にはリストではないですが)。

前回の記事でもこの点が実装できてないことを指摘され、自分でもそれが痛いなぁと感じていました。それからというもの、ことあるごとにJavaScriptでのPython range関数の実装を考えるようになっていました。しかし今回、ついに?ワンライナーでのそれっぽい実装を思いついたので、記しておきます。

本題

まずコードから。

var f=(a,b,c)=>(b-=a+1)&&(([d,e])=>[...Array((b-b%c)/e+1)].map((_,i)=>d+i*c))(c>0?[a,c]:[b+a-b%c,-c])

ワンライナーを意識して書きたかったのですが、かなり長くなりました。101文字で、減算代入を使っているので関数の形でしか使えません。前回の最短、ジェネレータ記法が45字だったことを考えると、随分長いですね。

ロジックを読みやすいように、書き下してみます。

function range(start, stop, step) {
  stop -= start + 1;

  const inner = ([startValue, positiveStep]) => {
    const length = (stop - (stop % step)) / positiveStep + 1;
    return [...Array(length)].map((_, i) => startValue + i * step);
  };

  if (step > 0) {
    return inner([start, step]);
  } else {
    return inner([stop + start - (stop % step), -step]);
  }
}

結果

image.png

あまり本腰を入れてテストしたわけではないですが、どうやら動いてそうですね。

解説

inner関数が機能の中心です。等差数列の式初項 + 公差 * indexをここで計算しています。

しかし難しいのはそれ以外の場所で、

  • 末項がstopよりも手前のときの、末項の値
  • stepが負の時の、配列の長さ
  • stepが負の時の、初項の値

などに場合分けや剰余算を使って対処しています。

ショートハンド版ですが、減算代入の組み込みに苦労し、文字数をかなり使っています。その上実は、stop == start + 1のときだけfalseが返るというバグもそこで組み込んでしまっています。w 全体的にテクニックを盛り込めず、長くて率直な書き方になってしまったのでそこが要改善ですね。

しかし今回は、range関数の複雑な処理をそれっぽく実装するというのが趣旨だったので、短縮アイデアには前回ほど凝っていません。

短縮アイデア、別の実装、バグなどなど、思いついたらぜひ教えてください。
以上です。

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

入力チェックエラーをNERV本部警告アラート風にして、サクッと人類を補完しちゃおう!!エヴァ劇場公開記念

人の造りしもの WEBシステム

それが人類の脅威になってはいけません!

WEBシステムは人類に無限の可能性を示唆する福音になる必要があるのです!
(作品が違う。)

ではどうするか!?

そうです!
入力チェックエラーの時にNERV本部警告アラートを出すしかありません!

左記のアラートにより、人類が己の過ちに気付き正すことができます。
そして、どこまでが自分でどこからか他人かがわからないほどに正しい入力が行えるセカイ(WEBシステム)になるのです!!

これこそまさに人類補完WEBシステムの究極!!!!!!!

はじめるぞ、冬月。

要約

CSSとJavaScriptだけで、NERV本部のモニターに出る警告アラートを出したい!!

では、やっていきましょう!

NERV本部の警告アラートは六角形の形がベース

まずは、警告アラートがどんな感じだったかを画像で検索すると、
赤い六角形をベースに形を作っていることがわかります。
5698dbfa.png
この画像は、どなたか知らないのですが、パズドラのブログのようです。

六角形を作る:※PGは一番最後に載せます。

まずはベースとなる六角形を作っていきます。

参考にしたのはこのサイト

長方形の組み合わせで六角形を作るわけですな。
こんな感じ
無題.jpg
で長方形を組み合わせます。
(雑な絵ですいません。。。)

そして出来たのがこの形
エヴァ六角形.jpg
うぉーー!NERV感出まくり!
テンション上がるーー!!
外枠の六角形と中の六角形は別々の六角形で作っています。
中の三角形や文字列も別々のdivで指定しています。

六角形をベースにした警告文表示本体を作る

警告文を表示する本体を作ります。

六角形を組み合わせたこんな形
完成イメージ.jpg
をまずcssで作ってからいい感じに色付け。

NERV感を出すために、jsを使う

NERV本部の警告演出を映像で確認して、どう動かすかを検討。
まぁjs使うしかないなと考え、jsで警告の演出をしていきます。
考えた演出は以下。
・六角形のEMERGENCYを一つずつ順々に出す・消す。
・警告文言は点滅する。

よし、これで演出も出来た!

NERVアラート...? 完成していたの...?

そして、出来たものがこちら!!

NERVアラート2.gif

NERVアラート.gif

勝ったな。。。

どうですか、このNERV警告アラート!
自己の過ち(エラー内容)がすぐに確認できる!

デメリットとしては、ビックリしすぎて倒れちゃう可能性があるくらいですかね?(笑)

素CSS・素javascript・その他

jqueryや何かのフレームワークを使おうかなとも考えたのですが、あんまりフレームワークによる影響がでても困るなと思いまして、素CSS・素javascriptで作っています。
また、思いついてすぐ作ったのでレスポンシブみたいなシャレた仕組みになっていません。

あと、表示はWin10のchromeでしかチェックしてません。
たぶんfontの大きさの解釈が違うと思うので、ちょっとずれると思います。
githubにも上げますので、興味があれば好きに加工してください!

ちなみにテキストボックスとボタンのデザインはここから持ってきています。

映画を見て。。。

無性に作りたくなっちゃいましたね。
私の思春期にズドンと影響を与えたアニメで、
オジサンになっても見に行って、
しかもこれ作ってるんだから。

私はエヴァがかなり好きなようです。

ありがとうございました。

こちらがgithub!
好きに使ってください!

CodePenも

See the Pen NERV ALERT by Kei Saito (@3S-Laboo) on CodePen.

ソース

js/excessAlert.js
/**
 * NERV警告アラート
 * ※かなり雑に書いてることをお許しください。
 * written by 3s.laboo
 */

/** 警告アラートの周りに表示する六角形のテンプレート */
var hexagon_alert_template =
  "<div class='hexagon__alert' style='top:$top$px;left:$left$px;'><div class='hexagon__common hexagon__alert__hexagon1'><div class='hexagon__border__common hexagon__left__border-1'></div><div class='hexagon__border__common hexagon__left__border-2 hexagon__rotate-1'></div><div class='hexagon__border__common hexagon__left__border-3 hexagon__rotate-2'></div><div class='hexagon__inner__common'></div><div class='hexagon__inner__common hexagon__rotate-1'></div><div class='hexagon__inner__common hexagon__rotate-2'></div></div><div class='hexagon__common hexagon__alert__hexagon2'><div class='hexagon__border__common hexagon__up__border-1'></div><div class='hexagon__border__common hexagon__up__border-2 hexagon__rotate-1'></div><div class='hexagon__border__common hexagon__up__border-3 hexagon__rotate-2'></div><div class='hexagon__inner__common'></div><div class='hexagon__inner__common hexagon__rotate-1'></div><div class='hexagon__inner__common hexagon__rotate-2'></div><div class='triangle__common triangle__up triangle__up__alert'></div></div><div class='hexagon__common hexagon__alert__hexagon3'><div class='hexagon__border__common hexagon__down__border-1'></div><div class='hexagon__border__common hexagon__down__border-2 hexagon__rotate-1'></div><div class='hexagon__border__common hexagon__down__border-3 hexagon__rotate-2'></div><div class='hexagon__inner__common'></div><div class='hexagon__inner__common hexagon__rotate-1'></div><div class='hexagon__inner__common hexagon__rotate-2'></div><div class='triangle__common triangle__down triangle__down__alert'></div></div><div class='hexagon__common hexagon__alert__hexagon4'><div class='hexagon__border__common hexagon__middle__border'></div><div class='hexagon__inner__common hexagon__inner__middle'></div></div><div class='hexagon__common hexagon__alert__hexagon5'><div class='hexagon__border__common hexagon__up__border-1'></div><div class='hexagon__border__common hexagon__up__border-2 hexagon__rotate-1'></div><div class='hexagon__border__common hexagon__up__border-3 hexagon__rotate-2'></div><div class='hexagon__inner__common hexagon__inner-1'></div><div class='hexagon__inner__common hexagon__inner-2 hexagon__rotate-1'></div><div class='hexagon__inner__common hexagon__inner-3 hexagon__rotate-2'></div><div class='triangle__common triangle__up triangle__up__alert'></div></div><div class='hexagon__common hexagon__alert__hexagon6'><div class='hexagon__border__common hexagon__down__border-1'></div><div class='hexagon__border__common hexagon__down__border-2 hexagon__rotate-1'></div><div class='hexagon__border__common hexagon__down__border-3 hexagon__rotate-2'></div><div class='hexagon__inner__common'></div><div class='hexagon__inner__common hexagon__rotate-1'></div><div class='hexagon__inner__common hexagon__rotate-2'></div><div class='triangle__common triangle__down triangle__down__alert'></div></div><div class='hexagon__common hexagon__alert__hexagon7'><div class='hexagon__border__common hexagon__right__border-1'></div><div class='hexagon__border__common hexagon__right__border-2 hexagon__rotate-1'></div><div class='hexagon__border__common hexagon__right__border-3 hexagon__rotate-2'></div><div class='hexagon__inner__common'></div><div class='hexagon__inner__common hexagon__rotate-1'></div><div class='hexagon__inner__common hexagon__rotate-2'></div></div><div class='hexagon__alert__warning__common hexagon__alert__warning__side hexagon__alert__warning__left hexagon__alert__warning__top'>警告</div><div class='hexagon__alert__warning__common hexagon__alert__warning__side hexagon__alert__warning__left hexagon__alert__warning__bottom'>警告</div><div class='hexagon__alert__warning__common hexagon__alert__warning__side hexagon__alert__warning__right hexagon__alert__warning__top'>警告</div><div class='hexagon__alert__warning__common hexagon__alert__warning__side hexagon__alert__warning__right hexagon__alert__warning__bottom pointer' onclick='closeExcessAlert()'>閉幕</div><div class='hexagon__alert__warning__common hexagon__alert__warning__head'>警告</div><div class='hexagon__alert__message'><span class='blinking'>$alertStr$</span></div></div></div>";
/** 警告アラートの本体のテンプレート */
var hexagon_element_template =
  "<div class='hexagon' style='top:$top$px;left:$left$px;'><div class='hexagon__common'><div class='hexagon__border__common hexagon__border'></div><div class='hexagon__border__common hexagon__border hexagon__rotate-1'></div><div class='hexagon__border__common hexagon__border hexagon__rotate-2'></div><div class='hexagon__inner__common hexagon__inner-1'></div><div class='hexagon__inner__common hexagon__inner-2 hexagon__rotate-1'></div><div class='hexagon__inner__common hexagon__inner-3 hexagon__rotate-2'></div><div class='triangle__common triangle__up triangle__up__hexagon'></div><div class='triangle__common triangle__down triangle__down__hexagon'></div><div class='hexagon__inner__emergency'>EMERGENCY</div></div></div>";
/** 六角形の表示する位置を配列で定義 */
var hexagon_position_array = [
  { top: 35, left: 30 },
  { top: 35, left: 280 },
  { top: 35, left: 529 },
  { top: 35, left: 777 },
  { top: 107, left: 155 },
  { top: 107, left: 405 },
  { top: 107, left: 653 },
  { top: 180, left: 30 },
  { top: 180, left: 777 },
  { top: 320, left: 30 },
  { top: 320, left: 777 },
  { top: 392, left: 155 },
  { top: 392, left: 405 },
  { top: 392, left: 653 },
  { top: 464, left: 30 },
  { top: 464, left: 280 },
  { top: 464, left: 529 },
  { top: 464, left: 777 },
];
/** 警告アラートの本体を表示する位置を定義 */
var hexagon_alert_position = { top: 185, left: 166 };

/**
 * 初期化処理
 */
function excessAlert_init() {
  // <div id='alert_cover'></div>を配置
  let alert_cover = document.createElement("div");
  alert_cover.id = "alert_cover";
  document.body.appendChild(alert_cover);
}

/**
 * NERV警告アラートを表示する
 * @param alert_str 警告文
 */
function openExcessAlert(alert_str) {
  // html内のalert_coverを取得
  let parentDiv = document.getElementById("alert_cover");
  // 黒い幕の大きさ・位置を指定
  parentDiv.style.width = "960px";
  parentDiv.style.height = "620px";
  parentDiv.style.top = "0";
  parentDiv.style.left = "0";
  // 内部を初期化
  parentDiv.innerHTML = "";
  // NERV警告アラート本体を取得 且つ 警告文と表示位置を置換
  let hexagon_alert = hexagon_alert_template
    .replace("$alertStr$", alert_str)
    .replace("$top$", hexagon_alert_position.top)
    .replace("$left$", hexagon_alert_position.left);
  setTimeout(() => {
    // NERV警告アラート本体を配置
    parentDiv.innerHTML = parentDiv.innerHTML + hexagon_alert;
    // 六角形を順々に配置していく
    // タイマーを置くことで、あの映画感を出す
    hexagon_position_array.forEach((hexagon_position, index) => {
      setTimeout(() => {
        // 六角形のテンプレートを取得 且つ 表示位置を置換
        let hexagon_element = hexagon_element_template
          .replace("$top$", hexagon_position.top)
          .replace("$left$", hexagon_position.left);
        // 画面に表示していく
        parentDiv.innerHTML = parentDiv.innerHTML + hexagon_element;
      }, 20 * index);
    });
  }, 10);
}

/**
 * NERV警告文を消す
 */
function closeExcessAlert() {
  // html内のalert_coverを取得
  let parentDiv = document.getElementById("alert_cover");
  // 順々に消していく
  setTimeout(() => {
    // 黒い幕をサイズを縮小して消す
    parentDiv.style.width = "0";
    parentDiv.style.height = "0";
    // NERV警告文本体を消す
    let hexagonAlert = document.getElementsByClassName("hexagon__alert")[0];
    hexagonAlert.remove();
    // 周りに配置している六角形の要素を取得
    let hexagonArray = Array.prototype.slice.call(
      document.getElementsByClassName("hexagon")
    );
    // 順々に消していく
    // タイマーを置くことで、あの映画感を出す
    hexagonArray.forEach((hexagon, index) => {
      setTimeout(() => {
        hexagon.remove();
      }, 20 * index);
    });
  }, 10);
}
css/excessAlert.css
#alert_cover {
  background-color: #000;
  z-index: 2147483647;
  position: absolute;
}
.hexagon {
  position: absolute;
}
.hexagon__alert {
  position: absolute;
  display: block;
}
.hexagon__common {
  position: absolute;
  width: 80px;
}
.hexagon__common::before {
  display: block;
  padding-top: 173.205080757%; /* 3 / √3 * 100 */
  content: "";
}
.hexagon__alert__hexagon1 {
  top: 65px;
  left: 0;
}
.hexagon__alert__hexagon2 {
  top: 0;
  left: 113px;
}
.hexagon__alert__hexagon3 {
  top: 130px;
  left: 113px;
}
.hexagon__alert__hexagon4 {
  top: 65px;
  left: 227px;
}
.hexagon__alert__hexagon5 {
  top: 0;
  left: 363px;
}
.hexagon__alert__hexagon6 {
  top: 130px;
  left: 363px;
}
.hexagon__alert__hexagon7 {
  top: 65px;
  left: 477px;
}
.hexagon__border__common {
  position: absolute;
  top: 0;
  left: 37px;
  width: 100%;
  height: 100%;
}
.hexagon__inner__common {
  position: absolute;
  top: 4px;
  left: 39px;
  width: 95%;
  height: 95%;
  background-color: #e71f1f;
}
.hexagon__inner__middle {
  left: 0;
  width: 217%;
}

.hexagon__rotate-1 {
  transform: rotate(120deg);
}
.hexagon__rotate-2 {
  transform: rotate(-120deg);
}
.hexagon__border__common::before,
.hexagon__border__common::after {
  position: absolute;
  height: 2px;
  content: "";
  background-color: #e71f1f;
}
.hexagon__border__common::before {
  top: 0;
}
.hexagon__border__common::after {
  bottom: 0;
}
.hexagon__border::before,
.hexagon__border::after {
  left: 0;
  right: 0;
}
.hexagon__left__border-1::before,
.hexagon__left__border-1::after {
  left: 0;
  right: 3px;
}
.hexagon__left__border-2::after {
  left: 0;
  right: 0;
}
.hexagon__left__border-3::before {
  left: 0;
  right: 0;
}
.hexagon__right__border-1::before,
.hexagon__right__border-1::after {
  left: 2px;
  right: 0;
}
.hexagon__right__border-2::before {
  left: 0;
  right: 0;
}
.hexagon__right__border-3::after {
  left: 0;
  right: 0;
}
.hexagon__middle__border::before,
.hexagon__middle__border::after {
  left: 2px;
  right: -19px;
}
.hexagon__up__border-1::before {
  left: 0;
  right: 0;
}
.hexagon__up__border-2::after {
  left: 0;
  right: 4px;
}
.hexagon__up__border-3::after {
  left: 4px;
  right: 0;
}
.hexagon__down__border-1::after {
  left: 0;
  right: 0;
}
.hexagon__down__border-2::before {
  left: 4px;
  right: 0;
}
.hexagon__down__border-3::before {
  left: 0;
  right: 4px;
}
.triangle__common {
  position: absolute;
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
}
.triangle__up {
  border-bottom: 16px solid #000;
}
.triangle__down {
  border-top: 16px solid #000;
}
.triangle__up__alert {
  top: 50px;
  left: 70px;
}
.triangle__down__alert {
  top: 75px;
  left: 70px;
}
.triangle__up__hexagon {
  top: 10px;
  left: 70px;
}
.triangle__down__hexagon {
  top: 113px;
  left: 70px;
}
.hexagon__alert__message {
  position: absolute;
  font-family: "HGS明朝E";
  font-size: 28px;
  top: 119px;
  left: 110px;
  width: 502px;
  text-align: center;
}

.hexagon__alert__warning__common {
  position: absolute;
  font-family: "HGS明朝E";
  border: 1px solid #000;
  text-align: center;
}
.hexagon__inner__emergency {
  position: absolute;
  font-weight: bold;
  font-family: "MV Boil";
  font-size: 19px;
  top: 56px;
  left: 15px;
}

.hexagon__alert__warning__head {
  font-size: 36px;
  width: 85px;
  top: 112px;
  left: 31px;
}
.hexagon__alert__warning__side {
  font-size: 27px;
  width: 73px;
}
.hexagon__alert__warning__left {
  left: 153px;
}
.hexagon__alert__warning__right {
  left: 403px;
}
.hexagon__alert__warning__top {
  top: 10px;
}
.hexagon__alert__warning__bottom {
  top: 228px;
}
.pointer {
  cursor: pointer;
}

/* 点滅 */
.blinking {
  -webkit-animation: blink 0.5s ease-in-out infinite alternate;
  -moz-animation: blink 0.5s ease-in-out infinite alternate;
  animation: blink 0.5s ease-in-out infinite alternate;
}
@-webkit-keyframes blink {
  0% {
    opacity: 0;
  }
  40% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
@-moz-keyframes blink {
  0% {
    opacity: 0;
  }
  40% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
@keyframes blink {
  0% {
    opacity: 0;
  }
  40% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
css/style.css
.container {
  width: 960px;
  height: 620px;
}

.cp_iptxt {
  position: relative;
  width: 93%;
  margin: 40px 3%;
}
.cp_iptxt input[type="text"] {
  font: 15px/24px sans-serif;
  box-sizing: border-box;
  width: 100%;
  padding: 0.3em;
  transition: 0.3s;
  letter-spacing: 1px;
  color: #000;
  border: 1px solid #1b2538;
  border-radius: 4px;
}
.ef input[type="text"]:focus {
  border: 1px solid #da3c41;
  outline: none;
  box-shadow: 0 0 5px 1px rgba(218, 60, 65, 0.5);
}
.btn-square {
  display: inline-block;
  padding: 0.5em 1em;
  text-decoration: none;
  background: #668ad8; /*ボタン色*/
  color: #fff;
  border-bottom: solid 4px #627295;
  border-radius: 3px;
  margin-top: 30px;
}
.btn-square:active {
  /*ボタンを押したとき*/
  -webkit-transform: translateY(4px);
  transform: translateY(4px); /*下に動く*/
  border-bottom: none; /*線を消す*/
}
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>NERV本部警告アラート</title>
    <link rel="stylesheet" href="./css/style.css" />
    <link rel="stylesheet" href="./css/excessAlert.css" />
    <script src="./js/excessAlert.js"></script>
    <script>
      window.onload = function () {
        // excessAlert.jsを初期化
        excessAlert_init();
      };
      function checkInput() {
        let shimei = document.getElementById("shimei").value;
        let mailAddress = document.getElementById("mail_address").value;
        if (shimei == "") {
          openExcessAlert("お名前を入力してください。");
          return;
        }
        if (mailAddress == "") {
          openExcessAlert("メールアドレスを入力してください。");
          return;
        }
      }
    </script>
  </head>
  <body>
    <div class="container">
      <div class="cp_iptxt">
        <label class="ef">
          <input type="text" id="shimei" placeholder="お名前" />
        </label>
      </div>
      <div class="cp_iptxt">
        <label class="ef">
          <input type="text" id="mail_address" placeholder="メールアドレス" />
        </label>
        <a href="#" class="btn-square" onclick="checkInput()">登 録</a>
      </div>
    </div>
  </body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Hansontableのdataをオブジェクトで取得したい

Class: Core - Handsontable Documentation

getData()で二次元配列として取得できるが、オブジェクトとして欲しいときはどうするのか?

どうやらそういうメソッドは無いっぽい

How to get the data object in the handsontable, not a two-dimensional array - Getting help / Questions - Handsontable Forum

  var visualObjectRow = function(row) {
      var obj = {};
      for (var i = 0; i < hot.countCols(); i++) {
        obj[hot.colToProp(i)] = hot.getDataAtCell(row, i);
      }
      return obj
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む