- 投稿日:2020-08-30T23:24:52+09:00
簡易相関図ツールを作成した
概要
ログイン不要、無料で簡易の相関図を作れるツールを作成しました。
作成した図はimgタグ情報コピー機能があるので、htmlにそのまま貼り付けられます。
wordpressや記事投稿サイトに使用するときは画像のダウンロード、アップロードが不要です。
(文字数はすごいことになりますが・・・)
qiitaはそのまま貼り付けできないみたいです。png形式でダウンロードも可能です。
https://skz-app.tontonton.work/技術
フロントエンド
- html
- javascript
バックエンド
- Firebase
その他
- google マテリアルアイコン (https://material.io/resources/icons/?style=baseline)
なぜ作ったか
記事を書いているときに、
「説明するための図が欲しいな」
「そんなに詳細な図じゃなくていいんだけど、図を作成するのがだるい」
「作成するためのアプリを立ち上げるのが煩わしい」
「作成した図を保存→サイトにアップロードというプロセスが面倒」
という、なるべく怠けたい熱い思いから作成に至りました。
まとめ
Firebaseは初めて使用しましたが、簡単にデプロイできて胸熱ですね。
試しに使ってみてください。
- 投稿日:2020-08-30T22:59:10+09:00
高速フーリエ変換(FFT)を行列で理解する - 自分用メモ
何番煎じか分からんですが、どうしても数式が多いと読む気がなくなるので、
厳密さは棚上げして、なんで計算量が減るのかのイメージを把握する目的で、記事を残してみます。前提知識
- 三角関数を知っている
- 複素数を知っている
- オイラーの公式を知っている
- 積分を知っている1
- 行列の積の計算規則を知っている
結構なレベルの数学知識が要求される内容なんだと改めて思いますね。。
フーリエ変換
上記の前提知識があれば、下記が分かりやすいと思います。
【画像45枚あり】フーリエ変換を宇宙一わかりやすく解説してみる高速フーリエ変換
数学・工学系の記事にしてはめずらしく、Wikipediaの解説(下記)がイメージ付きやすいように思います。
- 計算したい式(離散フーリエ変換)
- 高速フーリエ変換を行列で表現上記(Wikipedia記事)の補足説明
上記のWikipediaの式(行列の計算式のとこ)で使われている記号と同じ記号を割り当てます。
W=e^{2\pi i\frac{1}{N}}とおくと、高速フーリエ変換では下記の$X_0, X_1, X_2,...,X_{N-1}$が求める対象になります。
X_t = \sum_{k=0}^{N-1}W^{k\ t}x_k例として、$N=8$のときは下記のようになります。(通常、高速フーリエ変換を適用する際は$N$は$2$のべき乗を前提とします。)
\begin{align} X_0&=W^0x_0+W^0x_1+W^0x_2+W^0x_3+W^0x_4+W^{0}x_5+W^{0}x_6+W^{0}x_7\\ X_1&=W^0x_0+W^1x_1+W^2x_2+W^3x_3+W^4x_4+W^{5}x_5+W^{6}x_6+W^{7}x_7\\ X_2&=W^0x_0+W^2x_1+W^4x_2+W^6x_3+W^8x_4+W^{10}x_5+W^{12}x_6+W^{14}x_7\\ &:\\ X_7&=W^0x_0+W^7x_1+W^{14}x_2+W^{21}x_3+W^{28}x_4+W^{35}x_5+W^{42}x_6+W^{49}x_7 \end{align}下記のようにベクトルにみたてて、
\begin{pmatrix} X_0\\ X_1\\ X_2\\ :\\ X_7\\ \end{pmatrix}と書くと、下記のような行列とベクトルの積として表現できます。左辺の行列の要素をクリックすると、関連する要素が強調表示されます。
(※下記で左辺の$x$の添え字の並びが連番ではなくなっているのは意図的なものです。)See the Pen Matrix Image of Fast Fourier Transform by kob58im (@kob58im) on CodePen.
(描画サイズが大きいので、縮小表示(0.5x)もしくはCodePen上での閲覧推奨です。)
上記の分解は、$W^N=e^{2\pi i}=1$であることにより、$W^8=1\ (=W^0)$が成り立つことを利用しています。
右辺の右側から結合させる(積を求める)のがミソのようで、
右辺の右側の行列(真ん中というべきかも(?))が、$\frac{N}{2}$次の行列2つ分相当になり、再帰的に分解ができる構造になります。
さらに、$W^{N/2}=e^{\pi i}=-W$であることを使って計算を効率化させたりするようです。実際の計算手順(バタフライ演算)と計算量について
下記が分かりやすいかと思います。
高速フーリエ変換(千葉大学 講義資料)
高速フーリエ変換を計算するだけであれば無くてもどうにかなるが、フーリエ変換自体の理解には必要 ↩
- 投稿日:2020-08-30T22:59:10+09:00
高速フーリエ変換(FFT)を行列で理解する
何番煎じか分からんですが、どうしても数式が多い1と読む気がなくなるので、
厳密さは棚上げして、なんで計算量が減るのかのイメージを把握する目的で、記事を残してみます。前提知識
- 三角関数を知っている
- 複素数を知っている
- オイラーの公式を知っている
- 積分を知っている2
- 行列の積の計算規則を知っている
結構なレベルの数学知識が要求される内容なんだと改めて思いますね。。
フーリエ変換
上記の前提知識があれば、下記が分かりやすいと思います。
【画像45枚あり】フーリエ変換を宇宙一わかりやすく解説してみる高速フーリエ変換
数学・工学系の記事にしてはめずらしく、Wikipediaの解説(下記)がイメージ付きやすいように思います。
- 計算したい式(離散フーリエ変換)
- 高速フーリエ変換を行列で表現上記(Wikipedia記事)の補足説明
上記のWikipediaの式(行列の計算式のとこ)で使われている記号と同じ記号を割り当てます。3
W=e^{2\pi i\frac{1}{N}}とおくと、高速フーリエ変換では下記の$X_0, X_1, X_2,...,X_{N-1}$が求める対象になります。
X_t = \sum_{k=0}^{N-1}W^{k\ t}x_k例として、$N=8$のときは下記のようになります。(通常、高速フーリエ変換を適用する際は$N$は$2$のべき乗を前提とします。)
\begin{align} X_0&=W^0x_0+W^0x_1+W^0x_2+W^0x_3+W^0x_4+W^{0}x_5+W^{0}x_6+W^{0}x_7\\ X_1&=W^0x_0+W^1x_1+W^2x_2+W^3x_3+W^4x_4+W^{5}x_5+W^{6}x_6+W^{7}x_7\\ X_2&=W^0x_0+W^2x_1+W^4x_2+W^6x_3+W^8x_4+W^{10}x_5+W^{12}x_6+W^{14}x_7\\ &:\\ X_7&=W^0x_0+W^7x_1+W^{14}x_2+W^{21}x_3+W^{28}x_4+W^{35}x_5+W^{42}x_6+W^{49}x_7 \end{align}下記のようにベクトルにみたてて、
\begin{pmatrix} X_0\\ X_1\\ X_2\\ :\\ X_7\\ \end{pmatrix}と書くと、下記のような行列とベクトルの積として表現できます。左辺の行列の要素をクリックすると、関連する要素が強調表示されます。
(※下記で左辺の$x$の添え字の並びが連番ではなくなっているのは意図的なものです。)See the Pen Matrix Image of Fast Fourier Transform by kob58im (@kob58im) on CodePen.
(描画サイズが大きいので、縮小表示(0.5x)もしくはCodePen上での閲覧推奨です。4)
上記の分解は、$W^N=e^{2\pi i}=1$であることにより、$W^8=1\ (=W^0)$が成り立つことを利用しています。
右辺の右側から結合させる(積を求める)のがミソのようで、
右辺の右側の行列(真ん中というべきかも(?))が、$\frac{N}{2}$次の行列2つ分相当になり、再帰的に分解ができる構造になります。
さらに、$W^{N/2}=e^{\pi i}=-W$であることを使って計算を効率化させたりするようです。実際の計算手順(バタフライ演算)と計算量について
下記が分かりやすいかと思います。
高速フーリエ変換(千葉大学 講義資料 PDF)
- 投稿日:2020-08-30T22:57:33+09:00
datetimepicker datepicker 違い
- 投稿日:2020-08-30T21:36:55+09:00
Vue の transition で entering/leaving が非対称なトランジションを実装する
Vue には
v-if
などの条件付き描画を使ってコンポーネントを表示/非表示する際に CSS の transition や animation の動きを付与できる transition ラッパーコンポーネントというものがあります。今回は表示・非表示で非対称な動きをする Vue transition と CSS の実装についてメモします。時間差でフェードイン、クリック時にフェードアウトするカード
次のようなカードの要素を作ってみます。なおカードの上にあるボタンは動作確認用のものなので、記事の本題とは関係ありません。
See the Pen vue-transition-asynchronously-fade-in/out-1 by hamakou108 (@hamakou108) on CodePen.
このカードは Vue インスタンスがマウントされてから3秒後に表示され、クリックするとフェードアウトします。
実装を見ると分かる通り、 enter-active-class と leave-active-class に別々の CSS を当てることで非対称なトランジション・アニメーションを実現しています。 leave-active-class の方は transition を利用していますが、トランジション完了時の opacity の値を leave-to-class に指定することでフェードアウトを実現できます。
時間差でフェードインした後に時間差でフェードアウトするカード
次に少しひねったパターンとして、フェードインしてしばらく時間が経つと勝手に消えるカードを作ってみます。
クリック時にフェードアウトする動作も残しておきます。See the Pen vue-transition-asynchronously-fade-in/out-2 by hamakou108 (@hamakou108) on CodePen.
enter-active-class が利用する
@keyframes
を変更して自然にフェードアウトするようにしました。
ただ@keyframes
の変更だけだと幾つか問題があります。まず enter-active-class に指定したスタイルはアニメーション終了後に削除されます。
これにより、アニメーション終了後は.card
に指定したスタイルがそのまま当たるため、visibility: visible; opacity: 1
となってフェードアウトさせたカードが再表示されてしまいます。
これを避けるため.card
にはvisibility: hidden; opacity: 0;
を設定しています。さらにこの設定によって今度は leave-active-class のトランジションの初期状態が
visibility: hidden; opacity: 0;
となるため、クリック時はフェードアウトではなく瞬時に消えてしまうような動きになってしまいます。
これを避けるため.fade-leave-active
にはvisibility: visible; opacity: 1;
を指定して、表示された状態からトランジションが開始するようにしています。まとめ
Vue transition を使って非対称なトランジション・アニメーションを行う際、少しの動作の変更が細かな部分に影響してきます。
特に entering 終了後および leaving 開始前の状態がどうなっているべきかを考慮して CSS を当てていくことが重要になりそうです。
- 投稿日:2020-08-30T21:22:26+09:00
Angular ngForで配列要素の最初の1つだけを取得する方法
- 投稿日:2020-08-30T21:18:44+09:00
bootstrap-selectで作ったセレクトボックスを活性化できないときの対応方法
- 環境
- CentOS Linux release 7.8.2003 (Core)
- openjdk version "11.0.7" 2020-04-14 LTS
- JSF 2.3.9
- jquery 3.2.1
- bootstrap 4.3.1
- bootstrap-select 1.13.14
事象 : bootstrap-selectで作ったセレクトボックスを活性化できない
- 初期表示時にセレクトボックスを非活性にする
- チェックボックスを変更すると
onchange
イベントでセレクトボックスの活性非活性を切り替えるXHTML<!--省略--> <h:form id="formId"> <h:panelGroup> <ui:remove>[使う]チェックボックス</ui:remove> <h:selectBooleanCheckbox id="chekBox" value="#{sampleBean.chekBoxValue}" onchange="changeDisabled();" /> <h:outputLabel value="使う" for="chekBox" /> </h:panelGroup> <h:panelGroup> <ui:remove>セレクトボックス</ui:remove> <h:selectOneMenu id="select" styleClass="selectpicker" value="#{sampleBean.selected}"> <f:selectItems value="#{sampleBean.selectItems}" /> </h:selectOneMenu> </h:panelGroup> </h:form> <!--省略-->JavaScript$(document).ready(function(){ changeDisabled(); }); /** セレクトボックスの活性非活性を切り替える. */ function changeDisabled() { /** @type {boolean} チェックボックスの選択状態. */ var checked = $("#formId\\:chekBox").prop("checked"); if (checked) { // 活性化する $("#formId\\:select").removeAttr('disabled'); $("#formId\\:select").next().children().removeClass('disabled'); } else { // 非活性にする $("#formId\\:select").attr('disabled', 'disabled'); $("#formId\\:select").next().children().addClass('disabled'); } }SampleBean// 省略 public class SampleBean implements Serializable { /** serialVersionUID. */ private static final long serialVersionUID = -6782548672735889274L; /** セレクトボックスの選択肢. */ @Getter private List<SelectItem> selectItems; /** セレクトボックスで選択した値. */ @Getter @Setter private Integer selected; @Getter @Setter private Boolean chekBoxValue = false; /** Beanの初期化処理. */ @PostConstruct public void init() { setSelectItems(); } /** 選択肢を設定する. */ private void setSelectItems() { selectItems = new ArrayList<SelectItem>(); selectItems.add(new SelectItem(0, " ")); selectItems.add(new SelectItem(1, "いぬ")); selectItems.add(new SelectItem(2, "ねこ")); selectItems.add(new SelectItem(3, "さる")); selectItems.add(new SelectItem(4, "ぽんすけ")); } }原因 : 初期表示時に非活性にする処理を実行するタイミングが
ready
だからJavaScriptをデバックしながらHTMLの出力を見ているとbootstrap-selectのHTMLが構築されるのは
onload
っぽかったのでいろいろ試してみた。
試してみると初期表示の非活性処理は同じでも、タイミングがready
とonload
でdisabledが付く場所に違いがあった。
また、バージョンが異なると出力されるHTMLが変わるからかdisabledが付く場所も変わった。非活性処理// selectタグにdisabled属性をつける $("#formId\\:select").attr('disabled', 'disabled'); // selectの隣のタグの子供タグにclass属性のdisabbledをつける $("#formId\\:select").next().children().addClass('disabled');凡例) ★ : 出力されたHTMLで「selectの隣のタグの子供タグ」になるタグ
bootstrap-select 1.13.14 1.13.14 1.6.3 1.6.3 disableするタイミング ready onload ready onload disabled属性が付く select select select select class属性にdisabbledがつく 一番上のdiv,
buttonbutton直下のdiv★ button★,
libutton★,
buttonの隣のdivaria-disabled属性が付く button なし なし なし 今回JavaScriptで活性非活性を切り替える際に行っている処理の場合、
onload
でdisabledが付く場所にしか対応していなかった活性化処理$("#formId\\:select").removeAttr('disabled'); $("#formId\\:select").next().children().removeClass('disabled');bootstrap-select 1.13.14
- bootstrap 4.3.1
- bootstrap-select 1.13.14
非活性化しない場合に出力されるHTML
<div class="dropdown bootstrap-select"> <select id="formId:select" name="formId:select" class="selectpicker" size="1" tabindex="-98"> <option value="0"> </option> <option value="1">いぬ</option> <option value="2">ねこ</option> <option value="3">さる</option> <option value="4">ぽんすけ</option> </select> <button type="button" class="btn dropdown-toggle btn-light" data-toggle="dropdown" role="combobox" aria-owns="bs-select-1" aria-haspopup="listbox" aria-expanded="false" data-id="formId:select" title="Nothing selected"> <div class="filter-option"><div class="filter-option-inner"><div class="filter-option-inner-inner">Nothing selected</div></div></div> </button> <div class="dropdown-menu "> <div class="inner show" role="listbox" id="bs-select-1" tabindex="-1"> <ul class="dropdown-menu inner show" role="presentation"></ul> </div> </div> </div>readyで非活性化した場合に出力されるHTML
<div class="dropdown bootstrap-select disabled"> <select id="formId:select" name="formId:select" class="selectpicker" size="1" disabled="disabled" tabindex="-98"> <!-- 非活性化しない場合と同じなので省略 --> <button type="button" class="btn dropdown-toggle disabled btn-light" data-toggle="dropdown" role="combobox" aria-owns="bs-select-1" aria-haspopup="listbox" aria-expanded="false" data-id="formId:select" tabindex="-1" aria-disabled="true" title="Nothing selected"> <!-- 非活性化しない場合と同じなので省略 -->onloadで非活性化した場合に出力されるHTML
<!-- 非活性化しない場合と同じなので省略 --> <select id="formId:select" name="formId:select" class="selectpicker" size="1" tabindex="-98" disabled="disabled"> <!-- 非活性化しない場合と同じなので省略 --> <div class="filter-option disabled"><div class="filter-option-inner"><div class="filter-option-inner-inner">Nothing selected</div></div></div> <!-- 非活性化しない場合と同じなので省略 -->bootstrap-select 1.6.3
- bootstrap 3.3.0
- bootstrap-select 1.6.3
非活性化しない場合に出力されるHTML
<select id="formId:select" name="formId:select" class="selectpicker" size="1" style="display: none;"> <option value="0"> </option> <option value="1">いぬ</option> <option value="2">ねこ</option> <option value="3">さる</option> <option value="4">ぽんすけ</option> </select> <div class="btn-group bootstrap-select"> <button type="button" class="btn dropdown-toggle selectpicker btn-default" data-toggle="dropdown" data-id="formId:select" title=" "> <span class="filter-option pull-left"> </span> <span class="caret"></span> </button> <div class="dropdown-menu open"> <ul class="dropdown-menu inner selectpicker" role="menu"> <li data-original-index="0" class="selected"><a tabindex="0" class="" data-normalized-text="<span class="text"> </span>"><span class="text"> </span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> <li data-original-index="1"><a tabindex="0" class="" data-normalized-text="<span class="text">いぬ</span>"><span class="text">いぬ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> <li data-original-index="2"><a tabindex="0" class="" data-normalized-text="<span class="text">ねこ</span>"><span class="text">ねこ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> <li data-original-index="3"><a tabindex="0" class="" data-normalized-text="<span class="text">さる</span>"><span class="text">さる</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> <li data-original-index="4"><a tabindex="0" class="" data-normalized-text="<span class="text">ぽんすけ</span>"><span class="text">ぽんすけ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> </ul> </div> </div>readyで非活性化した場合に出力されるHTML
<select id="formId:select" name="formId:select" class="selectpicker" size="1" disabled="disabled" style="display: none;"> <!-- 非活性化しない場合と同じなので省略 --> <button type="button" class="btn dropdown-toggle selectpicker disabled btn-default" data-toggle="dropdown" data-id="formId:select" tabindex="-1" title=" "> <!-- 非活性化しない場合と同じなので省略 --> <li data-original-index="0" class="disabled selected"><a tabindex="-1" class="" data-normalized-text="<span class="text"> </span>" href="#"><span class="text"> </span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> <li data-original-index="1" class="disabled"><a tabindex="-1" class="" data-normalized-text="<span class="text">いぬ</span>" href="#"><span class="text">いぬ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> <li data-original-index="2" class="disabled"><a tabindex="-1" class="" data-normalized-text="<span class="text">ねこ</span>" href="#"><span class="text">ねこ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> <li data-original-index="3" class="disabled"><a tabindex="-1" class="" data-normalized-text="<span class="text">さる</span>" href="#"><span class="text">さる</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> <li data-original-index="4" class="disabled"><a tabindex="-1" class="" data-normalized-text="<span class="text">ぽんすけ</span>" href="#"><span class="text">ぽんすけ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li> <!-- 非活性化しない場合と同じなので省略 -->onloadで非活性化した場合に出力されるHTML
<select id="formId:select" name="formId:select" class="selectpicker" size="1" disabled="disabled" style="display: none;"> <!-- 非活性化しない場合と同じなので省略 --> <button type="button" class="btn dropdown-toggle selectpicker btn-default disabled" data-toggle="dropdown" data-id="formId:select" title=" "> <!-- 非活性化しない場合と同じなので省略 --> <div class="dropdown-menu open disabled"> <!-- 非活性化しない場合と同じなので省略 -->対応方法 : 初期表示時に非活性にする処理を
onload
でやる$(window).on('load', function(){ changeDisabled(); }); // 省略
- 投稿日:2020-08-30T20:13:02+09:00
IMI住所変換コンポーネントを改造してリバースジオコーディングに対応してみた
先日、経産省が公開する「住所変換コンポーネント」についての性能を調べた記事を投稿しました。
IMI住所変換コンポーネントでいろんな住所を正規化してみた
https://qiita.com/uedayou/items/4c9d30fc031a9bf6762e「IMI住所変換コンポーネント」は、表記にゆれがある住所表記を正規化してくれるとても便利なツールです。
たとえば
霞が関2 -> 東京都千代田区霞が関二丁目のようになります。
このツールのもう一つの大きな特徴として、その住所に対する位置情報(緯度経度)も取得することができる点があります。
霞が関2 -> 東京都千代田区霞が関二丁目 -> {"緯度": "35.675551", "経度": "139.750413"}所謂「ジオコーディング機能」があります。
これは、ツール内にあらかじめ住所に対する位置情報データが含まれることを意味し、作り方によってはこの逆、位置情報(緯度経度)から住所にも変換することができるのではないか?と思いました。{"緯度": "35.675551", "経度": "139.750413"} -> 東京都千代田区霞が関二丁目これを一般的にリバースジオコーディング(逆ジオコーディング)と言いますが、本家コードを改造してこのリバースジオコーディング機能をつけてみました。
使い方
ツールを使うにはまず、Node.jsとリバースジオコーディング対応住所変換コンポーネントモジュールのインストールが必要です。経産省が公開するものとは違うものをインストールします。
ファイルはhttps://github.com/uedayou/imi-enrichment-address-plus/releases/download/v1.0.0/imi-enrichment-address-plus-1.0.0.tgzにあります。このツールには3つの使い方があります。
- (1) コマンドラインインターフェイス
- (2) Web API
- (3) Node.js
(1) は、npm でグローバルにインストール
npm install -g https://github.com/uedayou/imi-enrichment-address-plus/releases/download/v1.0.0/imi-enrichment-address-plus-1.0.0.tgz(2), (3) はローカルインストールしてください。
npm install https://github.com/uedayou/imi-enrichment-address-plus/releases/download/v1.0.0/imi-enrichment-address-plus-1.0.0.tgz(1) コマンドラインインターフェイス
リバースジオコーディングをコマンドラインで利用する場合は
$ imi-enrichment-address-plus --lat [緯度] --lng [経度]例$ imi-enrichment-address-plus --lat 35.675551 --lng 139.750413出力{ "@context": "https://imi.go.jp/ns/core/context.jsonld", "@type": "場所型", "住所": { "@type": "住所型", "表記": "東京都千代田区霞が関二丁目", "都道府県": "東京都", "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C13000", "市区町村": "千代田区", "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C13101", "町名": "霞が関", "丁目": "2" }, "地理座標": { "@type": "座標型", "緯度": "35.675551", "経度": "139.750413" } }(2) Web API
ツール内にWebサーバ機能があり、サーバ経由でWeb APIとして利用することができます。
$ node node_modules/imi-enrichment-address-plus/bin/server.js 8080上記を実行して http://localhost:8080/ をブラウザで開くと以下のようなWebページが開きます。
ここの緯度経度の部分の変換ボタンを押すと、実行結果には以下のように表示されます。
プログラム上から使う場合は、以下のJSONをPOSTしてください。
{ "@type": "座標型", "緯度": "[緯度の値]", "経度": "[経度の値]" }cURL$ curl -X POST -H 'Content-Type: application/json' -d '{"@type": "座標型","緯度": "35.170915","経度": "136.881537"}' localhost:8080出力{ "@context": "https://imi.go.jp/ns/core/context.jsonld", "@type": "場所型", "住所": { "@type": "住所型", "表記": "愛知県名古屋市中村区名駅一丁目", "都道府県": "愛知県", "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C23000", "市区町村": "名古屋市", "区": "中村区", "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C23105", "町名": "名駅", "丁目": "1" }, "地理座標": { "@type": "座標型", "緯度": "35.170778", "経度": "136.882494" } }(3) Node.js
ツールはNode.jsのモジュールなので、Node.js のコード上からも利用可能です。
リバースジオコーディングは以下のように利用できます。const convert = require('imi-enrichment-address-plus'); const point = { lat: 35.675551, lng: 139.750413 } convert(point).then(json=>{ console.log(json); });利用についての注意
本ツールのリバースジオコーディングは、半径1km内から最も近い代表点を持つ住所データを1件返します。与える緯度経度によっては必ずしもその位置の住所を表さない可能性がありますので、くれぐれも正確なデータではなくある程度あいまいなデータであることを留意して使用してください。
ただ、間違っていたとしてもかなり近い住所であることがほとんどだと思います。ざっくりとした住所でも構わない場合も結構あると思いますが、そういう場合はかなり便利だと思います。
ソースコード
ソースコードは以下のリポジトリで公開しています。
詳しい使い方は、README.mdを参照してください。
- 投稿日:2020-08-30T19:34:23+09:00
【React】なぜsuper(props)は必要なの?
元(?)記事
https://qiita.com/hand-dot/items/61a4b808f110b12e4281
とても参考になったのですが、もう少し噛み砕いてわかりやすく解説していきます?♂️
【疑問その1】なぜ、super()を呼び出すのか
結論から言うと
this.name
のようなthis
を伴う初期化を親クラスで行うため。下記はReact
ではないJSのクラスです。class Parent { constructor(name) { //ここで初期化している this.name = name; } } class Child extends Parent { constructor(name) { console.log(this.name); // エラーになる super(name); } } const test = new Child("佐藤");直感的に
this.name
が使えそうに感じるが、初期化(親クラスのconstructor
を実行)してないので、使えない。React
の場合も同様。// React.componentの内部 class Component { constructor(props) { this.props = props; // ... } } class Child extends React.component { constructor(props) { console.log(this.props); // エラーになる super(props); } }propsを渡す必要性
結論、親と自分のコンストラクターの実行が終わるまで、
this.props
は未定義だから。実は、React内部で
props
がインスタンスに割り当てられている。// React内部 const instance = new YourComponent(props); instance.props = props;しかし、これはコンストラクター実行後に行われる処理なので、コンストラクター内で
this.props
を使った処理を書けなくなってしまう。以上の理由から、super(props)を書きましょう。
何か間違っているところがありましたらご指摘よろしくお願いします?♂️
- 投稿日:2020-08-30T19:00:25+09:00
【React】stateについて学ぶ
stateとは
- コンポーネントが持つ状態のこと。
- クラスコンポーネントで使用される。(
hooks
は一旦置いておく。)- コンストラクタ内で初期値が決められる。
setState
で更新する。- 値が変更されると
render
される。import React from "react"; class Test extends React.Component { constructor(props) { super(props); this.state = { name: "佐藤", }; } render() { return ( <> <h1>{this.state.name}</h1> </> ); } } export default Test;次にstateの更新について。以下のように書かなくてはいけない。
this.state({ name = "山田" })以下のように書くのはNG。再レンダリングされないから。
jsx
this.state.name = "山田"
- 投稿日:2020-08-30T19:00:11+09:00
80mm フィルム (58mm フィルム) を発明してみた
レシート用のサーマルロール紙 (幅 80 mm or 58 mm) を映画のフィルムにしちゃいます!
receiptline シリーズ最終回では、インスタントカメラを作って、最後にレシートプリンターを自撮り連写しました。
この写真を眺めていたら、動画もできるんじゃないか?と思ったのです。自動用紙カットを解除
receiptline は、印刷が終わると用紙を自動でカットしてくれます。
映画のフィルムにするには、この自動用紙カットを解除しなくてはなりません。
変換ライブラリlib/receiptline.js
のソースコードを調べます。lib/receiptline.js// // ESC/POS // const _escpos = { // start printing: ESC @ GS a n ESC M n FS ( A pL pH fn m ESC SP n FS S n1 n2 ESC 3 n ESC { n open: printer => '\x1b@\x1da\x00\x1bM0\x1c(A' + $(2, 0, 48, 0) + '\x1b \x00\x1cS\x00\x00\x1b3\x00\x1b{' + $(printer.upsideDown), // finish printing: GS r n close: function () { return this.cut() + '\x1dr1'; },見つけました。close メソッドの
this.cut() +
を削除します。lib/receiptline.js// // ESC/POS // const _escpos = { // start printing: ESC @ GS a n ESC M n FS ( A pL pH fn m ESC SP n FS S n1 n2 ESC 3 n ESC { n open: printer => '\x1b@\x1da\x00\x1bM0\x1c(A' + $(2, 0, 48, 0) + '\x1b \x00\x1cS\x00\x00\x1b3\x00\x1b{' + $(printer.upsideDown), // finish printing: GS r n close: function () { // return this.cut() + '\x1dr1'; return '\x1dr1'; },インスタントカメラを改造
インスタントカメラのコードと実行環境を流用します。
映像クリックで撮影開始/終了するように変更しました。wwwroot/index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>80mm Film Camera</title> <script type="text/javascript"> async function initialize() { const video = document.querySelector('video'); const canvas = document.querySelector('canvas'); let timer = 0; // video video.srcObject = await navigator.mediaDevices.getUserMedia({ audio: false, video: { width: 1280, height: 720 } }); video.onclick = event => { if (timer > 0) { // stop clearInterval(timer); timer = 0; } else { // start timer = setInterval(() => { // image canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height); let data = `{i:${canvas.toDataURL('image/png').slice(22)}}\n`; // barcode const now = new Date(); const iso = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString(); data += `{c:${iso.replace(/\D/g, '').slice(2, 14)};o:ean,24}|`; // send data const xhr = new XMLHttpRequest(); xhr.open('POST', 'printer'); xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf-8'); xhr.send(data); }, 2000); } }; } </script> </head> <body onload="initialize()"> <video autoplay style="width: 100%;"></video> <canvas width="512" height="288" style="display: none;"></canvas> </body> </html>
- レシートプリンター
- TM-T88V (80mm)
- 記録メディア
- 80 ミリフィルム (80mm 幅のサーマルロール紙)
- アスペクト比
- 16 : 9
- 画素数
- 512 x 288 ピクセル
- フレームレート
- 0.5 fps (2 秒間隔で印刷)
- 音声
- なし
ストリームデータ処理にすれば、もっとフレームレートを上げられると思います。
80 ミリカメラ
80 ミリカメラのハードウェアです。家にあるものを組み合わせました。
- ノートパソコン (カメラデバイス)
- レシートプリンター
- AC アダプタ
- LAN ケーブル
- ポータブル電源
- ベビーキャリア
レシートプリンターが据え置き型なので重装備になってしまいました。
撮影しているとフィルムがはみ出してくるので、巻き取り装置がほしいです。80 ミリ映写機
80 ミリ映写機は、まだこれから。そこで・・・
撮影済みのフィルムをイメージスキャナで取り込んで GIF アニメーションを作りました。
倍速でお届けします!いかがでしょうか?
また何か作ったら投稿します。ではまた!
- 投稿日:2020-08-30T17:57:47+09:00
【JS】即時関数を使ってメソッドをモジュール化してみよう
即時関数とは
定義と同時に実行される関数のこと。
なんで使うの?
即時関数の中の
return
内でメソッド定義することで、グローバルスコープの汚染を防ぐことができるから。まあ、即時関数自体の名前はぶつかる可能性があるが。サンプルコード
const fn = (() => { // 初期化処理。即時で実行される。 console.log("即時関数実行!"); let name = "佐藤"; //以降は明示的に呼び出してやる(fn.callName())みたいな形で。 return { callName: () => { console.log(name); }, }; })(); // return{}内のメソッドに関しては、関数名.メソッド名で呼び出すことができる。ここでは、fn.callName()で呼べる
- 投稿日:2020-08-30T17:34:43+09:00
[GAS][JS] Momentライブラリのdiffでの日付差分
GASで話をしますが、Javascriptでも同じです。
GAS版 MomentライブラリのIDは下記です。
MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48やりたかったこと
こんな表を作って、毎朝8時台にGASが起動して、「締め切りまであと3日」以内になったら「締め切り近いけど大丈夫?」というリマインドをしてくれる機能を作りたい。
そのため、Momentライブラリをつかって「日付の差分」を取りたかった。
ダメだった例
簡略化するためにA2セルの日付だけを対象にします。
始めはこう書きました。
function sample1() { // 日付のセルからgetValue()したら Dateオブジェクト が取れるのでこう書いてしまいます const deadline_date = new Date("2020-09-10"); const deadline_moment = Moment.moment(deadline_date); const now_moment = Moment.moment(); const diff = deadline_moment.diff(now_moment, "days"); console.log(diff); }そして「今日は 2020/9/9 だとする」じゃないですか。
A2セルは 2020/9/10 なので 1 が出力されることを期待するじゃないですか。
だけど 出力結果は「0」ダメだった理由
上記で 「今日は 2020/9/9 だとする」 と書いたのですが、
実際はこのGASが実行される時刻は 2020/09/09 12:34:56 だったりするわけです。そして deadline として指定しているのは 「2020-09-10 00:00:00」 なのです。
実験してみるとこうなりました↓
function sample2() { const deadline_date = new Date("2020-09-10 00:00:00"); const deadline_moment = Moment.moment(deadline_date); const now_moment1 = Moment.moment("2020-09-09 00:00:00"); const diff1 = deadline_moment.diff(now_moment1, "days"); console.log(diff1); //=> 1 const now_moment2 = Moment.moment("2020-09-09 00:00:01"); const diff2 = deadline_moment.diff(now_moment2, "days"); console.log(diff2); //=> 0 }now_moment1 と now_moment2では「1秒」異なります。
diff1 では deadlineである 2020-09-10 00:00:00 と 2020-09-09 00:00:00 の差分が 24:00:00 なので 1日。
diff2 では deadlineである 2020-09-10 00:00:00 と 2020-09-09 00:00:01 の差分が 23:59:59 なので 0日。という動きのようだ。
どうすればいいか
「日付」で比較したいのだからこうしたらいいのでは。
「時分秒ミリ秒にゼロをセットしちゃう」作戦
function sample3() { const deadline_date = new Date("2020-09-10 00:00:00"); const deadline_moment = Moment.moment(deadline_date); // 時分秒ミリ秒にゼロをセットしちゃう const today_moment = Moment.moment().hour(0).minutes(0).second(0).millisecond(0); const diff = deadline_moment.diff(today_moment, "days"); console.log(diff); }これで、実行時刻が「2020-09-09 11:11:11」であっても「1」が出力されます。
↓ 念のため実験
function sample4() { const deadline_date = new Date("2020-09-10 00:00:00"); const deadline_moment = Moment.moment(deadline_date); const now = new Date("2020-09-09 11:11:11") const today_moment = Moment.moment(now).hour(0).minutes(0).second(0).millisecond(0); const diff = deadline_moment.diff(today_moment, "days"); console.log(diff); //=> 1 }予想通り!
本当にそうなの?
"days" で比較するときに、「24時間未満かどうかで判定している」というのは、動作させた結果を見て私が推測しているところなのですが、実際どういう実装になってるのか momentライブラリのdiff.jsソースコードを見てみました。(これはmoment.jsのソースコードであってGASのライブラリのコードではないので、GASでは違う実装かもしれませんが、きっと同じであろう)
export function diff(input, units, asFloat) { var that, zoneDelta, output; if (!this.isValid()) { return NaN; } that = cloneWithOffset(input, this); if (!that.isValid()) { return NaN; } zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; units = normalizeUnits(units); switch (units) { // 略 case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst // 略 } return asFloat ? output : absFloor(output); }
864e5
とは 1日をミリ秒にしたときの 86400000 の指数表記。
よって 時刻の差分を 1日を表すミリ秒で割ってる のね。diff関数の第3引数である
asFloat
は指定していないので、return asFloat ? output : absFloor(output);では
absFloor(output)
が return される。
absFloor()
関数の定義は こちらexport default function absFloor(number) { if (number < 0) { // -0 -> 0 return Math.ceil(number) || 0; } else { return Math.floor(number); } }Math.ceil() : 引数として与えた数以上の最小の整数を返します。
Math.floor() : 引数として与えた数以下の最大の整数を返します。ということなので、output(時刻の差分を 1日を表すミリ秒で割った数値) が
- 0.999 であれば 0 を返すし、
- 1.234 なら 1 を返すし、
- -3.55 なら -3 を返す。
私の推測はあってたようです!(...というここまでの流れが間違ってたら教えてほしいです)
結論
ということで 「日にち」を比較したいのであれば、日にち(date)より小さい単位(hour, minutes, second, millisecond)はゼロで埋めてあげる
補足ですが、ミリ秒がでてくるなら
const deadline_date = new Date("2020-09-10 00:00:00.000");としたほうが粒度は揃いますね。
(ミリ秒まで指定してDateオブジェクトをnewしたことが無いのでへんな感じする)
- 投稿日:2020-08-30T17:21:37+09:00
リスト処理関数(cons,car,cdr,atom,list)実装例まとめ
拙作記事『7行インタプリタ実装まとめ』について,そろそろSchemeとPython以外にも対応しないとなあと思っていろいろ整理した結果,『S式入力の実装部分がほとんどじゃないこれ?』→『あと,リスト処理内容に基準を設けてないと言語ごとに実装方針がバラバラになりそう』となり,とりあえず『
cons
car
cdr
atom
list
が使える』ようにする記述例を先にまとめていくことにした次第.ターゲット言語上での純LISP機能実装に近いとでもいいますか.※Python版については,これを用いたS式入出力記述例も掲載しています(他の言語版も揃ったら別記事に移すかも).
仕様
- ドット対(cons cells)を定義
- アトムは全て文字列,空リストはNULL
cons
car
cdr
を実装- アトムか否かを判定する
atom
を実装- 文字列の羅列から単方向リストを生成する
list
を実装Python(CPython 3.7.3)
ドット対はペア要素のタプルで定義(不変としたいため).空リストNULLは
None
を使用.#### Cons cells are created by using tuple. #### All of atoms are string and the null value is None. def cons(x, y): return (x, y) def car(s): return s[0] def cdr(s): return s[1] def atom(s): return isinstance(s, str) or s == None def list(ts): if not ts: return None else: return cons(ts[0], list(ts[1:]))利用例は次の通り.
def mkassoc(a, b): if a == None or b == None: return None else: return cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b))) def assoc(k, vs): if vs == None: return None else: if car(car(vs)) == k: return car(vs) else: return assoc(k, cdr(vs))>>> vs = mkassoc(list(("hoge", "hage", "hige")), list(("10", "20", "30"))) >>> assoc("hage", vs) ('hage', '20') >>> car(assoc("hage", vs)) 'hage' >>> cdr(assoc("hage", vs)) '20'(上記定義を用いたS式入出力記述例)
#### Cons cells are created by using tuple. #### All of atoms are string and the null value is None. def cons(x, y): return (x, y) def car(s): return s[0] def cdr(s): return s[1] def atom(s): return isinstance(s, str) or s == None def list(ts): if not ts: return None else: return cons(ts[0], list(ts[1:])) #### s_read # '(a b c)' # => ['(', 'a', 'b', 'c', ')'] def s_lex(s): return s.replace('(', ' ( ').replace(')', ' ) ').split() # ['(', 'a', 'b', 'c', ')'] # => ('a', 'b', 'c') def s_syn(s): t = s.pop(0) if t == '(': r = [] while s[0] != ')': r.append(s_syn(s)) s.pop(0) return tuple(r) else: if t == 'None': return None else: return t # ('a', 'b', 'c') # => ('a', ('b', ('c', None))) def s_sem(s): if atom(s): return s elif len(s) == 0: return None elif s[0] == '.': return s_sem(s[1]) else: return cons(s_sem(s[0]), s_sem(s[1:])) def s_read(ss): return s_sem(s_syn(s_lex(ss))) #### s_print def s_prcons(s): sa_r = s_print(car(s)) sd = cdr(s) if sd == None: return sa_r elif atom(sd): return sa_r + ' . ' + sd else: return sa_r + ' ' + s_prcons(sd) def s_print(s): if atom(s): return s elif s == None: return None else: return '(' + s_prcons(s) + ')'>>> s_print(s_read('((Apple . 100) (Orange . 120) (Lemmon . 250))')) '((Apple . 100) (Orange . 120) (Lemmon . 250))' >>> x = s_read('((Apple . 100) (Orange . 120) (Lemmon . 250))') >>> car(x) ('Apple', '100') >>> car(car(cdr(x))) 'Orange' >>> s_print(cdr(x)) '((Orange . 120) (Lemmon . 250))'C言語(gcc 8.3.0)
atom
実装のため,まず,文字列とドット対ポインタの両方を扱うことができるnode_t
構造体を定義,それを用いてドット対cons_t
構造体を定義.空リストNULLはNULLポインタを使用.#include <stdio.h> #include <stdlib.h> /* Cons cells are created by using typedef struct. */ /* All of atoms are char* and the null value is NULL. */ typedef unsigned int value_t; enum NODE_TAG { NODE_STRG, NODE_CONS }; typedef struct _node_t_ { value_t value; enum NODE_TAG tag; } _node_t, *node_t; node_t node(value_t value, enum NODE_TAG tag) { node_t n = (node_t)malloc(sizeof(_node_t)); n->value = value; n->tag = tag; return (n); } typedef struct _cons_t_ { node_t x; node_t y; } _cons_t, *cons_t; node_t cons(node_t x, node_t y) { cons_t c = (cons_t)malloc(sizeof(_cons_t)); c->x = x; c->y = y; node_t n = node((value_t)c, NODE_CONS); return (n); } #define str_to_node(s) (node((value_t)(s), NODE_STRG)) #define node_to_str(s) ((char *)(s->value)) #define car(s) (((cons_t)(s->value))->x) #define cdr(s) (((cons_t)(s->value))->y) #define atom(s) (s->tag == NODE_STRG) #define MAXSTR 64 node_t list(const char s[][MAXSTR], const int n) { node_t r = str_to_node(NULL); for (int i = n - 1; i >= 0; i--) { r = cons(str_to_node(s[i]), r); } return (r); }利用例は次の通り.
#include <string.h> node_t mkassoc(node_t a, node_t b) { if (node_to_str(a) == NULL || node_to_str(b) == NULL) { return NULL; } else { return cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b))); } } node_t assoc(node_t k, node_t vs) { if (node_to_str(vs) == NULL) { return NULL; } else { if (strcmp(node_to_str(car(car(vs))), node_to_str(k)) == 0) { return car(vs); } else { return assoc(k, cdr(vs)); } } } int main(void) { const char s1[][MAXSTR] = { "hoge", "hage", "hige" }; const char s2[][MAXSTR] = { "10", "20", "30" }; node_t vs = mkassoc(list(s1, 3), list(s2, 3)); node_t k = str_to_node("hage"); node_t r = assoc(k, vs); printf("car(assoc(\"hage\", vs)) = %s\n", node_to_str(car(r))); printf("cdr(assoc(\"hage\", vs)) = %s\n", node_to_str(cdr(r))); free(vs); free(k); free(r); return (0); }car(assoc("hage", vs)) = hage cdr(assoc("hage", vs)) = 20Common Lisp(SBCL 1.4.16)
あくまで参考.ドット対はクロージャで実現.オリジナルと区別するため,
s_cons
s_car
s_cdr
s_atom
s_list
の名前で定義.空リストNULLはNIL
を使用.;;;; Cons cells are created by using lambda closure. ;;;; All of atoms are string and the null value is NIL. (defun s_cons (x y) (lambda (f) (funcall f x y))) (defun s_car (c) (funcall c (lambda (x y) x))) (defun s_cdr (c) (funcall c (lambda (x y) y))) (defun s_atom (s) (and (not (functionp s)) (not (equal s NIL)))) (defun s_list (s) (if (null s) NIL (s_cons (car s) (s_list (cdr s)))))利用例は次の通り.
(defun s_mkassoc (a b) (if (or (equal a NIL) (equal b NIL)) NIL (s_cons (s_cons (s_car a) (s_car b)) (s_mkassoc (s_cdr a) (s_cdr b))))) (defun s_assoc (k vs) (if (equal vs NIL) NIL (if (equal (s_car (s_car vs)) k) (s_car vs) (s_assoc k (s_cdr vs)))))* (defparameter vs (s_mkassoc (s_list '("hoge" "hage" "hige")) (s_list '("10" "20" "30")))) VS * (s_assoc "hage" vs) #<CLOSURE (LAMBDA (F) :IN S_CONS) {50F3E645}> * (s_car (s_assoc "hage" vs)) "hage" * (s_cdr (s_assoc "hage" vs)) "20"Ruby(CRuby 2.5.5)
ドット対は二要素の(凍結)配列で定義.空リストNULLは
nil
を使用.#### Cons cells are created by using Array. #### All of atoms are string and the null value is nil. def cons(x, y) [x, y].freeze end def car(s) s[0] end def cdr(s) s[1] end def atom(s) s.is_a?(String) || s == nil end def list(s) s.size == 0 ? nil : cons(s[0], list(s[1..-1])) end利用例は次の通り.
def mkassoc(a, b) if a == nil || b == nil then return nil else return cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b))) end end def assoc(k, vs) if vs == nil then return nil else if car(car(vs)) == k then return car(vs) else return assoc(k, cdr(vs)) end end end>> vs = mkassoc(list(["hoge", "hage", "hige"]), list(["10", "20", "30"])) => [["hoge", "10"], [["hage", "20"], [["hige", "30"], nil]]] >> assoc("hage", vs) => ["hage", "20"] >> car(assoc("hage", vs)) => "hage" >> cdr(assoc("hage", vs)) => "20"JavaScript (Node.js 10.21)
ドット対は二要素の(凍結)配列で定義.空リストNULLは
null
を使用.//// Cons cells are created by using Array. //// All of atoms are string and the null value is null. function cons(x, y) { return Object.freeze([x, y]); } function car(s) { return s[0]; } function cdr(s) { return s[1]; } function atom(s) { return typeof s == 'string' || s == null; } function list(s) { return s.length == 0 ? null : cons(s[0], list(s.slice(1))); }利用例は次の通り.
function mkassoc(a, b) { return a == null || b == null ? null : cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b))); } function assoc(k, vs) { if (vs == null) { return null; } else { if (car(car(vs)) == k) { return car(vs); } else { return assoc(k, cdr(vs)); } } }> vs = mkassoc(list(["hoge", "hage", "hige"]), list(["10", "20", "30"])) [ [ 'hoge', '10' ], [ [ 'hage', '20' ], [ [Array], null ] ] ] > assoc("hage", vs) [ 'hage', '20' ] > car(assoc("hage", vs)) 'hage' > cdr(assoc("hage", vs)) '20'備考
記事に関する補足
- 参照用を想定していることもあり,エラーチェックもモジュール化もガーベジコレクションもなにそれおいしいの状態.実用のS式パーサとかは既にたくさんあるしなあ.
- 現バージョンの
list
だと,Common Lisp版を含めて『リストのリスト』が作れない…cons
使えばいっか(いいかげん).変更履歴
- 2020-08-31:JavaScriptの実装例を追加
- 2020-08-30:利用例を連想リスト実装に統一
- 2020-08-30:Rubyの実装例を追加
- 2020-08-30:初版公開(Python,C,Common Lisp)
- 投稿日:2020-08-30T17:12:23+09:00
カタカタカタッターンのほかにクリック音とカーソルの移動音も可視化した!
はじめに
こちらの投稿(カタカタカタッターンを可視化した)、そしてそのChrome拡張機能、知っている方も多いのではないでしょうか?
先日確認したところ、拡張機能がなくなってしまっていたので、自分で作ってみました。
(※公開はしていません、Github上にあるファイルで自身のChromeでは使用可能です。)こんな感じになりました
こちらで試せます
https://koichirokato.github.io/katakatademo/内容とキャレット位置の取得方法
本家(?)様と同様です。
以下、引用(引用元:https://qiita.com/ampersand/items/3ef94ebe9cba8c07a157)内容
input要素のtypeがtextまたはsearchの要素、もしくはテキストエリア内におけるキャレットの位置を画面内の絶対値として取得し、その周辺に打鍵と同時に画像を散らかしています。
キャレット位置の取得方法
これの実現については今回は自前実装ではなく caretposition.jsという素晴らしいライブラリがあったので、使わせていただいてます。
http://d.akiroom.com/2012-06/jquery-textarea-caret-position-javascript-library/実装
以下が実装内容ですが、キーボード入力、クリック、カーソルの移動と3つに分かれています。
// Kachi Kachi document.onkeydown = function (e) { var current = document.activeElement; if (e.key === 'Backspace') { return true; } if (current.type === 'textarea' || current.type === 'text' || current.type === 'search') { var isEnter = e.key === 'Enter'; var prefix = isEnter ? 'tan' : 'kata'; var size = isEnter ? rand(90, 120) : rand(40, 50); var caretPosition = Measurement.caretPos(current); var imgUrl = chrome.extension.getURL('images/' + prefix + '_' + rand(1, 4) + '.svg'); chrome.storage.sync.get("kata", function(items) { if(items.kata==true) outputimage(caretPosition.left, caretPosition.top, imgUrl, size, isEnter); }); } } // Pochi Pochi document.body.addEventListener( "click", function( event ) { var x = event.pageX ; var y = event.pageY ; var size = rand(40, 50); var imgUrl = chrome.extension.getURL('images/' + 'pochi' + '_' + rand(1, 4) + '.svg'); chrome.storage.sync.get("pochi", function(items) { if(items.pochi==true) outputimage(x,y,imgUrl,size); }); } ) ; // Byu-n var lastx=0; var lasty=0; document.body.addEventListener("mousemove", function(event){ var x = event.pageX; var y = event.pageY; var size = rand(60, 70); var imgUrl = chrome.extension.getURL('images/' + 'byun' + '_' + rand(1, 4) + '.svg'); chrome.storage.sync.get("byun", function(items) { if(items.byun==true) outputimage(x,y,imgUrl,size,isEnter=false,mousemove=true,lastx=lastx,lasty=lasty); }); lastx = x; lasty = y; }); // Output Image function outputimage(x, y, imgUrl, size, isEnter=false, mousemove=false,lastx=0,lasty=0){ var $img = $('<img width="' + size + '">'); $img.attr('src', imgUrl); $img.css({ 'position': 'absolute', 'top': mousemove ? lasty : y+rand(-10, 10), 'left': mousemove ? lastx : x+rand(-10, 10), 'zIndex': 99999 }); $('body').append($img); $img.animate({ 'top': mousemove ? y : y+rand(-40, 40), 'left': mousemove ? x : x+rand(-40, 40), 'width': size + (isEnter ? rand(30, 50) : rand(10, 20)), 'opacity': 0 }, mousemove ? 200 : 500, function () { $img.remove(); }) }Chrome拡張機能
拡張機能用にポップアップを利用した設定ができる用にしました。
アイコンをクリックでチェックボックスと保存用のボタンが出てきます。うるさいときは止めれますね。おわりに
Chromeの拡張機能の部分、なかなか大変でしたがなんとかできてよかったです。
公開するかはまた決めようと思います。(そもそもいいのか?という)
- 投稿日:2020-08-30T16:37:21+09:00
【JS】async,awaitを理解していく
https://qiita.com/soarflat/items/1a9613e023200bbebcb3
すごくわかりやすかったのでアウトプットしていく。
async,awaitとは
- Promiseより簡潔に書ける。非同期処理構文
asyncとは
非同期関数を定義することができる。関数の前に
async
をつけることで非同期の関数を定義することができる。// asyncを前につける async function asyncFunction() {} //アロー関数の場合 const asyncFunction = async () => {}非同期関数は戻り値(
return
した)をresolve
の値として返される。例外やthrow
した場合はreject
の値として返される。実際に確認してみる
resolve
(成功)の場合const asyncFunction = async () => { name = "hoge"; return name; }; asyncFunction().then((value) => { console.log(value); });
reject
(失敗)の場合const asyncFunction = async () => { throw new Error("reject"); }; asyncFunction().catch((error) => { console.log(error); });どちらの場合でも
Promise
を返していることがわかると思います。とにかくasync
がついた関数(非同期関数)はPromise
を返すみたいですね。awaitとは
- 非同期関数(
async function
)内で使う- 関数の前に
await
をつけて使うawait
がつけられた関数がPromise
を返すまで待機する。const asyncFunction = async () => { // sampleResolve()の処理が終わるまで、待機する const result = await sampleResolve(); console.log(result); };
await
を使うことでasync function
内の処理を指定した場所で止めることができます。忘備録
then
関数の役割は二つ。
- 非同期の処理を同期的にする。
- 前の処理が成功(resolve)だった場合に実行する。
この前者の部分をawaitが担うことはできる。しかし、後者の部分は補えないので、完全になくなることはない。
- 投稿日:2020-08-30T15:37:07+09:00
FetchAPIでReadableStreamからMP4を読みながらvideoタグで再生する
やりたいこと
認証がかかったストレージのAPIからJavaScriptで動画データを取得してvideoタグで再生したかった.
FetchAPIやXHRでMP4ファイルにアクセスはできるので,これをブラウザ上で再生したいだけだけなのだけど...
- 普通にvideoタグのsrcにAPIのURLを突っ込む → リクエストヘッダ等をセットできなくて認証できない
- FetchAPIでデータを読んでblobを再生する → ファイル全体を読まないと再生開始できない(動画は数百MBある) (参考記事)
- MPEG-DASH等のFragmented MP4を読み込んでMSEで再生する → 事前にサーバ側に置くデータを変換しないと再生できない (参考記事)
意外とめんどう.
JavaScriptでFragmented MP4を作る
+------------+ +-----------+ +----------------+ +-------------+ +-----------+ | 動画データ | → | Fetch API | → | ReadableStream | → (なんか良い感じの変換) → | MediaSource | → | videoタグ | +------------+ +-----------+ +----------------+ +-------------+ +-----------+この
(なんか良い感じの変換)
の部分が欲しい.MediaSourceは通常のMP4ではなくて,Fragmented MP4を入れる必要がある.
良さげなライブラリが無いか探しているけど,https://github.com/gpac/mp4box.js くらいしか見当たらない.(探し足りないのかもしれないけど)
やりたいことに対してライブラリが大きすぎるのと,ダウンロードしたデータを入れるとsegmentができたときにコールバックが呼ばれる感じなので,必要な量だけStreamから読むという挙動にしにくい.Fragmentedが付いても所詮はMP4なので簡単に変換できるだろうと思って,とりあえずライブラリ使わず再生してみた.
とりあえず動いたやつ: https://github.com/binzume/mp4player-js
再生するだけで意外と面倒くさかったので,必要だった作業をメモ.
変換方法のメモ
https://www.w3.org/TR/mse-byte-stream-format-isobmff/ この辺の説明と実際のMP4のデータを見ながら適当に変換していけばよい.
MP4とかISOBMFFのドキュメントは大きくて真面目に把握するのはめんどいので,なるべく元のMP4のBox構造を触らずに書き換えが必要なBoxだけ触ることに.
最初,比較用にffmpegで生成したfragmentを見てたけど,どういうわけかffmpegで生成したやつはChromeのMediaSourceで読めなかった.以下はMP4の構造は知っている前提の記述です.
initialization Segment
最初にinitialization Segmentを一度だけ渡す必要がある.内容は以下のような感じ.
- ftyp
- moov
- moovの下はMP4からコピーして少し修正
- mvex
- trex
元がMP4なら,ftyp + moovのうち各tracのstbl下のboxのうちstsd以外を空にして,mvexを追加すればよいだけ.
trexにはトラック番号だけ書いておけば良さそう.media segment
普通のmp4ならmoovに入っていた情報を複数のmoofに分割して格納する必要がある.
moov → moof,trac → traf等対応しているBoxは名前でわかりやすいが,中身の格納方法は別物なので変換は地味に面倒.
- moof
- mfhd
- traf
- tfhd
- tfdt
- trun
- mdat
生成しなければいけない主なBox
trakとかmoofとか単に他のboxを持つだけの親は省く.
trex
基本的に各fragmentのtfhdに入れれば良いので少しでもバイト数削りたい場合以外は気にしなくて良さそう.
ただボックス自体は省略できないので,トラック番号だけ入れておく.fthd,ftdt,trun
trunがサンプルの在り処を指す.stco,stsc,stsz,stts,stss,cttsから生成できる.
trunが持てる情報色々あって難しく感じるけど,生成するときは必要なやつ以外は書き込まなければよいので割とシンプル.mdat
元のMP4のmdatから必要な範囲をコピーしてくる.どのサンプルがどの場所にあるかはstco,stsc,stszを見る.
MediaSource.addSourceBuffer() に渡す mimeType
video/mp4; codecs="avc1.42e01e,mp4a.40.2"
みたいな文字列を渡すのだけどど,Chromeとかだとcodecsの指定が必須.
(多くのサンプルコードで特に説明無くハードコードされてて,残念な雰囲気がある)MP4のstsd boxからconfiguration recordを取り出して中身を見る必要がある.
avc1
とかmp4a
のコーデック名はstsdの先頭あたりにそのまま入ってるが,後ろの数字はコーデックごとのパラメータを見ないといけない.
手抜き実装をするなら,h264のパラメータはavcCの後にある固定位置の3バイトを16進にして,aacは40.2固定にしてもほとんどの場合は良さそう.雑感
- https://github.com/gpac/mp4box.js とかあるけど,単純に再生するのに使いやすそうなのが見当たらない
- ちゃんと動くの作るのは色々めんどう
- バイト列の扱いだるいのでwasmでやるのが良さそう
- MediaSourceとReadableStream,変なことしなくても普通につながってほしい
- 投稿日:2020-08-30T15:22:26+09:00
【JS】Promiseをがっつり学んでいく。
初学者なので、何かご指摘、アドバイス等ありましたらコメントしていただけるとありがたいです?♂️
Promiseとは
Promise は非同期処理の最終的な完了もしくは失敗を表すオブジェクトです。
参考:https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises
つまりは、下記のとおり。
- 非同期処理の成功と失敗を分岐することができる。
- 複数の非同期処理を平行,直列的に処理することができる。
参考:https://qiita.com/saka212/items/9b6cfe06b464580c2ee6
簡単な例から見ていく。
let myPromise = new Promise((resolve, reject) => { // 非同期処理をコールバック関数として渡す。 setTimeout(function () { resolve("非同期処理が成功しました。"); }, 1000); }); myPromise .then(function (value) { // 非同期処理が成功した場合 console.log("成功メッセージ→" + value); // => 成功メッセージ→非同期処理が成功しました。 }) .catch(function (value) { // 非同期処理が失敗した場合 console.log("エラーメッセージ" + value); // => でエラーかreject()が出た場合に呼ばれる });Promise内のコールバック関数(アロー関数)が
resolve()
を返したときはthen()
関数が実行されます。また、Promise内のコールバック関数(アロー関数)がエラーを検出したり、reject()
を返した場合はcatch()
関数が実行されます。Promise関数内で複数の値を返す
let myPromise = new Promise((resolve, reject) => { // 非同期処理をコールバック関数として渡す。 setTimeout(function () { resolve(["成功", "失敗してないよ"]); }, 1000); }); myPromise .then(function (value) { // 非同期処理が成功した場合 console.log(value[0]); // => 成功 console.log(value[1]); // => 失敗してないよ }) .catch(function (value) { // 非同期処理が失敗した場合 console.log("エラーメッセージ" + value); // => でエラーかreject()が出た場合に呼ばれる });こんな感じで
resolve()
内で配列で渡してあげればよき。ちなみに、当然オブジェクトも返すことができる。let myPromise = new Promise((resolve, reject) => { // 非同期処理をコールバック関数として渡す。 setTimeout(function () { const obj = { sucess: "成功", notFailure: "失敗してないよ", }; resolve(obj); }, 1000); }); myPromise .then(function (value) { // 非同期処理が成功した場合 console.log(value.sucess); // => 成功 console.log(value.notFailure); // => 失敗してないよ }) .catch(function (value) { // 非同期処理が失敗した場合 console.log("エラーメッセージ" + value); // => でエラーかreject()が出た場合に呼ばれる });複数の非同期処理を同期的に。(直列)
fn1
→fn2
→fn3
のようにそれぞれの非同期処理を同期的に処理したいとします。では、書いていきましょう。Promise.resolve() .then(fn1) .then(fn2) .then(fn3) // それぞれの関数は省略みたいに書くことで、順番に処理されます。
複数の非同期処理を並列に。
Promise.allを使う
fn4
とfn5
の処理がどちらも成功した場合に実行したい処理があるとします。Promise.all([fn4, fn5]).then(() => { // fn4,fn5がどちらも成功した時に実行する処理をここに書く。 });Promise.raceを使う
渡された関数のどれかが
resolve
(またはreject
)を返したタイミングで返す。Promise.race([fn6, fn7]).then(() => { // fn6かfn7がresolveを返した時点で実行する処理 }).catch(() => { console.log('error'); });ちなみに、thenメソッドが呼ばれた後もPromiseの処理は継続されます。例えば、
fn6
が先にresolve
を返した後でもfn7
は実行されます。こんな感じでまとめてみたので次は
Async/await
を学んでいきたいと思います。
- 投稿日:2020-08-30T14:57:33+09:00
トースト通知のライブラリ(jQuery非依存版)を作ってみた
かなり前に、Androidっぽい見た目のトースト通知(数秒で消える通知メッセージ)のjsライブラリを作成して、投稿したのですが、がっつりJQueryを使っていたため、jQuery非依存に書き換えました。
またコードをGitHubに公開し、npmでinstallできるようにもしてみました。今回作った物:a-toast
Androidっぽい見た目のトースト通知なので、そのaです。
リポジトリ:
https://github.com/negi141/a-toast※先にメジャーなトースト通知ライブラリを紹介
- Toaster ※jQuery必須
- Bootstrap Notify
- Bootstrap Toasts
- iziToast
デモ
GitHubリポジトリ:
https://github.com/negi141/a-toastデモページ
https://negi141.github.io/a-toast/使い方
npm install negi141/a-toast
でインストール。
または、/build/内のjsとcssを手元に持ってくる。html<link rel="stylesheet" href="a-toast.css" /> <script src="a-toast.js"></script>jsvar toast = new aToast(); // スピードや表示位置の設定 (任意) // speed : 表示時間. millisecond. default=4000. // position : 表示位置. 'top' or 'bottom'. default='top'. toast.setOption(speed, position); // 通知を表示 // message : 表示するテキスト // style : 通知の色. '' or 'success' or 'warn' or 'danger'. default=''. toast.show(message, style); toast.success(message); toast.warn(message); toast.danger(message);その他:jQuery⇒jQuery非依存にあたって
fadeIn()はjQueryを使わない場合、どう書くのかなーと思いましたが、以下のようにCSSのtransition-(時間的変化)で書き換え可能でした。
js$('.a-toast').hide().fadeIn('fast');↓ ↓ ↓
jst.classList.add("a-toast-show");css.a-toast { transition-property: opacity; transition-duration: 0.8s; opacity: 0; } .a-toast-show { opacity: 1; }
- 投稿日:2020-08-30T14:33:14+09:00
実質20行ほどでニコニコ動画風のコメント表示機能を作る 〜素の JavaScript とアニメーション用ライブラリ(GSAP)を利用〜
はじめに
この記事は、以下のツイートの動画にあるようなニコニコ動画風のコメント表示を、HTML+JavaScript で実装した際の話です。
テキストを動的に生成して、画面内をアニメーションで動かす処理を書いてた件、さらに一歩先へ。
— you (@youtoy) August 29, 2020
OBSとの組み合わせ!#ゆるメカトロ #Noodlもくもく会 #ビジュアルプログラミング交流会 https://t.co/rngaNDFNWj pic.twitter.com/GuZqc1vMO8こちらの動画の内容を実現しているものは、ボタンがクリックされたら動的にテキストを生成して画面上に流すという Webページと、その Webページとカメラ映像とをクロマキー合成する機能(OBS による)です。
記事のこの後の部分では、上記の Webページ の部分を作るまでの流れを書き、そして仕組みの話へと入っていきます。また、仕組みの話の後には、オンラインでコメント・リアクションをやりとりするサービス・プロトタイプの事例をいくつか書いています。
仕組みのところだけ知りたいという方は、「作った仕組みについて」の項目からお読みください。
着手するまでの流れ1(OBS向けに過去に作った仕組み)
自身のプライベートの活動で、ここ最近はオンラインイベントの対応(自身でのイベント主催やLT登壇など)が増加しています。そんな中、配信・登壇者の側を便利にしてくれる仕組みや、イベントの参加者と登壇者をつなげる仕組みが自作できないか、と思っていました。
そして、以下の Qiita の記事やツイートの動画にあるような、配信などに使われるオープンソースのソフト「OBS」の「obs-websocket というプラグイン」を活用した遠隔操作の仕組みを作ったりしていました。
●OBS をスマホや M5GO(M5Stack)から遠隔制御 〜 MQTT や obs-websocket を利用 〜 - Qiita
https://qiita.com/youtoy/items/23fb0e16f1a4428b5c9b
MQTTを利用し、PC上で動くOBSのシーンの切り替え操作を、他デバイスから制御してみた!
— you (@youtoy) August 20, 2020
UIFlowでプログラムを書いたM5GOと、JavaScriptで処理を書いたWebサイトを表示したスマホの、2つのデバイスを操作し、3つのシーンを切り替えてみました。 pic.twitter.com/fg3QAauCB6この配信者側で活用する仕組みを作ったところで、さらに配信者と視聴者をつなぐ部分の何かを作ることもできれば、と思いました。
着手するまでの流れ2(今回の内容の着手)
上記の仕組みを作った後に、自分も主催メンバーに入っている共同主催のイベント(もくもく会)を開催しました。
そのもくもく会でやる内容を考えていた時に、ふと「また OBS と組み合わせて使える何かを作ってみよう」と思いました。そして、おおまかな内容を考えた後に以下のツイートにあるような試作を始めました
今日の合同もくもく会は #ビジュアルプログラミング交流会 からの参加ですが、
— you (@youtoy) August 29, 2020
なぜかJavaScriptで動的にテキスト生成をして、それをアニメーションで動かすようなことをしているw#ゆるメカトロ #Noodlもくもく会 pic.twitter.com/qoeCwioeXn作った仕組みについて
プロトタイプを作るために考えたこと・やったこと
この記事の後半の「既存のサービスやプロトタイプ」という項目に書いたような仕組みの中で、参加者の操作が行われた際に登壇者側に何か表示がされる方向のもの作りたい、と思いました。
そして、最初に手をつけようと思ったのが「ニコニコ動画風のコメント表示機能」でした。
コメント表示に必要な機能
まずは、「ニコニコ動画風のコメント表示機能」を最小限の実装で実現するために、必要な仕組みを頭の中で整理しました。その結果は以下のとおりです。
- 画面に流すテキストの要素の動的な生成と設定
- 要素の動的な生成
- 動的に生成した要素の画面上への配置
- 動的に生成した要素へのテキストの設定
- 処理に必要なスタイル・属性などの設定(id や style)
- 画面上での要素の移動
- アニメーションの仕組みを用いた要素の移動
- クロマキー合成のための設定
- 背景色をクロマキー用の色に設定
最終的には外部から受け取ったテキストやリアクションのトリガーに基づいて、画面上に流すテキスト等の表示を変えるつもりでしたが、表示部分のみを試す段階では外部との連携は省きました。とりあえず版の実装は「画面上にボタンを配置し、ボタンが押されるごとにテキストの動的な生成とアニメーションを行う」というものにしました。
アニメーションの処理の実装
テキストを動かすアニメーションの部分は、CSS か JavaScript の処理により自前での実装もできる部分ですが、後々、表示のさせ方を工夫したくなった際などの拡張性を考慮すると、ライブラリを使っておいたほうが良いと考えました。
以下の記事など、まとめ記事をいくつか見るなどして、最終的に速度・多機能製に優れると書かれていた「GSAP」を用いることにしました。
●現場で使えるアニメーション系JSライブラリまとめ(2020年版) - GSAP, CreateJS, WebAnimation, Velocityなど - ICS MEDIA
https://ics.media/entry/14973/そして、簡素な Webページを作り、動的に生成した要素を動かせることを確認しました。
(※「着手するまでの流れ2(今回の内容の着手)」の項目に掲載していた動画の内容)実装したソースコードについて
ライブラリの調査・選定と機能テストを行い、その後に全体の実装を行いました。そこで書いた処理部分は「実質20行ほど」です(「実質」という部分に含まれていないのは HTMLの必須の構成要素で、今回用に手を加えてない部分)。
冒頭の動画で、OBS で合成していたテキストが流れる画面は、以下のようなものになります。
以下がソースコードですが、今回、「サクッと自分が試せれば良いや」という発想で作った段階のものであるため、ワンソースで適当な実装になっています(エラーハンドリング的なもの等は入ってないです)。もし、「こんな実装のほうが良いのでは?」等といったことがありましたら、記事のコメント欄にてコメントをいただけましたら幸いです。
<!DOCTYPE html> <html style="background:#00FF00;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ニコニコ動画風のコメント表示</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js"></script> </head> <body> <button id="button01">テキストを流す</button> <script> const button01 = document.getElementById("button01"); button01.addEventListener("click", function(){createText()}, false); let count = 0; async function createText() { let div_text = document.createElement('div'); div_text.id="text"+count; //アニメーション処理で対象の指定に必要なidを設定 count++; div_text.style.position = 'fixed'; //テキストのは位置を絶対位置にするための設定 div_text.style.whiteSpace = 'nowrap' //画面右端での折り返しがなく、画面外へはみ出すようにする div_text.style.left = (document.documentElement.clientWidth) + 'px'; //初期状態の横方向の位置は画面の右端に設定 var random = Math.round( Math.random()*document.documentElement.clientHeight ); div_text.style.top = random + 'px'; //初期状態の縦方向の位置は画面の上端から下端の間に設定(ランダムな配置に) div_text.appendChild(document.createTextNode('移動するテキスト'+count)); //画面上に表示されるテキストを設定 document.body.appendChild(div_text); //body直下へ挿入 //ライブラリを用いたテキスト移動のアニメーション: durationはアニメーションの時間、 // 横方向の移動距離は「画面の横幅+画面を流れるテキストの要素の横幅」、移動中に次の削除処理がされないようawait await gsap.to("#"+div_text.id, {duration: 5, x: -1*(document.documentElement.clientWidth+div_text.clientWidth)}); div_text.parentNode.removeChild(div_text); //画面上の移動終了後に削除 } </script> </body> </html>動作確認は Mac版の Chrome(バージョン: 84.0.4147.135)で行っていました。
なお、今回、画面で流しているテキストは
div_text.appendChild(document.createTextNode('移動するテキスト'+count));
の部分で生成したものですが、仮のものなので今後は手を加えます。具体的には、外部から MQTT などでリアルタイムに受け取ったテキストを表示させる形にするか、MQTT などでリアクションの ID的なものを受け取って ID に紐付いたテキストを表示させる、という実装を考えています。
配信用などに合成する(OBS が関わる部分)
上記を用いることで、グリーンバックの Webサイトにテキストが流れる画面が出せるようになりますが、配信用などに用いるにはクロマキー合成を行う必要があります。
詳細は割愛しますが、例えば一例として OBS を使って以下の対応をすれば OK です。
- PC に外部モニタをつけるなど画面を 2つにし、一方に OBS、もう一方にブラウザ(今回作ったものを表示)を置く
- OBS の「シーン」の「ソース」の中にコメント表示を重畳させたい対象を追加(記事の冒頭の動画は「映像キャプチャデバイス」で「PC内蔵のカメラ」を設定しています)
- OBS の「シーン」の「ソース」の中に「画面キャプチャ」も追加し、上記1)のブラウザを指定
- 上記2)で追加した 「画面キャプチャ」の「フィルタ」で「クロップ/パッド」と「クロマキー」の 2つを設定
- 「クロップ/パッド」のフィルタ設定で「上」の数値を増やし、ブラウザのタブの部分などが見えないようにクリッピングする(「クロマキー」のフィルタ設定は、今回の記事内の背景色を指定していれば、特別な設定は不要)
既存のサービスやプロトタイプ
ここでは、今回作ったもの・今回の作ったものの先にある流れと同じ方向性のもの・同じ機能を備えたもので、使ったことがあるもの・気になっているものをいくつか記載します。
オンラインイベント等で使えるコメントやリアクションを表示するためのサービス
Comment Screen
今回の内容は「ニコニコ動画風のコメント表示機能を作る」というものですが、オンラインイベントで活用できるコメント表示サービスで例えば「Comment Screen」があります。
こちらは、コメント等を受ける側がアプリを起動すると、PCのデスクトップ上にコメントやアイコンなどを重畳表示するためのレイヤーができ、コメントを送る側が入力したテキスト等がPCのデスクトップに重畳表示されます。
このサービスは、自分が運営メンバーとなったイベントや、一般参加者になったイベントで何度も使われていてお世話になっています。リモート擬音さん
また、ニコニコ動画風ではないですが、オンラインイベントで参加者がリアクションをしたら、登壇者側の画面にリアクションに応じた表示が行われるものに「リモート擬音さん」というものがあります。
以下の使い方の記載にあるように、表示側の仕組みは Comment Screen とは異なります。こちらは、グリーンバックで構成された Webサイトが生成されるので、それを OBS などでキャプチャしてクロマキーを適用する、という使い方です。
上記以外のオンラインフィードバックシステムのプロトタイプ
「いいね!」を受け取る仕組み+可視化
その他、イベントでプロトタイプに関するLTが行われたもので、例えば以下のものがあります。
●「作った登壇リアクションシステムをベースにNode-REDダッシュボード機能をゆるく紹介」 - ビジュアルプログラミングIoTLT vol3 - Speaker Deck
https://speakerdeck.com/1ftseabass/biziyuarupuroguraminguiotlt-vol3
オンラインイベント用ではなく、発端はオンライン授業で生徒さんの反応を受け取るため、という感じとのことです。オンラインで「いいね!」のリアクションを受け取り、その 5秒ごとの推移をグラフ化したりなどしています(可視化の部分は Node-RED による実装)。
リアクションを受け取り音にする
こちらも、イベントでプロトタイプに関するLTが行われたものです。
YouTube Live のアーカイブはこちら ⇒ https://youtu.be/lp3-ywQKKAc?t=966
仕組みとしては、登壇者側と視聴者側とを WebSocket でつなぎ、それを登壇者側はラズパイで受信する形のようでした。ラズパイは、リアルタイム通信で視聴者側のリアクションのトリガーを受け取り、そのタイミングで音声を再生してPCへはマイク入力で音を流し込む、というもののようでした。
終わりに
今回、ニコニコ動画風のコメント表示機能を「素の JavaScript とアニメーション用ライブラリ(GSAP)」を使って実装する、ということをやりました。
現状は、表示するテキストは内部で生成しているので、この部分は本文でも書いたとおり MQTT などで外部から受け取る形に変えようとしているところです。
- 投稿日:2020-08-30T14:24:55+09:00
JavaScriptでカンマ区切りCSVとタブ区切りテキストの双方をパース
外部プラグイン利用不可、サーバサイド処理不可という条件で、JavaScriptのみでCSVのパースが必要だったので、実装した処理をメモです。
必要要件
- システム側から出力されるタブ区切りテキストの取り込み
- ユーザーがExcelから出力したCSVの取り込み
- セルの中にカンマが入る事もある
- Excel出力したファイルは、必要なセルしかダブルクォーテーションで囲まれない
以上の事から、単純にsplitできる物ではなかったため、実装処理を備忘録として残します。
実際には、さらにSJISとUTF-8(BOM付き)のどちらのパターンも対応する必要がありましたが、ここではそれは割愛しますが、データを読み込んで、先頭にBOMがついていれば、文字コード指定して再読み込みする処理をいれて対応しました。
実装
簡単に言えば、
1. 行末改行で区切ってループ
2. ダブルクォーテーションの数が奇数なら次の行と結合して行データにする
3. 行データごとにループ
4. 先頭からセルを切り出すという流れで構築しています。
Excelから出力したCSV(UTF-8)id,"nam,e",price,about,,d 1,スイカ,1200,熊本産,a,e 2,"メロン メロン2","2,500","マスクメロン 贈答品にお勧め",b,f 3,そら豆,500,塩茹でしてビールとともに,c,gタブ区切りテキストid nam,e price about d 1 スイカ 1200 熊本産 a e 2 "メロン メロン2" 2,500 "マスクメロン 贈答品にお勧め" b f 3 そら豆 500 塩茹でしてビールとともに c gindex.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <link rel="stylesheet" href="style.css" /> </head> <body> <form name="form"> <input type="file" name="filechoose"> </form> <table></table> <script src="script.js"></script> </body> </html>style.css@charset "UTF-8"; table { background: #ccc; border-collapse: separate; border-spacing: 1px; } th, td { min-width: 80px; padding: 5px 10px; } th { background: #efefef; } td { background: #fff; }script.jsconst table = document.querySelector('table'); const input = document.querySelector('[name="filechoose"]'); const reader = new FileReader(); const make_col = (label, tag) => { const col = document.createElement(tag); if (label !== null) col.innerHTML = label.replace('\n', '<br />').replace('\x22\x22', '\x22'); return col; } const make_row = (line, tag) => { const tr = document.createElement('tr'); line.forEach(label => { tr.appendChild(make_col(label, tag)); }); return tr; } // 画面出力 const show = (header, items) => { table.innerHTML = ''; table.appendChild(make_row(header, 'th')); // 値 items.forEach(line => { table.appendChild(make_row(line, 'td')); }); } // ファイル選択確認 const file_choosed = () => { if (input.files.length === 0) return; const file = input.files[0]; reader.readAsText(file); } // ファイル読み込み const file_load = () => { let csv = reader.result; // 空データなら処理しない if (csv.length === 0) return false; // Excel出力されたCSVの最終行の空行を無視 // Excel出力されたセル中の改行は\n、行末の改行は\r\n(macは行末\nかも...) csv = csv.replace(/\r\n$/, ''); console.log(csv); // 改行区切りしたデータ const parse = csv.split(/\r\n/m); let data = []; // 行データごとにまとめる let line = []; let quot = 0; for (const current of parse) { const this_quots = current.match(/\x22/g); if (this_quots == null && line.length == 0) { data.push(current); } else if (this_quots == null) { line.push(current); } else { quot += this_quots.length; line.push(current); if (quot % 2 == 0) { data.push(line.join('\r\n')); quot = 0; line = []; } } } /** * セル区切りパターン */ // 空セル const pt_1 = new RegExp(/^\x22?[,\t]\x22?/, 'm'); // ダブルクォーテーションつきのセルはダブルクォーテーション後カンマ、タブが出てくるまで const pt_2 = new RegExp(/^\x22([^\x22]+)\x22(\,|\t)/, 'm'); // ダブルクォーテーションなしのタブ区切りでカンマが出てくるパターン const pt_3 = new RegExp(/^([^\t]+)(\t)/); // ダブルクォーテーションで囲まれていない場合はカンマかタブが出現するまで const pt_4 = new RegExp(/^([^\,\t]+)(\,|\t|$)/); // ヘッダ、内容出力データ用配列 const header = []; const items = []; before = null; data.forEach((v, i) => { cell = null; while (v.length > 0) { if (pt_1.exec(v)) { cell = [pt_1.exec(v)[0], null, null]; } else if (pt_2.exec(v)) { cell = pt_2.exec(v); } else if (pt_3.exec(v)) { cell = pt_3.exec(v); } else if (pt_4.exec(v)) { cell = pt_4.exec(v); } else { // 最終行 cell = [v, v, v]; } if (i === 0) { header.push(cell[1]); } else { if (typeof(items[i - 1]) === 'undefined') items[i - 1] = []; items[i - 1].push(cell[1]); } v = v.substring(cell[0].length, v.length); } }); // 画面出力 show(header, items); } // Events input.addEventListener('change', file_choosed, false); reader.addEventListener('load', file_load, false);取り込み結果
あとがき
タブ区切りテキストで、ダブルクォーテーションで囲まれていないセルの中に、ダブルクォーテーションが入っている時の処理も入れたら、ほぼほぼカバーできます。この処理はさらに精度を高めた処理にしていきたいと思います。
- 投稿日:2020-08-30T13:34:02+09:00
mediaQueryの記述を効率化するためのラッパーを npm package で作ってみた
はじめに
毎回微妙に違うけど似たようなロジックを、プロジェクト毎に組んでしまいがちです。
どうせいつも使うなら、自分用にnpm package
化し、方針変更をバージョンアップで受け取れるようにしておく方が、理にかなってるような気がしたので、今回学習と実験を兼ねて作ってみました。簡単に作れそうなものとして、css の
MediaQueries
を題材に選んでみました。おそらく似たようなpackageは大量に存在しているとは思っていますが、自力で車輪を発明することも今回の趣旨なので、ご容赦を。
大枠の方針
link要素
での使用は禁止。.class
内で細かく分岐させる。- breakpointは 任意の数を定数で管理。プロジェクトに応じて簡単にoverride可能にする。
- MediaQueries用のラッパー関数を中継して記述。生では書かない。
- mobile first / desktop first のどちらの方針も対応できる。
window.matchMedia
を使い、可能な限り似たロジックでjs
版もつくる。今回作った2つの npm package
scss用とjs用として2つに分割して作成。
ほぼ自分用の側面が強いので名前をscoped modules
にします。(統合する方針で当初構想したが、そもそも、 MediaQueries 管理をしないと行けない状況は最終手段感がある、 命名が完全には揃えられない、使うべき理由がそもそも大きく異なるはず、等の理由で分けて扱う事に。)
原則scssで完結できる世界を願い、jsの方はおまけです。
mq-scssの方針
cssコーディングは、絵を扱うという性質上、まま理不尽な状況が多い気がします。
また webサイト なのか webアプリ なのかといった違いで、求められるブレイクポイントの粒度が全然異なります。
これらの事情を鑑み、安全に運用するために、グローバルなbreakpoint管理
とイレギュラーな状況に対応できる
の2点を同時に担保できる方法を目指します。インストール
$ npm i -D @rm-labo/mq-scss@import 'path/to/node_modules/@rm-labo/mq-scss/_mq.scss';JavaScriptのエントリポイントからCSSを処理する場合は、スタイルシートをモジュールとして直接インポート。
import '@rm-labo/_mq.scss'使い方
3種類 * 2方向の@mixinsを、キャメルケースかケバブケースで使えるようにした。
任意の箇所でで使うときは以下。各mixinは以下の場面で使う。
mq-{'width'|'height'}-up($bp) {}
: スマホファーストの記述用mq-{'width'|'height'}-down($bp) {}
: デスクトップファーストの記述用mq-{'width'|'height'}-between($bp1, $bp2) {}
: 特定範囲用// CamelCase or kebab-case .class-name { // width camelCase @include mqUp($bp) {} @include mqDown($bp) {} @include mqBetween($bp1, $bp2) {} // width kebab-case (same as camelCase) @include mq-up($bp) {} @include mq-down($bp) {} @include mq-between($bp1, $bp2) {} // height camelCase @include mqHeightUp($bp) {} @include mqHeightDown($bp) {} @include mqHeightBetween($bp1, $bp2) {} // height kebab-case (same as camelCase) @include mq-height-up($bp) {} @include mq-height-down($bp) {} @include mq-height-between($bp1, $bp2) {} }
arguments type required default value description $bp string
,number
false
md
sm
,md
,lg
,xl
,Free px number
$bp1 string
,number
true
- sm
,md
,lg
,xl
,Free px number
$bp2 string
,number
true
- sm
,md
,lg
,xl
,Free px number
全体で共用するbreakpointが!defaultで定義されている。
// デフォルト値 $mq-breakpoints: ( sm: 600px, md: 900px, // default lg: 1200px, xl: 1800px ) !default; $mq-breakpoints-default-key: 'md' !default;プロジェクトに合わせてoverrideして使う。
// override例 $mq-breakpoints: ( micro: 320px, small: 620px, medium: 840px, // default large: 1280px, extra: 1900px, // .. 好きなだけ追加 .. ); $mq-breakpoints-default-key: 'medium'; @import 'path/to/node_modules/@rm-labo/mq-scss/_mq.scss';基本的にいはスマホファースト且つ、$breakpointsで定義した共用の値のkey
sm
md
lg
xl
を引数として受け取るが、その一箇所でしか使わないイレギュラーケース用として、ピクセル値による指定も許容するよう実装する。
また、PC/スマホだけのような1ブレイクポイントの簡易的な実装も多いため、省略してスッキリかけるようにも対応する。.className { // SINGLE BREAK POINT @include mqUp() { /* 900px (default: md) and up */ } @include mqDown() { /* 900px (default: md) and down */ } // MOBILE FIRST POLICY @include mqUp('sm') { /* 600px and up */ } @include mqUp(900px) { /* 900px and up */ } // イレギュラーな px数でもOK @include mqUp('lg') { /* 1200px and up */ } @include mqUp('xl') { /* 1800px and up */ } // DESKTOP FIRST POLICY @include mqDown('xl') { /* 1799px and down */ } @include mqDown(1200px) { /* 1199px and down */ } // イレギュラーな px数でもOK @include mqDown('md') { /* 899px and down */ } @include mqDown('sm') { /* 599px and down */ } // SPECIFY BY RANGE @include mqBetween('sm', 'md') { /* 600px ~ 899px */ } @include mqBetween('sm', 'lg') { /* 600px ~ 1199px */ } @include mqBetween('sm', 1600px) { /* 600px ~ 1599px */ } // 混在OK @include mqBetween(1234px, 850px) { /* 850px ~ 1233px */ } // 大小逆もOK }mq-jsの方針
原則cssで完結するべきだが、どうしてもjsによるサポートが必要なケースもある。
そのため、scssとなるべく近いコンセプトのjsもおまけで作ってみた。window.matchMedia を使い、ブレイクポイントのkeyの名前とcallbackを受け取るようなラッパーを生成する方針。
インストール
$ npm i -D @rm-labo/mq-jsimport Mq from '@rm-labo/mq-js'使い方
読み込んでインスタンスを作り、それに対して以下のようなメソッドを実行していきます。
当たり前ですがjsなので kebab-case はありません。import Mq from '@rm-labo/mq-js' const mq = new Mq() mq.up( bpKeyName1, matchHandler [, unmatchHandler ]) mq.down( bpKeyName1, matchHandler [, unmatchHandler ]) mq.between( bpKeyName1, bpKeyName2, matchHandler [, unmatchHandler ])パラメータは以下
arguments type required bpKeyName1 string
true
bpKeyName2 string
true
matchHandler function
true
unmatchHandler function
false
// 単一ブレイクポイントの場合 mq.up( 'sm', () => { /* Fires in 600px and up */ }, () => { /* Fires in 599px and down */ } ) // 別の明示的な書き方もできるように mq .down('md', () => { /* Fires in 899px and down */ }) .up('md', () => { /* Fires in 900px and up */ }) // 特定の範囲だけを対象にしたい場合 mq .between( 'md', 'lg', () => { /* Fires in 900 ~ 1199px */ }, () => { /* Fires in ~ 899px and 1200px ~ */ }) // すべての範囲を網羅したい場合はメソッドチェーンでつなげる mq .down('sm', () => { /* Fires in 599px and down */ }) .between('sm', 'md', () => { /* Fires in 600 ~ 899px */ }) .between('md', 'lg', () => { /* Fires in 900 ~ 1199px */ }) .between('lg', 'xl', () => { /* Fires in 1200 ~ 1799px */ }) .up('xl', () => { /* Fires in 1800px ~ */ })scssと同様で高さを対象にできるように。
mq .heightUp(bpKeyName1, matchHandler [, unmatchHandler ]) .heightDown(bpKeyName1, matchHandler [, unmatchHandler ]) .heightBetween(bpKeyName1, bpKeyName2, matchHandler [, unmatchHandler ])ブレイクポイントもoprion経由でoverrideできるようにする。
// 例 const option = { breakpoints: { 'micro' : 320, 'small' : 620, 'medium' : 840, 'large' : 1280, 'extra' : 1900, // .. 好きなだけ追加 .. } } const mq = new Mq(option) mq .down('micro', () => { /* Fires in 319px and down */ },) .between('micro', 'small', () => { /* Fires in 320 ~ 619px */ },) .between('small', 'medium', () => { /* Fires in 620 ~ 839px */ },) .between('medium', 'large', () => { /* Fires in 840 ~ 1279px */ },) .between('large', 'extra', () => { /* Fires in 1280 ~ 1899px */ },) .up('extra', () => { /* Fires in 1900px and up */ },)demo
実際に動かしているページも一応用意した。
もう少しスマートな方法もありそうではあるが、一旦これを使ってみる。
課題
pxベース限定になっている点。
emベースなど別の単位で管理したいケースもあるが今回は実装を見送った。バージョンアップで対応するかもしれない。
(em等、px以外の単位で MediaQuery を書きたい場合に必要なcalc計算が、一部レガシーブラウザ待ちの状況。)
- 投稿日:2020-08-30T10:51:54+09:00
初投稿
どうも
エンジニア就職を目指している20代です
現在はTechCamp82期生として最終課題に取り組んでいます
就職までの学習を記録します
既存の内容も多く投稿することになると思います
- 投稿日:2020-08-30T08:41:14+09:00
Array.map() あれこれ
Array.map() あれこれ
関数の中でmapが好きなので、いろいろな言語のmapを調べてみました。
※ここで言うmapは、主に配列(言語によってはリスト)を受け取って、内部で処理して(多くは無名関数などのコールバック関数)、新しい配列を作って返すような言語に込み込みの標準関数を想定しています。
mapの気に入っているところ
個人的に下記が気に入っている理由です。
- ループ処理を書かなくて良い
- 元の配列に影響しない
- コードが短くて済む
JavaScript
JSは最近コード書くことが多いのですが、forやforEachよりもmapを良く使います。
const array = ['Perl', 'Ruby', 'Scala']; console.log(array); let array2 = array.map(v => { return `"Hello ${v}!"`}); console.log(array2);Ruby
RubyのmapはArrayクラスの実装では無く、Arrayクラスが継承するEnumerableモジュールにあるんですね。
array = ['Perl', 'Ruby', 'Scala'] p array array2 = array.map {|v| "Hello #{v}!"} p array2Perl
map歴はPerlからですが、Rubyを使った時にPerlとmapへの配列の渡し方が違うので戸惑いました。
my @array = ('Perl', 'Ruby', 'Scala'); print("@array\n"); my @array2 = map { "Hello $_!" } @array; print("@array2");Scala
ScalaはJavaとRubyなどのスクリプト言語の手軽さを合わせたような言語です(個人的なイメージ)
val array = List("Perl", "Ruby", "Scala") println(array) val array2 = array.map(v => s"Hello $v!") println(array2)Clojure
Scalaと同じく、JVM上で動作するLisp記法の言語で関数型と並列処理が強み(個人的なイメージです)
(def array ["Perl" "Ruby" "Scala"]) (println array) (def array2 (map #(format "Hello %s!" %) array)) (println array2)https://clojuredocs.org/clojure.core/format
PHP
PHPも無名関数が使えるんですね。
<?php $array = ['Perl', 'Ruby', 'Scala']; var_dump($array); $array2 = array_map(function ($v){return "Hello " . $v . "!";}, $array); var_dump($array2); ?>Swift
Swift使ったこと無いですが、型推論があるようでスクリプト言語っぽく書けるので中々良いかもです。
変数の文字列展開は(変数名)な感じで書けます。
let array = ["Perl", "Ruby", "Scala"] print(array) let array2 = array.map {"Hello \($0)!"} print(array2)Python
大人気言語パイソンです。Pythonでは無名関数の定義にlambdaと言う宣言?を使うようです。
array = ["Perl", "Ruby", "Scala"]; print(array); array2 = list(map(lambda v: "Hello " + v + "!", array)); print(array2);lambda
f-string
array2 = list(map(lambda v: f"Hello {v}!", array));
リスト内包表記Haskell
Haskellです。関数型言語で型推論を持つ言語です。(多分)
main = do let array = ["Perl", "Ruby", "Scala"] print array let array2 = map (\ v -> "Hello " ++ v ++ "!") array print array2感想
kintoneアプリ開発の会社に所属している為、普段は限定された範囲のJavaScriptしか書かないのですが、書籍を持っている範囲の言語で1つの関数の使い方をまとめてみると、言語によっていろいろな考え方などを発見することが出来たので、良かったと思います。
JavaScriptで関数言語のような使い方に傾倒しているので、ScalaとClojureは書きやすいなと感じています。(あと、Swiftも意外と違和感が無かった)
参考
動作確認には paiza.IO を利用させていただきました。ありがとうございます。
- 投稿日:2020-08-30T06:30:52+09:00
[JavaScript] クロージャー(closure) をわかりやすくまとめてみた No.1
なんと、このコードもクロージャーなんです。
const myName = 'rika' function printName(){ console.log(myName) } PrintName() // rika他の言語では、functionの内側の変数から外側の変数にアクセスすることは出来ないのですが、JavaScriptだとそれが可能になります。それを「クロージャー」と呼びます。
上の例でも、グローバル変数myNameをfunctionの中での使用することが出来るんです。
一番使われるクロージャーの形はfunction内のfunction!
function outerFunction(outerVariable) { return function innerFunction(innerVariable) { console.log(`Outer Variable: ${outerVariable}`) //結果 outside console.log(`Inner Variable: ${innerVariable}`) //結果 inside } } const newFunction = outerFunction('outside') newFunction('inside')ここでもouterVariableが、innerFunction内でもアクセス出来ちゃってます。つまり「クロージャー」が使われているということですね。
たとえば、functionの外側に新しく変数を置いてみましょう。
function outerFunction(outerVariable) { const name = 'rika' 定数nameを追加してみました。↓ return function innerFunction(innerVariable) { console.log(`Outer Variable: ${outerVariable}`) //結果 outside console.log(`Inner Variable: ${innerVariable}`) //結果 inside console.log(name) //結果 rika と出力されます } } const newFunction = outerFunction('outside') newFunction('inside')つまり、functionの内側から、外側の変数・定数にアクセス出来ますよー!というのがクロージャーの特徴になりますね!
なぜクロージャーを使うのか?
たとえばグローバル変数を使えば、どこからでもアクセスが出来ます。
ですが、あえてクロージャーを使う利点をあげるとすれば、その名の通り外部から変数にアクセスできない仕組みになっているからです!(カプセル化と呼びます)これはコードの安全性を高める上で重要なテクニックです。クロージャーのテクニックを利用することでより安全で保守性の高いプログラムにすることが可能です。
- 投稿日:2020-08-30T03:42:03+09:00
5chの「なんJ」でAAが「貼れない」規制を突破できるようにAAを変換するサイト (をgithub.ioで無料公開する方法)
AAを貼れるように変換するサイトはこちら
上のurlにアクセスするとAAを貼れるように変換できます
使い方は入力フォームにAAをコピーして「実行」ボタンを押すだけ
変換ずみのAAがクリップボードにコピーされます=========================
現時点でも「なんj AA 貼れない」などと検索しても出てこないのでここで紹介しておきます。qiitaなら多分上位に来ると思うので
chmateなど専ブラがAA変換に対応してくれればいいのにって感じだけど
ちなみにこの記事の著者は製作者じゃありません=========================
なんJなど実況系板のAA規制とは
一定以上空白がある文章が書き込めなくなったため、5chの実況系板でほとんどのAAがはれなくなった規制。
スクリプトの内容
この変換サイトだと
空白やらNG判定になりそうなところを読み込んで
置き換えて
新しい文字列の固まりとして出すのは
jsのプログラミング部分でやってるそれをクリックポチポチッてできるWebのサイトとして成り立たせてるのはHTMLとgithub.ioの鯖
- index.html
サイトにアクセスすると基本的にindex.htmlが最初に表示されます。
<!DOCTYPE html> <meta charset="utf-8"> <script src="index.js"></script> <link rel="stylesheet" href="index.css"> <title>j-j-j-j</title> <div> <textarea id="input" cols="64" rows="16" onfocus="this.select();"> /.i /i∠__ノ /.i∠__ノ‘j’ )ノ エーライヤッチャ ∠/.i‘j’ )ノ==|┘ 〈,(∠__ノノ /.i__〈 エーライヤッチャ 〈,( ‘j’∠_/.i └i=〈,(∠__ノノ 〈__〈,( ‘j’ )ノ ジェイジェイジェイジェイ └i===|┘ 〈__〈</textarea> </div> <div> <button type="button" onclick="main()">実行</button> <p id="result"></p> </div> <div> </div>3行目の<script src="index.js"></script>で「index.jsに以下の内容を受け渡す」ということを宣言しています
2行目<link rel="stylesheet" href="index.css">はスタイルシートといい、サイトのデザイン(アイコン画像など)を決める.cssファイルを指定しています
4行目の<textarea id="input" cols="64" rows="16" onfocus="this.select();">は入力フォームを作っています。ここで渡された情報はindex.jsに渡されます。
index.jsではfunction escapeToUnicode(s)という関数内で文字列をunicodeに変換(これで5chの規制は適用されなくなる) 、function clipboard(s) でクリップボードにコピーします
- index.js
let CHARS = ' .,\'`"\\:;\\-=~_|\/<>'; function main() { let s = document.getElementById('input').value; s = escapeToUnicode(s); clipboard(s); checkByteCount(s); } function escapeToUnicode(s) { let r = /[ \n][ ]{3}[ \n]/; while (r.test(s)) { s = avoid(s, r); } r = RegExp(`[${CHARS}\n][${CHARS}]{3}[${CHARS}\n]`); while (r.test(s)) { s = avoid(s, r); } return s; function avoid(s, r) { return s.replace(r, (sub) => { // 最後の1文字を数値参照化 // 最後の1文字が改行の場合は最後から2文字目を数値参照化する if (sub.slice(-1) === '\n') { return sub.slice(0, -2) + toEntity(sub.slice(-2, -1)) + '\n'; } else { return sub.slice(0, -1) + toEntity(sub.slice(-1)); } }); } function toEntity(c) { return '&#' + c.charCodeAt() + ';'; } } function clipboard(s) { const tempElement = document.createElement('textarea'); tempElement.style = "position: absolute; left: -9999px; top: -9999px"; tempElement.value = s; document.body.appendChild(tempElement); tempElement.select(); document.execCommand('copy'); document.body.removeChild(tempElement); const result = document.getElementById('result'); result.textContent = 'クリップボードにコピーされました'; result.className = ''; } function checkByteCount(s) { // バイト数チェック // UTF-8ではなくShift_JISでカウントされる const bytes = countBytes(s); if (bytes > 4096) { result.textContent = `総バイト数が${bytes - 4096}バイト超過 連続するスペースや記号を減らしてください`; result.className = 'error'; } s .replace('\n', ' <br> \n') .split('\n') .some((s, i) => { const bytesOfRow = countBytes(s) if (bytesOfRow > 1024) { result.textContent = `${i + 1}行目が${bytesOfRow - 1024}バイト超過 連続するスペースや記号を減らしてください`; result.className = 'error'; return true; } else { return false; } }); function countBytes(s) { let bytes = 0; for (let c of s) { let code = c.charCodeAt(); if ((code >= 0x0 && code < 0x81) || (code >= 0xff61 && code < 0xffa0) || (code >= 0xf8f0 && code < 0xf8f4)) { bytes += 1; } else { bytes += 2; } } return bytes; } }こういう簡単なフロントエンドのwebアプリなんでソース見ればやってることはすぐわかるよね
以下のgithubのレポジトリに置いてあります。
https://github.com/j-j-j-j/j-j-j-j.github.io
github.ioでの公開の仕方
GitHubリポジトリ画面の「Settings」 → GitHub Pages → Source → master branch → Save ボタン → https://ユーザ名.github.io/リポジトリ名 にサイトが公開される
5ch運営はなぜAAを規制したのか
5ch運営の行動は基本的に不可解なので追求するだけ無駄。
運営がjimワトキンスになってから日本語読めない人が技術的な部分を担うようになってる(jimやcode monkeyは8chanの運営にしか興味ない)ので仕方ない
米国では掲示板のRedditのDAUやアクセス数(平均して全米4位-7位にいる)がinstagramやtwitterなどSNSを上回っているというのに、遥か前から掲示板文化のある日本ではゴミみたいな5chや爆サイが最大の掲示板で、どんどん衰退してるというのは、不幸なこと(板やスレのあるシステムは不特定多数との交流に本当に優れている)
5ch運営がタグ導入によるSEO最適化や、旧態依然としたUIでもChmate等の専用ブラウザ導入を促すなどやっていれば状況は違ったかもしれないけど、今も昔も運営にやる気がないので仕方ない
5ch+chmateは軽量でジェスチャーなどredditよりも使いやすいところが多いのに専門板はこういう意味不明な規制や荒らし放置で人が減るばかり、伸びてる板はなんJやyoutube板のようなゴミばっか、
一方のredditの日本語コミュニティーはAPI規制で移住したケンモメンの残党(ほとんどはchmateなどが使えるようになった時点で嫌儲に帰った)のあつまるnewsokurが内ゲバで崩壊
日本でも掲示板文化の復興が望まれます
5chでもredditでもいいので。。。
- 投稿日:2020-08-30T02:16:26+09:00
GitHubリポジトリのファイルをクロスオリジンで配信する超簡単な方法
小ネタです。
ちょっとしたスクリプトを配信したいな〜というとき。
GitHubリポジトリのファイル実体が保管されている
https://raw.githubusercontent.com
から直接src
属性などで引っ張ろうとしても、レスポンスヘッダにAccess-Control-Allow-Origin: *
がないためCORSエラーで弾かれてしまいます。CORS-Error<script src="https://raw.githubusercontent.com/dojyorin/myrepo/master/dist/index.min.js"></script>jsDelivrを使おう
実は、みんな大好きいつもお世話になっているjsDelivrさんからGitHubレポジトリのファイルへ クロスオリジン でアクセスできます!!
準備
jsDelivrでの配信準備はとても簡単。
- タグを発行する
- タグをpushする
- おわり
タグがリモートリポジトリと同期されると、jsDelivr側も即座に反映されます。
アクセス方法
タグが同期されたら、あとは以下のようにタグ名ごとのURLでアクセスできます。
タグ名をlatest
にすれば常に最新版をロードできるのはいつもと変わりません。CORS-OK<script src="https://cdn.jsdelivr.net/gh/dojyorin/myrepo@tagname/dist/index.min.js"></script>
- 投稿日:2020-08-30T01:04:37+09:00
[高階関数/Higher-order function] 英語の記事・動画まとめ No.1
The coding trainの解説
元動画: https://www.youtube.com/watch?v=H4awPsyugS0
function hello () { }上は普通のfunction。
高階関数とは2つ以上の関数(function)を使用するということ。それはparameterに入れたり、戻り値としてfunctionを設定したり、いろんなことが出来るんです。
function hello(function) { return funtion }具体的にみてみよう
1) 例えば以下のようにsingという関数がla la la -- という文字数を出すとしましょう
function sing(){ console.log('la la la la la') }2) la la la laの他にも他の音を一緒に出したい時はどうしましょう?
このような時は、別でmeow meowというfunctionを作って、singの引数として渡してしまえるんです!function sing(callback){ console.log('la la la la la') callback(); } function meow(){ console.log('meow meow') }3) 以下のようによりシンプルな表現にすることも可能です
sing(function(){ console.log('meow meow'); });function を戻り値にする場合
function multiplier (x, factor) { return x * factor; }でも、もしこの計算をしてくれるfunctionを戻り値として出したい場合はどうなるでしょうか?
function multiplier (factor) { return function(x) { return x * factor; } }1) multiplierに数字を入れて、その結果を変数に入れてみると・・・
let doubler = multiplier(2)といれると、undefined となりますが、
ここで戻ってきているのは関数・・・そう、function(x){return x * 2}ということになる!!2)その次に,doubler(2) と入力すると、 そう・・・・ 4が結果として表示されます!!
- 投稿日:2020-08-30T00:48:02+09:00
ReduxとFluxはどう違うのか
Reduxはわかった。Fluxとはなんなのだ?
React/Reduxは昨今のWebフロントではよく見る構成なので、学んでいる方は多いと思う。
Reduxの関連文献を読むと度々Fluxという言葉が出てくるがあれは一体なんなのか。この記事では、Reduxとの違いを意識しながらFluxを紐解いていく。
Fluxとはアーキテクチャである
Facebookが生み出したクライアントサイドウェブアプリ用のアーキテクチャである。
Reduxはライブラリの名前であるが、Fluxはアーキテクチャの名前である。つまり特定のライブラリを指す言葉ではない。
(とはいえfacebook/fluxがutilを提供しており、これが使われることが多い)このアーキテクチャの実装を助けるライブラリが多数生まれたが、その中で一際人気を誇ったのがRedux。
ReduxはFluxのような単一方向のデータパスに基づく設計思想になっているが構成要素に違いがある。なぜFluxなのか MVCでは難しい実装ケース
ReduxとFluxの話に入る前に、もう一つ有名なアーキテクチャのMVCの話を最初にする。
MVCモデルではどんな時に不都合なのだろうか。端的に書くとMVCでは1つのデータソースを複数箇所のUIから操作されるようなケースでコードが複雑になる。
公式の例ではチャットの未読数のカウント表示が挙げられている。
- チャットは不特定多数の人から送られ、その内どれかが既読になればカウントを-1する
- 2カ所からチャット開けるならそれぞれの箇所で既読時にカウントを-1するロジックをいれる
- 2カ所に存在するカウント表示にデータの変更を反映させる
このように、共通のデータを複数箇所から変更 & 参照する場合、Viewのデータソースとなる値をどこでどう管理するのかが難しくなる。
MVCでは複数箇所から更新され、複数箇所に対して更新を反映する複雑なデータフローになるところを、Fluxであれば単一で1方向のデータフローで表現することができる。Fluxの主要素
下記の要素がFluxの主要素と言われている。
- dispatcher
- store
- view
- action
いずれもReduxの経験者であればなんとなく耳にしたことがあるような単語ではあるが、それぞれ少しずつ意味合いが異なる。
dispatcher
storeに対してcallbackを登録し、呼び出しに応じてそれを起動する存在。
Action Creatorメソッドから呼び出され、新しいデータを持ったActionオブジェクトをStoreに伝える役割を持つ。
1つのdispatcherが複数のcallbackを登録することができ、データをブロードキャストすることができる。store
データを管理するオブジェクト。
getterを持つがsetterは持たず、dispatcherによって登録されたcallbackからのみ更新される。
Reduxとは異なり、storeは複数存在することができる。それぞれのstoreに対してdispatcherはcallbackを登録する。view
storeのデータを参照して作成されるアプリケーションの見た目の部分。
storeからデータを受け取るviewのことを Controller view と呼び、子孫コンポーネントに対してPropsを渡していく役割を持っている。
viewとController viewに分けることで、viewの責務をシンプルに保つことができる。Action
Actionはストアを更新する際の新しいデータを含んだオブジェクト。
dispatcherに対してActionを渡す関数をAction Creatorと呼ぶのはReduxと同じだが、FluxではActionsというオブジェクトのメソッドとしてAction Creatorが実装されることが多いらしい。(Flux公式でもAction Creatorはmethodであると書かれている)Fluxコードを体験してみたいなら
facebookがチュートリアル付きのexampleを提供しているので、Readme.mdに従ってコーディングをしてみるとFluxの書き味が体験できる。
flux-todomvcTodoMVCをFluxで作っていくチュートリアルで序盤は写経できるサンプルと解説、最後には解答無しの練習課題がついているので良い練習になる。
flux/utilを使用するので書き方はReduxに近いものがあり、Reduxでのアプリ制作経験があるの人ならそれほど時間をかけずに終えられると思う。
(筆者はこの記事を書くにあたってトライした)Reduxとの違い
上記のFluxの特徴と重複する部分はあるが、Reduxの違いにフォーカスしてまとめる。
dispatcherの存在
ReduxにDispatcherは無い。Store.dispatch()というメソッドはあるが別物。
上記の通り、dispatcherはstoreに対してcallbackを登録する役割を持つ。action creatorからDispatcher.dispatch()がcallされることからfluxの中心的な位置にいると言っていい。Reduxでは、dispatcherが登録するcallbackの役割をreducerが担っている。
action creatorの役割
dispatcherに関連してaction creatorの動きも少しだけ違う。
fluxのaction creatorはactionを引数にしてdispatcher.dispatchをcallする役割がある。Reduxにおけるaction creatorは呼ばれた時にactionを返却する。
creatorという名前のイメージにはこちらの方が近い。dispatcherとの結合がなくシンプル。middlewareが存在しない
Fluxはmiddlewareという仕組みを持たない。Dispatcher.dispatch()はあらかじめ登録したCallbackを呼ぶための処理であり、間に他の処理を挟むことはできない。
ReduxはmiddlewareによってdispatchされたActionをロギングしたり、エラーActionのハンドリングを行うなどReduxの処理フロー全体に影響を与えるような処理を含むことができる。
redux-thunk, redux-observableなどの非同期処理用のmiddlewareを活用することで、非同期処理の責務をらAction Creatorに持たせることができるようになるためComponentをシンプルに作りやすい。Fluxのstateは複数のstoreによって表現される
Fluxは複数のstoreを持つ。例えばTwitterのようなシステムであればUserInfoStoreとTweetListStoreのようなstoreを持つことになる。
これに対してReduxは1つのplain old Javascript object (POJO)でstateを表現する。アプリケーション全体の状態を扱いたいケースにおいては1つのオブジェクトである方が遥かに扱いやすい。
例えばSSRは、サーバーでstateを構築してクライアントに送信する必要があり単一のオブジェクトである恩恵を受けている。stateを追加する際にはFluxは新たなstoreを作成し、dispatcherから新たなcallbackを登録する。
Reduxは純粋な関数であるReducerを追加するのみであり、拡張がシンプル。終わりに
ReduxはFluxを基にして生まれたライブラリというだけあって使い勝手が良い面が多い。
Fluxを用いて新規にアプリケーションを作成する機会は少ないと思うが、アーキテクチャの話題では頻出単語なので正しく理解しておくと役立つと思う。
- 投稿日:2020-08-30T00:02:21+09:00
Dom国とJavaScript国の間の橋は狭い
昔聞いた先輩社員の名言
- DOM国とJavaScript国の間の橋は狭いので、頻繁に行き来しないように気をつけろと言われたのを思い出したので、ある意味初歩的なことではあるが、自戒も込めて記事にしておく
先に結論
- 要は
DOM操作は遅いので、JavaScriptの(特にループの中で)DOM操作を頻繁に行うと性能劣化につながる
というお話経緯/背景
- 私の所属するプロジェクトの終盤で、性能問題が顕在化した
- パッケージ製品を使っており、パッケージの大幅な更改があったのだが、その前後で画面描画が倍以上遅くなっている
- パッケージが新しくなっているのに、よく使うデフォルト的な画面が圧倒的に遅くなるのはありえない。1ヶ月以内になんとかしろと言うことに
- その画面は表形式のデータを表示する画面で、数千行(一度の画面描画は150件程度)が処理対象になる
- 列は10項目程度
原因
- ソースコードレベルまで追跡した結果以下2点が判明
1. 画面描画時にリサイズイベントが呼ばれ、同じ関数が2回呼ばれている
- 画面ロード時に自身とリサイズイベントで関数が2回呼ばれている。
- ただ実は、これは体感的には問題にならない
- なぜなら呼ばれる関数は描画に関数するもので、1回目が終わればレンダリングが行われ、その後の関数の再呼び出しと再レンダリングは見栄え上何も変わらないためである。
2. 二重ループの中でDOM操作
こちらが問題の本質
- 画面に表示される表の各セルの文字が見きれないように、それぞれの幅を計算して描画幅を調節している
- その過程で、行のループの中の列のループでセルごとに処理を実施
- セルごとに以下の処理を行う
- spanタグを作成
- append
- 幅を計測
- remove
- そのため、DOMアクセスが数万回単位の凄まじい回数で行われ、追加/削除も伴うため、DOM再構築も頻繁に行う
解決法
どちらも結構シンプルです
- そもそも別のイベントなので、リサイズのイベントを呼ばないようにする
- 毎回DOMの生成などをせずに、cssやその他のものを使って幅を計算する
- 今回は幅の問題でしたが、それ以外でもDOM操作を一括で行う。画面描画分だけを対象にするなど、工夫はいくらでもできると思います。
結果、画面描画に10秒近くかかっていたのが、1秒未満になりました。
某キャラ風にいうならば
今回の件から俺が得るべき教訓は、
DOM国とJavaScript国の橋は狭く輸出入のコストが高いので、エンジニアという監察官が、通して良いものと通さないものをきちんと吟味しないと、両国が損をすることになる。そしてそれは、流通量が増えたときにしか気づかず、そのときにはすでに手遅れになっている。
ということだ
ってことですね。