- 投稿日:2020-07-22T23:49:43+09:00
2020年中期のWebアニメーション実装の全容俯瞰
概要
Webページでアニメーションを実装しようとしたときに、どんな方法があるのかまとめる。
今回は「実装したい特定のアニメーションにとって良い方法とは」ではなく、Webのアニメーション全般に対する知見を深める目的。
このアニメーションどうしようかなと思ったときに見返せるように。
- 方法
- 参考リンク
- 特徴のコメント
- 実際の例
などを書く構成になっています。
パッとググって出てくるライブラリは網羅したはず。
ライブラリの細かい使い方についてはそれぞれ紹介している記事がいっぱいあるので、ここでは特徴をメモ程度に。
スター数や最終更新日は執筆時点のもので、こまめに更新する予定はありません。
目安程度で実際の値は実際にGitHubをご確認ください。パフォーマンスについてはスコープ外です。
アニメーションの調査方法について
既存サービス/サイトのアニメーションに何が使われているか調べる方法としてChrome devtoolsにAnimationという機能があります。
(Animationsが出ていない場合は左の3点メニューから選ぶ)これを開いた状態でページでアニメーションが発生すると、アニメーションを保存してくれます。
アニメーションが発生している要素はどれか判別したり、再生スピードを遅くして再生したりすることができます。
DOM Breakpoints
さらに、例えばTwitterのいいねの場合、アニメーションするハート要素がアニメーションが始まると同時に追加され、終わると同時に消えます。
そうすると上の画像のように要素が存在しないためセレクタの色が灰色になり押しても要素が見つけられません。そこでDOM Breakpointsが使えます。
DOM Breakpiontsはコードのブレークポイントと同じで、DOMの変化をステップ実行できる機能です。
Twitterのいいねの例で言えば、アニメーション時に追加される要素の親要素にブレークポイントを設定すると、アニメーション発生時にステップ実行することができます。
今回は子孫要素への変更なので、subtree modificationsを選びます。Animationsを開いた状態でDOM Breakpointsありで実行すれば、すぐに消えてしまう要素もじっくり観察することが出来ます。
Webのアニメーション実装の3分類
ここから本題。
- 動画ファイルを使う
- CSSを使う(画像使うことも)
- JSを使う(画像・CSSを使うことも)
上がより静的、下がより動的なイメージ。
基本はこれしかないものの、この中でも非常に多彩な方法が編み出されてる。方法
動画ファイルを使う編
素材が用意できれば一番簡単。
実際のDOM要素をアニメーションさせたい時には使えない。
- ちょろっとした部品っぽい要素
- 他の要素に依存しない要素
に向いてる。
GIFファイルを使う
ローディングインジケータ(ロード中のぐるぐる)とかはありがちですね。
例えばGitHubのローディングインジケータはGIF画像です。ほぼ対応してるので対応しているブラウザのことを考えなくて良いのは非常に大きなメリット。
WebPファイルを使う
WebPが次世代画像フォーマット天下統一! Safari 14でついにiPhoneもWebP対応・導入加速は間違いなし - アイデアマンズブログ
そろそろSafariも対応するらしいので、仕様の強さから言ってもう全部(実写系も色数少ない系もアニメーションも)WebPにしてくれ〜という感じ。
今はまだ後方互換を考えないといけないけど。画像の後方互換を考える時には、
picture
タグが使える。
<picture>: 画像要素 - HTML: HyperText Markup Language | MDN<!-- MDNのページよりコピペ --> <picture> <source srcset="logo.webp" type="image/webp"> <img src="logo.png" alt="logo"> </picture>
picture
タグはimg
要素のsrc
属性の値を条件に従って変更する要素。
source
のtype
にフォーマットを指定することで、それが対応しているブラウザの場合はそれが読まれる。
このpicture
タグはIE以外主要ブラウザで対応しているので、基本的にwebpを用意しない技術的な理由はないはず!APNGファイルを使う
Animated PNG graphics | MDN
Animated PNG - Can I use... Support tables for HTML5, CSS3, etcAnimated PNGの略。
GIFの次世代として開発された。IE以外は対応している。
透過が扱えたり扱える色数が多かったり。APNGでイラストやアイコンを動かそう!~AdobeAnimateの使い方~ – 東京のホームページ制作 / WEB制作会社 BRISK
WebPとAPNGの歴史について:アニメーション画像の歴史 APNG-WebP戦争 | 味わい尽くせ!
面白いですね、WebPも全ブラウザで対応になった今、APNGはこれからは先細りかなーWebMファイルを使う
HTML の音声と動画のメディア形式 - HTML: HyperText Markup Language | MDN
WebM - Can I use... Support tables for HTML5, CSS3, etc動画コーデックとしてVP8・VP9、音声コーデックとしてVorbis・Opusが使用できるコンテナ。
動画では珍しく背景透過に対応しているが、Safariが対応していない。
あまり話題に上がらない気がする。MP4ファイルを使う
お馴染みの動画コンテナ。背景透過はできない。
video
タグのplaysinline
属性とmuted
属性を使うことでいわゆる動画プレイヤーではなくGIFのようにインラインで再生できるが、例えばiPhoneのコントロールセンター(上とか下とかからスワイプで出てくるメニュー)はWebページ内でMP4が再生されているとシーカーが表示してしまうことに注意。<video>: 動画埋め込み要素 - HTML: HyperText Markup Language | MDN
CSSを使う編
animation
やtransition
プロパティのみを使うCSSではプリミティブで非常にベーシックな方法。
パッと思いつく軽量なアニメーション(単純に要素を移動させるなど、いくつかのプロパティの値を一定規則で変化させれば表現できるもの)はこれだけでも実現可能。SVGをCSSでアニメーションさせる
SVGにも、
animation
やtransition
が使える。SVGをSMILでアニメーションさせる
マークアップのみで完結するので手軽。
ただし一度Chromeで非推奨になりかけたりしているようなので、あえて使うメリットはそこまで感じられない。
【SMIL】SVGアニメーションでsvgのキャラ画像を動かそう! | webico blog
- 参考
CSSスプライト +
steps
CSSスプライト
- 細かい画像を一つの画像ファイルに結合して、表示位置と表示領域を変えることでそれぞれの画像を表示する手法。HTTPリクエストが1回で済むのでオーバーヘッドが少なく速度などパフォーマンス改善のために行われる。
steps
CSSにおける
steps
とはタイミングファンクションの一種。
- <timing-function> - CSS: カスケーディングスタイルシート | MDN
- タイミングファンクションとは、プロパティに指定する値を一定の規則に従って変化させることができるもの。
steps
はその中でも、指定した値ごとに階段状に変化していく関数。この二つを組み合わせると、画像とCSSだけでパラパラ漫画の仕組みでアニメーションを実装できる。
例えばTwitterのいいねのアニメーションはこの方法を使用している。
Twitterのハートマーク機能こと「いいね」のエフェクトがどうなっているのか調べてみた現在のいいねの実装はこちらの記事ともまた少し変わっていて、
https://abs.twimg.com/responsive-web/web/heart_animation.5c9f8e84.pngパーティクルのアニメーションはCSSスプライト +
steps
を使い、赤いハート部分はSVGをCSSでアニメーションさせる混合技になっていました。
- 参考
- スプライトアニメーションを使った最近のウェブデザイントレンド | その他 | つみきブログ
- リンク切れ色々あるけど、 https://demodern.com/ こちらのサイトのスプライトは理解しやすい。
CSSライブラリ
ここからはCSSライブラリ。
任意の要素にクラスを付与するだけでアニメーションを実装できる。
もちろん、動的にアニメーションの再生を制御したい場合は、任意のタイミングでのjsによるクラスの追加/削除の処理を書く必要はある。Animate.css
Animate.css
animate.css/ at master · animate-css/animate.css
スター数 67.4k 最終更新日 8日前 パワポのアニメーションみたいなのを簡単に実装できる。
知名度的に言っても、まずは自分が実装したいアニメーションがこれにあるかどうか確認すると良さそう。
公式サイトで動きを確認できる。HoverCSS
Hover.css - A collection of CSS3 powered hover effects
スター数 23k 最終更新日 5日前 マウスをホバーさせた時のアニメーションを簡単に実装できる。
主にクリッカブルなボタンなどに使う想定。
基本的なホバー系のアニメーションが実装したかったらまずはこれを確認。
公式サイトで動きを確認できる。imagehover.css
Imagehover.css - A Pure CSS Image Hover Effect Library | imagehover.css
ciar4n/imagehover.css: Pure CSS Image Hover Effect Library
スター数 1.5k 最終更新日 1年前 こちらもマウスホバー時のアニメーションだが、画像に関するアニメーション特化。
フリーで使用できるエフェクトに加えて、有料でさらに購入することもできる。
公式サイトで動きを確認できる。Magic
Magic Animations CSS3
miniMAC/magic: CSS3 Animations with special effects
スター数 6.6k 最終更新日 8日前 ちょっと見たことないようなin/out系のアニメーションが豊富。
変わった立体的なアニメーションなどもあるので、公式サイトで確認しよう。CSShake
CSShake
elrumordelaluz/csshake: CSS classes to move your DOM!
スター数 4.2k 最終更新日 5日前 震える系アニメーション。
公式サイトを見ていると思わず笑ってしまう、めっちゃ震えている。cssanimation.io
CSS Animation Library for Developers and Ninjas - cssanimation.io
yesiamrocks/cssanimation.io: CSS Animation Library for Developers and Ninjas
スター数 152 最終更新日 7ヶ月前 かなり変わったアニメーションがかなりたくさん含まれている。
使いこなすのは難しいが、ああそれね、みたいなありきたりなアニメーションに飽きた方は是非見て欲しい。Tuesday
https://shakrmedia.github.io/tuesday/
shakrmedia / tuesday:Shakrによる風変わりなCSSアニメーションライブラリ
スター数 434 最終更新日 1ヶ月前 quirky な(奇妙な、風変わりな)CSSライブラリ。in/out系が多い。
quirky と言いつつ割とよく見るアニメーションが含まれていて、特徴としてはアニメーションの持続時間が短いこと。
既存のアニメーションのCSSライブラリが持続時間が長いことに違和感を覚え作成されたそう。Wicked CSS
Wicked CSS3 Animations
kristofferandreasen/wickedCSS: A library for CSS3 animations. The animations are more vibrant than most simple animations.
スター数 263 最終更新日 5年前 こちらも同様に基本的なアニメーションが含まれている。
公式サイトの動くキノコがつぶらな瞳をしている。Hamburgers
Hamburgers by Jonathan Suh
jonsuh/hamburgers: Tasty CSS-animated Hamburgers
スター数 5.9k 最終更新日 2年前 ハンバーガーメニューのアイコンをタップした時のアニメーション。
ハンバーガーメニューでアニメーションしたければとりあえず覗いて見ると良さそう。JSを使う編
以降はライブラリのリスト。
Web Animations API
Web Animations API を利用する - Web API | MDN
Animation API - Can I use... Support tables for HTML5, CSS3, etc今までCSSの
animation
で実装していたものをjsで実装できるようにする仕様。
だいたいのブラウザで対応済み。
CSSでのアニメーションと違って、タイミングや終了イベントなど細かい制御がjsで可能になる。
標準が好きな方はまずこちら。polyfillもあるので一応紹介。
Web Animations Demos
web-animations/web-animations-js at 866e01726cf61ffeabad2baa7f6c10e3cadee138
スター数 3.4k 最終更新日 3年前 jQuery依存 なし canvas 非使用 汎用系
Anime.js
anime.js • JavaScript animation engine
juliangarnier/anime: JavaScript animation engine
スター数 36.1k 最終更新日 4ヶ月前 jQuery依存 なし canvas 非使用 CSS、要素、SVGなどをいい感じに変化させてアニメーションを実装できる。
軽量で、シンプルなAPIを持つ。
とりあえず凝ったアニメーション作りたくなったら使いたい。GreenSock GSAP(Tweenmax)
GSAP - GreenSock
greensock/GSAP: GreenSock's GSAP JavaScript animation library (including Draggable).
スター数 11.1k 最終更新日 3日前 jQuery依存 なし canvas 非使用 ライセンス not MIT 数年前のアニメーションライブラリのデファクトスタンダード的存在。
CSS Versus JavaScript Animations | Web FundamentalsAlternatively, if you're already using a JavaScript framework that includes animation functionality, such as via jQuery's .animate() method or GreenSock's TweenMax, then you may find it more convenient overall to stick with that for your animations.
Googleのドキュメントでも言及されていることからデファクト感が伺える。
ただし、ライセンスがMITではないことに注意。
ライセンス-GreenSock
基本は無料だが、サイト上で何らかの有料課金が存在する場合は優良ライセンスが必要。Kute.js
KUTE.js | JavaScript Animation Engine
スター数 2k 最終更新日 18日前 jQuery依存 なし canvas 非使用 汎用的で高機能なアニメーションライブラリとしてのポジション。
スター数の少なさが少し気にかかるか。
2,3年前に話題に上がって以来あまり言及が見当たらない。Lottie
Lottie
airbnb/lottie-web: Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/
スター数 21.4k 最終更新日 2日前 jQuery依存 なし canvas 非使用 AirBnB製で、After EffectsのアニメーションをサクッとWeb上に実装できる。
マルチプラットフォームなので、Web以外でも使える。CreateJS
CreateJS | A suite of JavaScript libraries and tools designed for working with HTML5
CreateJS
スター数 合計で17kくらい 最終更新日 6日前 jQuery依存 なし canvas 使用 4つの大きなライブラリからなり、canvas描画を中心とした処理をサポートする。
かなりしっかりとしたライブラリで、canvasで何かしら凝ったことをやりたいのであれば必見。
日本語の情報がかなり多い。Tween.js
tweenjs/tween.js: Javascript tweening engine
スター数 7.5k 最終更新日 2ヶ月前 jQuery依存 なし canvas 非使用 簡単なアニメーション用の軽量なライブラリ。
Understanding Tween.js - Michael Casebolt
基本的にやっていることはタイミングファンクション(easingとか)に沿ってjsのオブジェクトの値を変化させること。
直接アニメーションに使わなくても、ゲームの時間に伴う値調節とかに使えたりするんかなーAnimate Plus
bendc/animateplus: A+ animation module for the modern web
スター数 5.5k 最終更新日 2年前 jQuery依存 なし canvas 非使用 安定した60 FPSを実現し、重量が3 KB(縮小および圧縮)未満であることを目的としているため、特にモバイルに適しています。
DOMアニメーションに加えてSVGもいけるようだ。アニメーションの型が決まっている系
Bounce.js
Bounce.js
tictail / bounce.js:CSS3を使った美しいアニメーションをすぐに作成できます。
スター数 6.1k 最終更新日 6年前 jQuery依存 なし canvas 非使用 バウンドするようなアニメーションを簡単に作成できるツール、及びライブラリ。
公式サイトではGUIで任意のアニメーションを作成し、Export CSSをクリックすることでそのアニメーションの単体のCSSコードが生成できる。AniJS
AniJS, A Library to Raise your Web Design without Coding
anijs/anijs: A Library to Raise your Web Design without Coding.
スター数 3.6k 最終更新日 5年前 jQuery依存 なし canvas 非使用 要素の
data-anijs
属性で以下のような指定をすることでjsのコードなしである程度の動的なアニメーション制御ができる。<div data-anijs="if: click, do: flipInY animated, to: .container-box></div>扱えるアニメーションはAnimate.cssと同じもの。
Animate.cssをサクッと動的に制御したい場合は使うと良いかも。Swiper
Swiper - The Most Modern Mobile Touch Slider
nolimits4web/swiper: Most modern mobile touch slider with hardware accelerated transitions
スター数 24.3k 最終更新日 6日前 jQuery依存 なし canvas 非使用 スワイプ動作(PCの場合はドラッグ)におけるカルーセルの切り替わりアニメーションを実装できる。
実際にスマホで公式サイトを見てみると、スワイプに対してネイティブばりのヌルヌル感でアニメーションする様が体験できる。wagerfield/parallax.js
http://matthew.wagerfield.com/parallax/
wagerfield/parallax: Parallax Engine that reacts to the orientation of a smart device
スター数 14.8k 最終更新日 13ヶ月前 jQuery依存 なし canvas 非使用 端末のジャイロの値に合わせて動くアニメーションを実装できる。
っぽいのだが、自分のiPhoneXでは動かなかった...
更新が数年前に止まっているので、最新のデバイスで動くかどうかは要注意。pixelcog/parallax.js
Parallax.js | Simple Parallax Scrolling Effect with jQuery
pixelcog/parallax.js: Simple parallax scrolling effect inspired by Spotify.com implemented as a jQuery plugin
スター数 3.3k 最終更新日 2年前 jQuery依存 あり canvas 非使用 非常にわかりにくいんですがparallax.jsという名前のライブラリ2つ目。
どちらかというと日本語で言うパララックスに近いのはこっちだと思う。
スクロールしたときにスクロールの速度とは別の速度で動く要素があるタイプのデザインを実装できる。Slick
slick - the last carousel you'll ever need
kenwheeler/slick: the last carousel you'll ever need
スター数 25.8k 最終更新日 8ヶ月前 jQuery依存 あり canvas 非使用 カルーセルをいい感じに実装できるライブラリ。
公式サイトの例はいい感じ。jQuery...Popmotion
Popmotion | JavaScript animation libraries for delightful interfaces
Popmotion/popmotion: Simple animation libraries for delightful user interfaces
スター数 17.7k 最終更新日 3ヶ月前 jQuery依存 なし canvas 非使用 いくつかのライブラリの総称でもあるし、その中に単体のPopmotionというライブラリも含まれている。
Popmotion Pure
popmotion/packages/popmotion at master · Popmotion/popmotion
Popmotion Pure | A functional, flexible JavaScript animation libraryここに列挙されているようなアニメーションは自分で作ろうとすると超ツラいと思う。
慣性っぽい動きを実装したい場合は覗くと良さそう。他にもあるけど、総じて記述が少ないw
Typed.js
JavaScript Animated Typing with Typed.js | by Matt Boldt
mattboldt/typed.js: A JavaScript Typing Animation Library
スター数 9.8k 最終更新日 10ヶ月前 jQuery依存 なし canvas 非使用 タイピングしているようなアニメーションを実装できる。
Animsition
Animsition
blivesta/animsition: A simple and easy jQuery plugin for CSS animated page transitions.
スター数 3.8k 最終更新日 3年前 jQuery依存 あり canvas 非使用 ページ遷移の前後にアニメーションを入れることができる。
turn.js
Turn.js: The page flip effect in HTML5
blasten/turn.js: The page flip effect for HTML5
スター数 6.2k 最終更新日 7年前 jQuery依存 あり canvas 非使用 ライセンス not MIT 本のページをめくっているかのようなアニメーションを実装できる。
ライセンスがMITではないことに注意。商用利用は不可。
https://github.com/blasten/turn.js/blob/master/license.txt
v4は別のライセンスのようだが何になるのか見つけられなかった。スクロール系
AOS
AOS - Animate on scroll library
michalsnik/aos at v2
スター数 13.3k 最終更新日 2年前 jQuery依存 なし canvas 非使用 スクロールに応じて発火するアニメーションを簡単に実装できる。
公式サイトをスクロールするとすぐ分かる。
v1とv2で破壊的変更が入っているようなので注意。wow.js
wow.js — Reveal Animations When Scrolling
graingert/WOW: Reveal CSS animation as you scroll down a pageこれもスクロール時に発火するアニメーションですが、GitHub上には「意地的な非推奨、AOSを推奨」と書かれている。
一時的な非推奨...
よく見るとこのレポジトリforkされたもので、元のレポジトリはwow.jsを有料ソフトウェアとして販売している。
どうも過去のQ&Aサイトとかの会話を見るに、MITライセンスだったレポジトリを途中で有償化したらしい。
それに反発した人がMITライセンスでforkして公開したという流れのようだ。
AOS推奨となっているし、現時点でこれを使う理由は無いだろう。ScrollMagic
ScrollMagic ♥ Demo
janpaepke/ScrollMagic: The javascript library for magical scroll interactions.
スター数 12.4k 最終更新日 8ヶ月前 jQuery依存 なし canvas 非使用 ライセンス MIT or GPL スクロールに同期した任意のアニメーションを実装できる。
ライセンスはMIT or GPLでMITがあるので問題なし。実際のアニメーションの実装にGSAPを推奨しているが、GSAPはライセンスがMITではないことに注意。
Velocity.jsにも対応している他、他ライブラリを使わない方法(jsによるクラスの付け替え)でもOK。ScrollReveal
ScrollReveal
jlmakes/scrollreveal: Animate elements as they scroll into view.
スター数 18.9k 最終更新日 6日前 jQuery依存 なし canvas 非使用 ライセンス not MIT viewportに入った/出た際のアニメーションを簡単に実装できる。
ただし、ライセンスがMITではないことに注意。
OSSプロジェクトではGPLライセンスだが、商用の場合はコマーシャルライセンスが必要。ScrollTrigger
ScrollTrigger - Let your page react to scroll changes
terwanerik/ScrollTrigger: Let your page react to scroll changes.
スター数 3.3k 最終更新日 7ヶ月 jQuery依存 なし canvas 非使用 スクロールに合わせて発火する処理を簡単に制御できる。
これ自体はアニメーションではないが、基本的にスクロールに合わせて発火する処理はアニメーションがほとんどだと思うので紹介。
これとその他のアニメーションライブラリを合わせると良さそう。DynCSS
DynCSS - Simple dynamic CSS rules to give life to your sites.
vzaccaria/DynCSS: Dynamic CSS Rules
スター数 483 最終更新日 6年前 jQuery依存 あり canvas 非使用 スクロールやウィンドウのリサイズイベントに合わせたアニメーションを実装できる。
CSSに独自のプロパティを書くとjsがそれを解釈してなんやかやしてくれる、というなかなかにトリッキーな仕組みのよう。jQuery依存な汎用系
Velocity.js
Velocity.js
julianshapiro/velocity: Accelerated JavaScript animation.
スター数 16.7k 最終更新日 3ヶ月前 jQuery依存 あり canvas 非使用 jQuery依存のアニメーションライブラリとして優秀な様子。
https://qiita.com/kyota/items/754e0e6cb7a144eda850Transit.js
Transit - CSS transitions and transformations for jQuery
rstacruz/jquery.transit: Super-smooth CSS3 transformations and transitions for jQuery
スター数 7.4k 最終更新日 5年前 jQuery依存 あり canvas 非使用 jQuery依存のアニメーションライブラリだが、Velocity.jsの方が軽快に動くという評判が多い。
SVG系
Snap.svg
Snap.svg - Home
adobe-webplatform/Snap.svg: The JavaScript library for modern SVG graphics.
スター数 12.8k 最終更新日 3年前 jQuery依存 なし canvas 非使用 Adobe製のSVGアニメーションライブラリ。
DOMのjQuery、SVGのSnap、的な立ち位置を目指していた様子。
SVGを細かく動かしたいなら使ってみると良さそう。DrawSVG
jQuery DrawSVG
lcdsantos/jquery-drawsvg: Lightweight, simple to use jQuery plugin to animate SVG paths
スター数 752 最終更新日 4年前 jQuery依存 あり canvas 非使用 jQueryプラグインとしてのSVGアニメーションの実装ライブラリ。
Vivus
vivus.js - svg animation
maxwellito/vivus: JavaScript library to make drawing animation on SVG
スター数 13.1k 最終更新日 11ヶ月前 jQuery依存 なし canvas 非使用 SVGをアニメーションさせるライブラリ。
jQueryじゃないし比較的最近更新されてるしスター数もそれなりに多い。
SVG動かしたかったらmo.jsかこれかなあ。Mo.js
mo.js
mojs/mojs: The motion graphics toolbelt for the web
スター数 15.9k 最終更新日 3ヶ月前 jQuery依存 なし canvas 非使用 非常に滑らかなアニメーションをSVGで実現する。
Animocons: Animated Icons with mo.js | Codrops
リンク切れ起こしてる部分もあるので古いですが、このmo.jsを使用したサンプルはめっちゃ気持ち良いです。
日本語の情報も多そう。Two.js
Two.js
jonobr1/two.js: A renderer agnostic two-dimensional drawing api for the web.
スター数 6.7k 最終更新日 1日前 jQuery依存 なし canvas 非使用 フラットモーショングラフィックスの作成を想定したライブラリ。
SVGもcanvasもWebGLも同じAPI描画できるようだ。
どうやらAppleがApple Watchのページで使っていたらしい。bonsai.js
BonsaiJS - A Graphics Library
uxebu/bonsai: BonsaiJS is a graphics library and renderer
スター数 2k 最終更新日 6年前 jQuery依存 なし canvas 非使用 SVGの他、音声やフォントなども使えるよう。
公式サイトのサンプルが動いてないのは気になる。
古めなので動作するかどうかは要確認。Raphaël.js
Raphaël—JavaScript Library
DmitryBaranovskiy/raphael: JavaScript Vector Library
スター数 10.7k 最終更新日 1ヶ月前 jQuery依存 なし canvas 非使用 SVGと、VMLに対応したライブラリ。
クロスブラウザを意識していて、IEでも動くようだ。
VMLはSVGの前身。
Vector Markup Language - Wikipediaflubber
veltman/flubber: Tools for smoother shape animations.
スター数 5.3k 最終更新日 9ヶ月前 jQuery依存 なし canvas 非使用 SVGで特定の形から特定の形へとスムーズに変化させるアニメーションを実装できる。
SVG Morpheus
SVG Morpheus - Morph SVG icons
alexk111/SVG-Morpheus: JavaScript library enabling SVG icons to morph from one to the other. It implements Material Design's Delightful Details transitions. (THIS PROJECT IS NOT MAINTAINED ANYMORE)
スター数 2.6k 最終更新日 3年前 jQuery依存 なし canvas 非使用 SVGの二つのアイコンをにゅっとしたアニメーションで変更するアニメーションを実装できる。
THIS PROJECT IS NOT MAINTAINED ANYMORE
とのこと。Walkway
Connor Atherton | Full stack developer
ConnorAtherton/walkway: An easy way to animate SVG elements.
スター数 4.3k 最終更新日 2年前 jQuery依存 なし canvas 非使用 https://www.polygon.com/a/ps4-review/
このサイトのアニメーションを再現するために作られた。
SVG画像をアニメーションさせる。
公式サイトにはGUIツールも存在。Lazy Line Painter
Lazy Line Painter
camoconnell/lazy-line-painter: Lazy Line Painter - A Modern JS library for SVG path animation
スター数 1.8k 最終更新日 2年前 jQuery依存 なし canvas 非使用 SVGのパスアニメーションを実装できる。
letterbolt
buseca.github.io/letterbolt/
buseca/letterbolt
スター数 29 最終更新日 6年前 jQuery依存 なし canvas 非使用 アニメーションするフォントをSVGで描画できる。
zPath.js
ZetCoby/zPath.js: A jquery plugin that will animate/draw SVG
スター数 29 最終更新日 5年前 jQuery依存 あり canvas 非使用 Yallow
Yarrow — svg animated arrow pointer and tooltip
krispo/yarrow: svg animated arrow pointer and tooltip
スター数 35 最終更新日 4年前 jQuery依存 なし canvas 非使用 矢印のアニメーションをSVGで実装できる。
公式サイトのサンプルが動いていないので動作は要確認。loading...
Loading...
jxnblk/loading: This could take a while
スター数 3.6k 最終更新日 5年前 jQuery依存 なし canvas 非使用 ネタレポジトリのようだけど、スター数が多い。
公式サイトが動いていなくて残念...見てみたかった。WebGL
WebGL API直接
WebGL: Web の 2D および 3D グラフィックス - Web API | MDN
WebGL チュートリアル - Web API | MDNライブラリを使わずにそのまま書く。
時間が無限にあったら書きたい。Three.js
three.js – JavaScript 3D library
mrdoob/three.js: JavaScript 3D library.
スター数 62.2k 最終更新日 5時間前 jQuery依存 なし canvas 使用 WebGLを使い、3Dを描画するためのライブラリ。
公式サイトの使用例だけで1日つぶせそう。めちゃくちゃ個人的な話だが、アプリでめっちゃプレイしていたクロッシーロードが公式サイトの中の例に並んでいて、Webでクロッシーロードあったんか!という感動でこれを書きながら数分プレイしてしまった。
Three.jsありがとう。Webで3Dをやろうと思ったらとりあえずこれを使えば良さそう。
GrimoireJS
Grimoire.js - WebGL framework for Web development -
GrimoireGL/GrimoireJS: A WebGL framework for Web development.
スター数 333 最終更新日 3年前 jQuery依存 なし canvas 使用 DOMとDOM APIっぽい書き方(独自のタグでマークアップするなど)でWebGLを実装できる。
どうでもいいけど、アイコンがbrowserifyを思い出すよ。
公式のサンプルが動いてないReact
React Transition Group
React Transition Group
reactjs/react-transition-group: An easy way to perform animations when a React component enters or leaves the DOM
スター数 7.4k 最終更新日 6日前 jQuery依存 なし canvas 使用 React公式のアニメーションライブラリ。
react-motion
chenglou/react-motion: A spring that solves your animation problems.
スター数 18.6k 最終更新日 8ヶ月前 jQuery依存 なし canvas 使用 RenderPropsな書き方で、Reactコンポーネント内の値を変化させる。
イメージ的にはTween.jsのReact版という感じ。react-spring
react-spring
react-spring/react-spring: ✌️ A spring physics based React animation library
スター数 17.6k 最終更新日 3ヶ月前 jQuery依存 なし canvas 使用 spring-physics based animation
を提供するReact用アニメーションライブラリ。
従来のタイミングファンクション系の動きとは考え方が違うらしい。
react-motionやanimatedの良いとこどりをしているそう。Animated
animatedjs/animated: Declarative Animations Library for React and React Native
スター数 1.7k 最終更新日 2年前 jQuery依存 なし canvas 使用 framer/motion
Framer Motion
framer/motion: Open source, production-ready animation and gesture library for React
スター数 6.1k 最終更新日 8時間前 jQuery依存 なし canvas 非使用 React環境下において、アニメーションを宣言的に実装できる。
Popmotion系列。
宣言的にアニメーションを定義できるので、例えばstateがtrueからfalseに変わったときに要素の位置が変わる、みたいなコンポーネントでこれを使うとReactと親和性高くアニメーションを実装できる。
これ個人的に、公式サイトのファーストビューの例のアニメーションが非常に秀逸だと思う、パッと見でそのstateの変更とアニメーションっていう繋がりがわかりやすい。
日本語の情報は少なそうだけど、Reactで書くときにアニメーション欲しくなったらまず使ってみたい。Pose
Pose | A truly simple animation library for React, React Native, and Vue
popmotion/packages/popmotion-pose at master · Popmotion/popmotionPopmotion系列のReact用アニメーションライブラリ。
実装のサンプルコードを見るとstyled-componentsっぽさを感じる。velocity-react
google-fabric/velocity-react: React components for Velocity.js
Fabric製のVelocity.jsをReactで使うためのライブラリ。
その他ツール
アニメーションジェネレータ
- CSS3 Keyframes Animation Generator
- Animista
- WAIT! Animate
- Shape Shifter
- アイコンアニメーションの作成
- アイコンアニメーションテクニックの概要| Androidデザインパターン
その他
- Ceaser - CSS Easing Animation Tool - Matthew Lein
- タイミングファンクションのベジェ曲線をGUIで作成できる。
- cubic-bezier(.26,.5,.75,.28) ✿ cubic-bezier.com
- 上に同じ。
- CSS triangle generator
- CSSによる三角形を簡単に作れる。
- Primitive for macOS
- 既存の画像をプリミティブな図形の集合に変換する。
- Lazy Line Composer
コピペで出来る系
- コピペで使うマウスオーバー時のhover cssエフェクト28選 | SONICMOOV LAB
- Web最新テクニックはこれ!コピペで実装できるすごいHTML/CSSスニペット68個まとめ - PhotoshopVIP
- コピペで実装できる!最新WebテクHTML/CSSコードスニペット66個まとめ - PhotoshopVIP
- アニメーションが気持ちいい!コピペで実装できる最新HTML/CSSスニペットまとめ - PhotoshopVIP
- すごすぎるポートフォリオ...コピペで実装できるCSSデザイン! 【 アニメーション 】 - デシノン
- CSSアニメーションで実現! コピペで使えるマイクロインタラクション - ICS MEDIA
その他の参考
ありがとうございます?
- 投稿日:2020-07-22T23:43:52+09:00
1日の仕事の振り返りで使えるコーチング用LINE BOTを作ろうとした
はじめに
自社のWEBサイトのQ&Aで、LINE BOT作れたらなと考え、
とりあえず何か簡単なLINE BOTを作ってみようと思いました。普段、人材開発の仕事に携わっているので、
自分の1日の振り返り用のLINE BOTだったら実用的かなと、
作ることにチャレンジしました。コーチング用LINE BOTの意図
振り返りで自動化したいことは、
・月間目標のリマインド
・月間の残り日数
・振り返りの質問
・PDCAを回す時の視点
です。そのあたりを自動化しつつ、
明日の行動をイメージできるように、
明日の天気も自動で取得できるようにしてみました。結果できたLINE BOT
天気apiを使うまでのLINE BOTは作れた。
"use strict"; const express = require("express"); const line = require("@line/bot-sdk"); const axios = require('axios'); const PORT = process.env.PORT || 3000; const config = { channelSecret: "channelSecret", channelAccessToken: "channelAccessToken", }; const app = express(); app.get("/", (req, res) => res.send("Hello LINE BOT!(GET)")); app.post("/webhook", line.middleware(config), (req, res) => { console.log(req.body.events); if ( req.body.events[0].replyToken === "00000000000000000000000000000000" && req.body.events[1].replyToken === "ffffffffffffffffffffffffffffffff" ) { res.send("Hello LINE BOT!(POST)"); console.log("疎通確認用"); return; } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result) ); }); const client = new line.Client(config); async function handleEvent(event) { if (event.type !== "message" || event.message.type !== "text") { return Promise.resolve(null); } const message = event.message.text; let replyMessage = event.message.text; if (message == "完了") { replyMessage = "お疲れ様!1日振り返りましょう!"; } if (message == "はい") { replyMessage = "今月の目標は、Qiitaの記事を3つ書くことです。現状は?"; } let date = new Date(); let year = date.getFullYear(); let month = date.getMonth() + 1; let day = date.getDate(); let eom = new Date( year, month, 0 ).getDate(); let number = eom - day ; if (message.indexOf("現状") > -1) { replyMessage = `今月は残り${number}日です。順調ですか?`; } if (message == "いいえ") { replyMessage = "いますぐリスケしましょう。"; } if (message == "やばい") { replyMessage = "6つの変数<質・量・納期・方法・代替・金>どこ変えますか?"; } if (message.indexOf("目標") > -1) { replyMessage = "今月は「WEBサイトに新サービスを追加」です"; } if (message == "順調") { const randomMessages = [ "さすが!間違いなく成長してます!", "素晴らしい!!いうことなし!", "成功間違いなし!明日も頑張りましょう!", ]; replyMessage = randomMessages[Math.floor(Math.random() * 3)]; } if (message == "ヒント") { const randomMessages = [ "Tips:タスクじゃなく得たい結果を意識しよう", "Tips:タスクを15分単位に分解してみよう", "Tips:移動時間でもできるタスクを見つけよう", ]; replyMessage = randomMessages[Math.floor(Math.random() * 3)]; } if (message == "明日もよろしく") { const randomMessages = [ "こちらこそ!!", "明日もがんばって!", "たのしんで!!", ]; replyMessage = randomMessages[Math.floor(Math.random() * 3)]; } return client.replyMessage(event.replyToken, { type: 'text', text: mes }); } app.listen(PORT); console.log(`Server running at ${PORT}`);最後に天気apiで明日の天気を取得し、明日の行動スケジュールを立てやすくしようとしたが、うまくいかなかった。
上のソースコードに、挿入したのがこちら。
if(event.message.text == '天気教えて') { let replyText = ''; replyText = '考え中'; await client.replyMessage(event.replyToken, { type: 'text', text: replyText }); } const CITY_ID = `400040`; const URL = `http://weather.livedoor.com/forecast/webservice/json/v1?city=${CITY_ID}`; const res = await axios.get(URL); const pushText = res.data.description.text; return client.pushMessage(event.source.userId, { type: 'text', text: pushText, }); } return client.replyMessage(event.replyToken, { type: 'text', text: mes }); }やはりプログラム1つ1つの意味が解読できないと、どこをどう変えればいいかわからない。
まずはJavascriptのプログラムの勉強からはじめる必要がありそう。
工夫したこと
・目標達成に向けて順調に進んでいたら、褒める、労う。
・目標達成ノウハウで、たまに思い出したいものを、いつでも引き出せるようにする
・今月の残り日数を自動計算することで、目標達成の具体的なスケジュールを意識できるようにした今後の改善事項
・まずはapiを使ったプログラムを完成させる。
・状況に合わせて、効果的な問いが自動で出てくるようにしたい
・もっとリマインドしたい目標達成ノウハウを詰め込みたい
・計画をリスケしたら自動でgoogleカレンダーに入力されるようにしたい今後、改善して、より実用的なものに繋げていこうと思います。
まとめ
LINE BOTの勉強をはじめて3日でやってみましたが、
思いの外できた部分と、うまくいかない部分と味わった。これからも色々チャレンジしながら、学んでいけたらと思います。
- 投稿日:2020-07-22T23:06:36+09:00
Svelteが公式にTypeScriptに対応しました
そもそも Svelte って何?
そもそも Svelte って何?という事についてはこの記事では触れるつもりはないので、
知りたい方は下記に参考になる Qiita の記事をいくつか記載しておきますので、そちらを一読してください。ReactとVueを改善したSvelteというライブラリーについて
vueよりも満足度の高いjsフレームワーク、svelteについて
君はVue,Reactの次に来るSvelteを知っているか?Svelte が公式に TypeScript に対応しました
その Svelte が 2020/07/17 に要望の多かった TypeScript に対応しました!
以下に公式サイトに掲載されているTypeScript対応の概要をまとめて記載してみました。なお以下は概訳であり、オリジナルの文章は下記のリンクを参照してください。
公式サイト概訳
Svelte <3 TypeScript
以前から最も多く要望をもらっていた機能がついに実現しました、Svelte は正式に TypeScript をサポートします。
これにより、TypeScript と JavaScript のどちらを使用しているかに関わらず、より良い開発体験が得られると考えています。今すぐ試してみてください
新しい Svelte TypeScript プロジェクトは normal template を利用して
node scripts/setupTypeScript.js
を実行することで開始できます。npx degit sveltejs/template svelte-typescript-app cd svelte-typescript-app node scripts/setupTypeScript.jsVS Code ユーザーの方は、(新しい) official extension を使用していることを確認してください。
これは James Birtles による人気の高い拡張機能に取って代わるものです。
後半では、既存の Svelte プロジェクトで TypeScript を使用するための個々のステップについて詳しく説明します。Svelte で TypeScript をサポートするとはどういうことでしょうか?
Svelte で TypeScript に対応することはかなり前から可能でしたが、多くの異なるツールを組み合わせ、それぞれを個別に動作するようにしなければなりませんでした。
今日では、これらのツールのほぼすべてが Svelte の組織の下にあり、パイプライン全体に責任を持ち、共通の目標を持った人たちによってメンテナンスされています。
COVIDがパンデミックと宣言される一週間前、私は類似の開発エコシステムによる最高のSvelte用のツールとアイデアの統合を提案し、ファーストクラスの TypeScript サポートを得るための一連のステップを提供しました。
それ以来、多くの人たちが協力してくれて、そのためのコードを書いてくれました。Svelte が TypeScript をサポートするようになったということは、いくつかの異なる意味を持っています。
<script>
ブロックの中で TypeScript を使うことができます -lang="ts"
属性を追加するだけです。- TypeScript を持つコンポーネントは
svelte-check
コマンドでタイプチェックを行うことができます。- コンポーネントを書いているときに、マークアップ内の式でも自動補完のヒントや型チェックを得ることができます。
- TypeScript ファイルは Svelte コンポーネント API を理解しています -
.ts
モジュールに.svelte
ファイルをインポートしても、赤い四角い線はもうありません。どのような仕組みになっているのか?
TypeScript のサポートの 2 つの主要な部分を理解するために、TypeScript が開発ツールを提供するために使用している技術と比較してみましょう。
それはコマンドラインで実行し
*.ts
を*.js
に変換するtsc compiler
と、テキストエディタからのリクエストに応答するノードAPI であるTSServer
です。TSServer
は、コーディング中のエディタにJavaScriptとTypeScriptのリアルタイムイントロスペクションを提供するもので、その中にコンパイラのコードのほとんどが含まれています。一方 Svelte には、Svelte コンパイラと、Language Server Protocol standard を介してテキストエディタの呼び出しに応答する
svelte-language-server
があります。
ファーストクラスの TypeScript をサポートしているということは、これらの二つのシステムが TypeScript コードをうまく扱えることを意味しています。TypeScript のための Svelte コンパイラのサポートは、Christian Kaisermann の
svelte-preprocess
が担当しており、現在では公式の Svelte プロジェクトとなっています。エディタレベルについては、Pine 氏による Vueエコシステムの Vetur からインスピレーションを得ました。
Vetur は LSP、VS Code 拡張機能、CLI を提供します。Svelteも現在、LSP、VS Code拡張、CLIを提供しています。*.svelte イントロスペクション
公式のSvelte VS Codeエクステンションでは、James Birtles氏が
UnwrittenFun/svelte-vscode
とUnwrittenFun/svelte-language-server
で作成した基盤を基に構築しました。Simon Holthausen と Lyu, Wei-Da は、JavaScript と TypeScript のイントロスペクションを改善する素晴らしい仕事をしてくれました。
またコードベース内のコンポーネントのPropsを理解する力を強化する @halfnelson のsvelte2tsx
を統合しました。既存のプロジェクトにTypeScriptを追加する
スタートする前に、依存関係を追加します。
npm install --save-dev @tsconfig/svelte typescript svelte-preprocess svelte-check1. TypeScriptのコンパイル
まず最初に、
<script lang="ts">
ブロックの内容を TypeScript コンパイラに渡すsvelte-preprocess
を設定する必要があります。Rollupプロジェクトでは、次のようになります - Rollupが
.ts
ファイルを扱えるように@rollup/plugin-typescript
をインストールする必要があることに注意してください。+ import autoPreprocess from 'svelte-preprocess'; + import typescript from '@rollup/plugin-typescript'; export default { ..., plugins: [ svelte({ + preprocess: autoPreprocess() }), + typescript({ sourceMap: !production }) ] }TypeScriptを設定するには、プロジェクトのルートに
tsconfig.json
を作成する必要があります。{ "extends": "@tsconfig/svelte/tsconfig.json", "include": ["src/**/*", "src/node_modules"], "exclude": ["node_modules/*", "__sapper__/*", "public/*"], }
include/exclude
はあなたのプロジェクトによって異なります。ー これらは、ほとんどの Svelte プロジェクトで動作するデフォルト値です。2. エディタサポート
LSPを使用しているエディタであれば、どのようなエディタでも対応可能です。
VS Code 拡張機能は、私たちが最も力を入れてきたものです。
しかし、Atom上では作業が進行中で、coc-svelte
経由のVimは最新のLSPでアップデートされています。これらのエディタ拡張機能は、JavaScriptだけを使っていてもコーディング体験を向上させてくれます。
エディタはエラーを提供してくれませんが、推論やリファクタリングツールを提供してくれます。
JavaScript を使って<script>
タグの先頭に// @check-js
を追加すると、インフラの変更なしでより良いエラーメッセージを得ることができます。
<script>
を TypeScript を使うように切り替えるには、<script lang="ts">
を使ってください。願わくば、赤い四角い線の海を見ることがないことを願っています。3. CIチェック
赤い四角いマークがあるのは素晴らしいことですが、まあ、ちょっとしたことです。
しかし、長期的には、コードにエラーがないことを確認できるようにしたいものです。
プロジェクトにエラーがないことを確認するには、CLIツールのsvelte-check
を使うことができます。
これはエディタのように動作し、すべての.svelte
ファイルに対してエラーを確認します。依存関係をプロジェクトに追加し、CI に追加することができます。
❯ npx svelte-check Loading svelte-check in workspace: /Users/ortatherox/dev/svelte/example-app Getting Svelte diagnostics... ==================================== /Users/ortatherox/dev/svelte/example-app/src/App.svelte:3:2 Error: Type '123' is not assignable to type 'string'. (ts) ==================================== svelte-check found 1 error error Command failed with exit code 1.SapperプロジェクトのTypeScriptについてはどうなってますか?
このページを見ろ(注:SapperもSvelteも同様という意味)
どうすれば貢献できますか?
ご質問ありがとうございます。
作業はsveltejs/language-tools
リポジトリとSvelte Discord
の#language-tools
チャンネルで行われています。
問題を報告したり、修正を提出したり、新しいエディタのための拡張機能の開発を手伝ったりしたい場合は、こちらで作業を行っています。お会いしましょう!
- 投稿日:2020-07-22T23:00:43+09:00
簡単レシート印刷 receiptline で仮想プリンターを使ってみた
日本発のオープンソース receiptline でレシート印刷に少しずつトライしています。
先日落札したレシートプリンターは、セルフテストして投稿用に写真撮影しました。
IP アドレス設定中のため、前回利用した開発ツールを引き続き使います。
今回は仮想プリンターを使った印刷です。ファイルのロード
receiptline に添付されているサンプルデータを開発ツールで読み込んでみましょう。
フォルダアイコンをクリックすると、ダイアログボックスが開きます。
example/data/ja/receipt.txt
を選択して、OK ボタンをクリック。
キャンセルしたいときは、ダイアログボックスの外をクリックします。編集エリアにファイルがロードされました。
ReceiptLine{image:iVBORw0KGgoAAAANSUhEUgAAAIAAAAAwAQMAAADjOuD9AAAABlBMVEUAAAD///+l2Z/dAAAAZklEQVQoz2P4jwYYRrrABwYGOwYG5gMMDBUMDPxAgQcMDDJAgQYGhgJcAv//yMj//9/8//+HerAZRAsAzUASAJoGMhRF4AC6ANCIAhQz8AkAXQoUOIDidBQBkG8hAj8gAqPJAa8AAGjulhOsX97yAAAAAElFTkSuQmCC} 市ヶ谷駅前店 東京都千代田区九段1-Y-X 2019年 2月19日(火) 19:00 {border:line} ^領 収 証 {border:space} {width:*,2,10} ビール | 2| ¥1,300 千鳥コース | 2| ¥17,280 ------------------------------------- {width:*,20} ^合計 | ^¥18,580 現 金 | ¥20,000 お 釣 り | ¥1,420ちなみにセーブ機能はないです。コピペでファイルに保存するしかないようです。
テスト印刷
テスト印刷は、開発ツールを Node.js で実行している必要があります。
Web ブラウザーのアドレスがhttp://localhost:10080
であれば OK です。開発ツールの右上にはテスト印刷用のボタンがあります。
printer1
は印刷するレシートプリンターの ID です。プリンター ID
初期状態では以下のレシートプリンターが登録されています。
プリンターID 桁数 言語 用紙排出 出力データ printer1 42 英語 なし SVG printer2 48 日本語 なし SVG printer3 48 日本語 上方向 ⤴ ESC/POS tm_series1 42 英語 上方向 ⤴ ESC/POS (エプソン) mc_series1 48 日本語 前方向 ⤵ StarPRNT (スター) mc_series2 48 英語 前方向 ⤵ StarPRNT (スター) rp_series1 48 日本語 前方向 ⤵ ESC/POS (セイコー) ct_series1 48 日本語 前方向 ⤵ ESC/POS (シチズン) fp_series1 48 日本語 上方向 ⤴ ESC/POS (富士通) 送信先はすべて仮想プリンター (TCP ポート 19100) となっています。
printers.json
プリンター ID と設定情報は、
printers.json
に保存されています。
host と port は通信用。他は receiptline の変換 API に渡されます。
実機で印刷するときは、このファイルを変更する必要があります。printers.json{ "printer1": { "host": "127.0.0.1", "port": 19100, "cpl": 42, "encoding": "cp437", "gamma": 1.0, "upsideDown": false, "command": "svg" }, ... "fp_series1": { "host": "127.0.0.1", "port": 19100, "cpl": 48, "encoding": "cp932", "gamma": 1.8, "upsideDown": false, "command": "fit" } }仮想プリンター
送信ボタンをクリックすると、編集エリアのテキストが HTTP で Node.js へ送られて変換されます。
仮想プリンターが受信したデータは 16 進ダンプで表示されます。
printer2
へ送信。仮想プリンターが受信したデータは SVG です。Virtual printer running at 127.0.0.1:19100 Server running at http://127.0.0.1:10080/ Virtual printer received: 00000000 3c 73 76 67 20 77 69 64 74 68 3d 22 35 37 36 70 <svg width="576p 00000010 78 22 20 68 65 69 67 68 74 3d 22 33 36 30 70 78 x" height="360px 00000020 22 20 76 69 65 77 42 6f 78 3d 22 30 20 30 20 35 " viewBox="0 0 5 00000030 37 36 20 33 36 30 22 20 70 72 65 73 65 72 76 65 76 360" preserve 00000040 41 73 70 65 63 74 52 61 74 69 6f 3d 22 78 4d 69 AspectRatio="xMi 00000050 6e 59 4d 69 6e 20 6d 65 65 74 22 20 78 6d 6c 6e nYMin meet" xmln 00000060 73 3d 22 68 74 74 70 3a 2f 2f 77 77 77 2e 77 33 s="http://www.w3 00000070 2e 6f 72 67 2f 32 30 30 30 2f 73 76 67 22 20 78 .org/2000/svg" x ... 00000e70 2c 33 36 30 29 22 3e 3c 74 65 78 74 20 78 3d 22 ,360)"><text x=" 00000e80 30 2c 32 34 2c 33 36 2c 36 30 2c 37 32 22 3e e3 0,24,36,60,72">. 00000e90 81 8a 26 23 78 61 30 3b e9 87 a3 26 23 78 61 30 .. ...  00000ea0 3b e3 82 8a 3c 2f 74 65 78 74 3e 3c 74 65 78 74 ;...</text><text 00000eb0 20 78 3d 22 35 30 34 2c 35 31 36 2c 35 32 38 2c x="504,516,528, 00000ec0 35 34 30 2c 35 35 32 2c 35 36 34 22 3e c2 a5 31 540,552,564">..1 00000ed0 2c 34 32 30 3c 2f 74 65 78 74 3e 3c 2f 67 3e 3c ,420</text></g>< 00000ee0 2f 67 3e 3c 2f 73 76 67 3e 0a /g></svg>.
printer3
へ送信。仮想プリンターが受信したデータは ESC/POS です。Virtual printer running at 127.0.0.1:19100 Server running at http://127.0.0.1:10080/ Virtual printer received: 00000000 1b 40 1d 61 00 1b 4d 30 1c 28 41 02 00 30 00 1b .@.a..M0.(A..0.. 00000010 20 00 1c 53 00 00 1b 33 00 1b 7b 00 1b 2d 30 1c ..S...3..{..-0. 00000020 2d 30 1b 45 30 1d 42 30 1d 21 00 1d 4c 00 00 1d -0.E0.B0.!..L... 00000030 57 40 02 1b 61 01 1d 38 4c 0a 03 00 00 30 70 30 W@..a..8L....0p0 00000040 01 01 31 80 00 30 00 00 00 00 00 00 00 00 00 00 ..1..0.......... 00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ... 000007b0 52 08 5c 32 30 2c 30 30 30 0a 1b 2d 30 1c 2d 30 R.\20,000..-0.-0 000007c0 1b 45 30 1d 42 30 1d 21 00 1d 4c 00 00 1d 57 40 .E0.B0.!..L...W@ 000007d0 02 1b 61 00 1b 24 00 00 1b 5c 00 00 1b 2d 30 1c ..a..$...\...-0. 000007e0 2d 30 1b 45 30 1d 42 30 1d 21 00 1b 74 01 1c 43 -0.E0.B0.!..t..C 000007f0 31 1b 52 08 82 a8 20 92 de 20 82 e8 1b 24 50 01 1.R... .. ...$P. 00000800 1b 5c a8 00 1b 2d 30 1c 2d 30 1b 45 30 1d 42 30 .\...-0.-0.E0.B0 00000810 1d 21 00 1b 74 01 1c 43 31 1b 52 08 5c 31 2c 34 .!..t..C1.R.\1,4 00000820 32 30 0a 1d 56 42 00 1d 72 31 20..VB..r1コマンドを見ても、ちょっとわからないですよね・・・
次回は落札したレシートプリンターで印刷します!
- 投稿日:2020-07-22T22:39:09+09:00
Vuexでシステム日時をリアクティブに + Vuexプラグイン自作時の注意点
Vueを使っていて、画面上で日時を出したい場合や日時をもとに比較したい場合があったが、それに対してベストなプラグインが無かったので自作しました。
vuex-systime npm
vuex-systime github日付をシンプルにリアクティブにできない理由
JavaSriptでローカルの日時を取得する方法は以下のように実装します。
const currentDate = new Date() // 日付型を取得 const currentTime = Date.now() // エポックタイムからの経過ミリ秒を取得これをVueのcomputedに書いたとします
computed: { currentDate() { const d = new Date() return d } }ただ、これではリアクティブにはならないです。理由はここらへんとか見て欲しいですが、VueのリアクティブはVueで管理されているプロパティが変わったら、依存しているプロパティを再計算しに行く形で実装しています。そこでこの日付はnew Date()を実行すれば変わるのですが、実行するまでは変わらないので変わったことを検知されないので、初期化時に計算された値がキャッシュされ、そのまま保持されます。
日付をリアクティブにするやり方
よくあるやり方はsetInterva()で都度書き換えて上げれば良いです。
data: { currentDate:null }, mounted() { setInterval(() => { this.currentDate = new Date() }, 1000) }1秒毎にcurrentDateが変わります。そのためそれをもとに画面描画しているとその値が常に再描画されていきます。
vuex-nowというライブラリがあり、vuexのプラグインとして設定しておくと良い感じに1秒毎に描画してくれます。自作した理由
vuex-nowでだいたい事足りそうなのですが、以下の理由から自作しました。
- 1秒毎の書き換えだと書き換わりすぎる(1分毎とかが良い)
- 現在時刻と比較して、何かするといのがあるが、現在時刻との比較する量が多く性能が懸念された
- 1分毎なんだけど、1分のインターバルではなく、00秒で切り替えたい
- サーバの時間に基づいていて欲しい。サーバの時間とクライアントの時間がずれていた場合にはその差分を考慮して計算して欲しい。
- vuexのプラグインを作ってみたかった
1つ目の理由は、秒を切り捨てたプロパティを別途保持しておき、そいつと比較すれば良さそう。
2つ目の理由も、vuex-nowを使って、それとサーバ時間との差分を計算した別のプロパティを持てば良さそう。
結局3つ目の理由なのかもしれない。Vuexプラグイン作成
以下の機能を保持したVuexプラグインを作りました
- リアクティブにシステム日時を保持
- 更新タイミングを指定可能
- 1分ごとに更新といった場合には、00秒が経過したタイミングで書き換わる
- サーバ時刻とクライアント時刻の差分(オフセット時間)を保持し、オフセット時間とクライアント時間をもとにシステム日時を算出する
Vuexで任意の機能を持ったStoreを登録する場合には、対象のstoreをimportして登録するでも良いのですが、プラグインを使っても登録できます。
以下はVuexのプラグインでRegisterModuleでstoreを登録する際の記載です。import {systemDatetimeStore} from './vuex-systime-store' export default function VuexSystemDatetime (param) { return store => { 〜省略〜 // モジュールの登録 store.registerModule(moduleName, systemDatetimeStore) // 登録後に、必要に応じて初期化処理等を走らせる store.dispatch(moduleName + '/updateTime', null, { root: true }) } }モジュール名は任意にプラグインの中で任意に決めても良いのですが、paramから上書きできるようにした方が良いと思います。
また、再利用性を求めてStoreの中で、どのモジュール名が与えられているか取得しようとしたのですが、現在はできないようですね。仕方ないので、モジュール名をstateに持ってしまいました。そして、モジュール名は、プラグインからの初期化時に設定という形です。
何か良い案があれば良いのですが。。。export const systemDatetimeStore = { namespaced: true, state: { moduleName: null, // モジュール名を持たせてしまっている1分ごとに更新といった場合には、00秒が経過したタイミングで書き換わる
という処理については、以下のアクションで実装しました。
updateTime: function ({state, commit, dispatch}, param) { const now = Date.now() const calcTime = now + state.offsetTime // cutoffTimeに60*1000を持たせておき、差異を算出 const timeoutTime = calcTime % state.cutoffTime // setTimeoutで差異時間経過後に再度自身を再帰的に呼び出す const oldTimeoutId = moduleParameter[state.moduleName].timeoutID moduleParameter[state.moduleName].timeoutID = setTimeout(() => { dispatch('updateTime') }, state.cutoffTime - timeoutTime) // もし別のsetTimeoutが呼び出されているならば、リセット clearTimeout(oldTimeoutId) commit('setLocalTime', now) commit('setSystemTime', calcTime) }感想
Vuexのプラグインって、stateも含めて再利用したい場合にも応用できるみたいですね。Vueライブラリと合わせて提供すればコンポーネントおよびその状態管理も合わせて提供可能になりそうですね。
- 投稿日:2020-07-22T21:03:28+09:00
【LINE BOT】WebAPIを使用し自動返信BOTを作ってみた
はじめに
LINE BOTを利用し、六曜日とその内容を自動で返信するものを作りをました。
入力文字を替えることで、NASA(非公式)の「今日の天文学写真」を楽しむこともできます。目的
六曜にこだわる人は少なくなってきていると思いますが、まだ大安などの吉日を好む人は多いと思います、個人的なイベントが発生すると「今日は大安だっけ?」「友引かな?」などとたまに気にすることがあるので、「今日は」と入力する今日の六曜日を自動返信するBOTを製作しました。
別で作ったほうがいいかと思いましたが、今回は練習も兼ねてで画像も取得したいと思いましたので、「今日の画像は」と入力するとNASA(非公式)より「今日の天文学写真」を取得し自動で表示します。宇宙の美しさ広さをみて、自分の小ささを感じます。
作ったもの
構成
環境
- node.js v14.5.0
- @line/bot-sdk 7.0.0
- axios 0.19.2
- date-utils 1.2.21
- express 4.17.1
LINE Messaging API
Node.jsでLINE BOTを作る方法はこちらを参考にしました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
今回使用したWEBAPI
旧暦や六曜など日付に関する情報を取得できるAPI(外部サイト)
- 指定した日付に関する様々な(「今日は何の日」的な)情報を取得することができます。
- 「今日の天文学写真」を取得するのに使用しました。
コード
server.js"use strict"; const express = require("express"); const line = require("@line/bot-sdk"); const axios = require('axios'); const PORT = process.env.PORT || 3030; const config = { channelSecret: 'channelSecret', channelAccessToken: 'channelAccessToken' }; const app = express(); require('date-utils'); var dt = new Date(); var formatted = dt.toFormat("YYYY-MM-DD"); console.log(formatted); app.get("/", (req, res) => res.send("Hello LINE BOT!(GET)")); //ブラウザ確認用(無くても問題ない) app.post("/webhook", line.middleware(config), (req, res) => { console.log(req.body.events); //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。 if ( req.body.events[0].replyToken === "00000000000000000000000000000000" && req.body.events[1].replyToken === "ffffffffffffffffffffffffffffffff" ) { res.send("Hello LINE BOT!(POST)"); console.log("疎通確認用"); return; } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result) ); }); const client = new line.Client(config); async function handleEvent(event) { if(event.type !== 'message' || event.message.type !== 'text'|| event.message.type !== 'location') { return Promise.resolve(null); } let shakkou=''; let mes = ''; if(event.message.text === '今日は') {//「きょうは」と入力する。 const res = await axios.get('https://dateinfoapi.appspot.com/v1?date='+ formatted); const pushText = res.data.rokuyo; if(res.data.rokuyo === '赤口'){ shakkou ="正午の前後を除いて凶日、午前11時ごろから午後1時ごろまでは吉です。"; }else if(res.data.rokuyo === '大安'){ shakkou ="何事においても吉、成功しないことはない日"; }else if(res.data.rokuyo === '先勝'){ shakkou ="午前は吉、午後は凶"; }else if(res.data.rokuyo === '友引'){ shakkou ="朝晩は吉、昼は凶"; }else if(res.data.rokuyo === '先負'){ shakkou ="午前は凶、午後吉"; }else if(res.data.rokuyo === '仏滅'){ shakkou ="大凶日"; } console.log(pushText); mes = '今日は「'+ pushText +'」です。\n'+ shakkou; return client.replyMessage(event.replyToken, { type: 'text', text: mes, }); }else if(event.message.text === '今日の画像は'){ const response = await axios.get('https://api.nasa.gov/planetary/apod?api_key='+ {APIkey}); console.log(response.data.url); await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl:response.data.url, previewImageUrl:response.data.url, }); } } app.listen(PORT); console.log(`Server running at ${PORT}`);終わりに
LINEの国内ユーザー数は8400万人であり、SNSの中ではダントツで1位である、ちなみに2位のTwitterは4500万人(2020年6月時点)。メッセージインフラとして定着しているという利点があり。UIなども簡単にできるようなので、サービスを高速で構築できそう。
今後もLINE Messaging APIを活用しアウトプットしたいと思います。
- 投稿日:2020-07-22T19:48:34+09:00
TensorFlow.jsノードをNode-REDで使ってみる
日立製作所OSSソリューションセンタの横井です。今回は、画像認識を行うNode-REDのTensorflow.jsノードの使い方をご紹介します。
Tensorflow.jsとNode-RED
TensorFlow.jsとは、TensorFlowのJavaScript実装です。TensorFlow.jsを用いることで、ブラウザ上やサーバサイドのNode.jsで学習や推論処理をリアルタイムに実行できます。また、Node-REDは、主にIoT向けに開発されたピジュアルプログラミングツールです。InfoQの記事によると、2020年のトレンドとしてTensorlow.jsはEarly Majority、Node-REDはEarly Adoptersという流行っている/流行りつつあるOSSとして位置付けられています。
本記事では、これらトレンドの2つのOSSを組み合わせて何ができるか見てみましょう。
開発するフロー
本記事では、下のスクリーンショットの様な画像のどこに何が写っているか認識するフローを作成してみます。
このフローは、黄色のノード部品を用いてブラウザからファイルをアップロードすると、右下の「元の画像」のノードにアップロードした画像を表示します。同時に橙色の「画像認識」のノードにて、TensorFlow.jsの学習済モデルを用いて画像に何が写っているかを分析します。最後に、右上緑色の「認識結果出力」のノードを用いて右側のデパッグタブに写っている物の名前を表示します。同時に「認識結果付き画像」のノードの下に橙色の四角でアノテーションを付けた画像も表示してくれます。画像のどこの部分を認識したかが分かりやすいですね。
以降で、本フローの作成手順を説明します。今回の手順はローカル環境、Raspberry Pi環境、クラウド環境のNode-REDのどれを使っても動作します。ブラウザはNode-REDプロジェクトのUI動作テストで用いられているGoogle Chromeが良いでしょう。
Tensorflowノードをインストール
Node-REDのフローサイトには、TensorFlowを利用できるノードが複数公開されています。その中でも、学習済のモデルが入ったnode-red-contrib-tensorflowモジュールをインストールしてみます。Node-REDにTensorFlowノードをインストールするには、フローエディタの右上のメニュー -> 「パレットの管理」 -> 「パレット」 -> 「ノードを追加」と移動します。その後、検索キーワード入力欄に「node-red-contrib-tensorflow」と入力しましょう。
上の画像の様に、検索結果に今回用いるTensorFlow.jsノードが表示されるため、「インストール」ボタンをクリックしてTensorFlowノードをインストールします。インストールが完了すると、左側パレットの分析カテゴリの中に橙色のTensorFlow.jsノードが4つ登場します。
各TensorFlow.jsノードの説明は以下の表の通りです。全て画像認識を行うノードですが、画像認識の機能以外にも認識結果付きの画像データを生成したり、エッジ分析で必要となるオフラインでの利用ができたりする等の違いがあります。
# 名前 説明 認識結果付き画像 オフライン利用 1 cocossd 画像に写っている物体名を返すノード 有り 可能 2 handpose 手の画像から指や関節の位置を推定するノード 無し 不可 3 mobilenet 画像に写っている物体名を返すノード 無し 可能 4 posenet 人物の画像から腕や頭、足の位置を推定するノード 有り 可能 その他、Node-REDで画像データを扱うために必要な以下のノードも同じ手順でインストールしておきます。
- node-red-contrib-browser-utils : フローエディタ上から画像ファイルや音声ファイルをアップロードするノード
- node-red-contrib-image-output : フローエディタ上に画像を表示するノード
node-red-contrib-browser-utilsのインストールが終わると、入力カテゴリの中にfile-injectノード、microphoneノード、cameraノードが入っているはずです。また、node-red-contrib-image-outputのインストールが完了すると、出力カテゴリの中にimageノードが表示されていると思います。
フローを作成
必要なノードを準備できたため、早速フローを開発してみましょう。
右側のパレットから黄色のfile injectノード、橙色のcocossdノード、緑色のdebugノード(ワークスペースに配置すると、msg.payloadという名前に変わります)を配置し、各ノードの端子をワイヤーで接続します。ワイヤーを流れている画像データを確認するため、2つのimageノード(ワークスペース上に置くとimage previewという名前になります)をフローの下へ配置し、それぞれfile injectノードの出力端子、debugノードの入力端子に接続します。右側のimage previewノードのみは、表示する画像データの変数を指定するため、ノードの設定を変更する必要があります。設定を変更するには、image previewノードをダブルクリックしてノードプロパティ画面を開きます。ノードプロパティ画面では、デフォルトではmsg.payloadに入っている画像データを表示する様になっています。これを以下のスクリーンショットの様に、msg.annotatedInputに変更することで、cocossdノードがアノテーションを付けた画像を表示できる様になります。
各ノードに適切な名前を付けて右上の赤いデプロイボタンを押した後、file injectノードの左側のボタンをクリックして、ローカルPCにある空港の画像ファイルをアップロードしてみましょう。
「認識結果付き画像」のノードの下に機体に対して橙色のアノテーションが着いた画像が表示されました。また、右側のデバッグタブには、正しく「airplane(飛行機)」と表示されました。ぜひ手元にある画像をアップロードして、正しく認識できるか遊んでみてください。
最後に
今回は、cocossdというTensorFlow.jsノードをご紹介しました。その他のノードについてもほぼ同様のフロー作成手順で画像認識を行うことができます。本フローの応用例については、次回の記事で紹介してゆこうと思います。
※ 本記事はThe Linux FoundationのサイトLinux.comに投稿した下記記事の和訳です。
https://www.linux.com/news/using-tensorflow-js-and-node-red-with-image-recognition-applications/
- 投稿日:2020-07-22T19:20:22+09:00
写真を上げるだけ書籍管理linebot作ってみた
はじめに
一人暮らしを始めてからというものの、読書が趣味になりました。
家には本棚が6か所あり、そのどこに何の本があるかをしりたくなったので、書籍管理line bot作ってみました。line botベンリィ完成イメージ
写真をアップロード⇒本の登録
本情報の手打ちはしんどいなあと思ったので、本の写真アップロードで代用。
背表紙にあるISBN番号をOCRで読み取って、Google Books APIで書籍情報を呼び出します。
「これ登録しますけどどの棚に入れますか~」と聞かれるので、棚番号を応えて登録。
登録済のものについては※で忠告を出しておきます。
検索 (本の名前)⇒本の検索
「検索」(スペース)(本の名前の一部)とすると、その言葉を含む本がどの棚になるかを教えてくれます。複数の場合も対応、該当がない場合は該当するものがないよ、と言ってくれます。
スタンプ⇒キレる
スタンプに対しては厳しめの対応です。
そりゃあ書籍管理botだもんね。
スタンプなんて意味わかんないよね。
スタンプかわいい全体構造
本の登録
ざくっとした流れは
①写真アップロード
②cloud functionsが写真をcloud vision APIへ。写真内の文字を読み取る。
③cloud functionsでISBN番号を取り出し、google books APIへ。書籍情報を取得。
④cloud functionsからgoogle apps scriptに書籍情報を送り、スプレッドシートに入力。
となっています。直接GASとやり取りすればいいじゃんと思った人もいると思います。
その通りです(作ってる最中に思いました)。
その通りなんです。。。クソウセッカクツクッチャッタカラ本の検索
こちらの処理は
①検索_(書籍名の一部) と送る
②cloud functionsがgoogle apps scriptへ受け流す
③google apps scriptでスプレッドシート内の検索処理
④cloud functions経由でlineへ返す。
となっています。GASメインの処理ですね。いよいよcloud functionsなぜ使った感。
見切り発車でcloud functionsを起動したのが仇でしたね。。。
だからline botとGASつなげればいいじゃんとか言わないでやめて石投げなryサンプルコード
Cloud Functions
line botがpostする先の処理になります。
基本的にはpost内容に応じて処理を変えるようになっています。
言語はpythonで書いております。import os import base64, hashlib, hmac import logging import requests import json import re import base64 import urllib.request from flask import abort, jsonify from linebot import ( LineBotApi, WebhookParser ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, TextSendMessage ) def main(request): #環境変数から各トークンの取得 channel_secret = os.environ.get('LINE_CHANNEL_SECRET') channel_access_token = os.environ.get('LINE_CHANNEL_ACCESS_TOKEN') #botインスタンス line_bot_api = LineBotApi(channel_access_token) parser = WebhookParser(channel_secret) #lineから運ばれてきたデータ body = request.get_data(as_text=True) #認証周り hash = hmac.new(channel_secret.encode('utf-8'), body.encode('utf-8'), hashlib.sha256).digest() signature = base64.b64encode(hash).decode() if signature != request.headers['X_LINE_SIGNATURE']: return abort(405) try: events = parser.parse(body, signature) except InvalidSignatureError: return abort(405) #内容に応じて分岐 for event in events: #メッセージタイプに応じて処理を変化 message_type = event.message.type response_text = "" print(event) #テキストの場合、検索か棚番号 if message_type == "text": #テキストの内容を取得 message_text = event.message.text message_list = message_text.split() message = message_list[0] #検索の場合 if message == "検索" and len(message_list) >= 2: body = {"mode":"search","target":message_list[1]} res = send_to_GAS(body) response_text = res["content"] #数値の場合(写真を挙げた後の棚番号) elif message in ["0","1","2","3","4","5","6"]: body = {"mode":"int", "int":message} res = send_to_GAS(body) print(res) response_text = res["content"] #その他 else: response_text = "本の背表紙のアップロードをするか、\n「検索(スペース)(本の名前の一部)」で検索してください" #写真の場合、本の登録 elif message_type == "image": #写真取得 message_id = event.message.id message_content = line_bot_api.get_message_content(message_id) #OCRで文字を取得 res_json = vision_api(message_content) try: #文字からISBN番号を取得 res = res_json['responses'][0]['textAnnotations'][0]['description'] num = extractISBNNumbers(res) #ISBN番号から書籍を特定し一時保存 info_json = searchBooksFromISBN(num) res = send_to_GAS(info_json) #登録済の場合、警告を足す addition = "" if res["content"] >= 1: addition = "\n※既に登録済の書籍となっています" else: pass #確認レスポンス response_text = "「"+info_json["book"]["title"]+"」を登録します。登録先の棚番号を1~6の数字で記入してください。"+addition except KeyError: print('画像の品質が悪いようです') #スタンプはキレる elif message_type == "sticker": response_text = "は???????" #その他はキレない else: response_text = "本の背表紙のアップロードをするか、\n「検索(スペース)(本の名前の一部)」で検索してください" #lineに返信 line_bot_api.reply_message( event.reply_token, TextSendMessage(text=response_text)) return jsonify({ 'message': 'ok'}) #共通method #Google Apps ScriptとJSONのやり取りをする処理 def send_to_GAS(info_json): #基本パラメータ url = (Google Apps ScriptのURL) headers = {"Content-Type": "application/json"} #引数の内容をdumpsしてPOST body = json.dumps(info_json).encode("utf-8") post_request = urllib.request.Request(url, data=body, method="POST", headers=headers) #レスポンスをdictにparse with urllib.request.urlopen(post_request) as response: response_binary = response.read().decode("utf-8") response_dict = json.loads(response_binary) return response_dict #ここから先は写真に対する処理 #取得した写真をcloud visionへ送って文字取得 def vision_api(image_content): #基本パラメータ GOOGLE_CLOUD_VISION_API_URL = 'https://vision.googleapis.com/v1/images:annotate?key=' API_KEY = (cloud visionのAPI KEY) api_url = GOOGLE_CLOUD_VISION_API_URL + API_KEY #画像データをエンコード image_base64 = base64.b64encode(image_content.content).decode('utf-8') #POSTするJSONの用意 req_body = json.dumps({ 'requests': [ { 'image': { 'content': image_base64 }, 'features': [ { 'type': 'TEXT_DETECTION', 'maxResults': 1, } ] }] }) res = requests.post(api_url, data=req_body) return res.json() #取得した文字列からISBN番号を取得 def extractISBNNumbers(words): #ISBNが始まる文字の番号を取得し、そこから多めにとる startLetterNumber = words.find('ISBN') endLetterNumber = startLetterNumber + 21 ISBN_Number = words[startLetterNumber:endLetterNumber] #数値のみ取り出す number = re.sub("\\D", "", ISBN_Number) if len(number)==13 or len(number)==10: return number else: print('ISBNナンバーが読み取れません') return 'null' #取り出したISBN番号を利用して書籍情報を取得 def searchBooksFromISBN(num): #基本パラメータ API_URL = 'https://www.googleapis.com/books/v1/volumes?q=isbn:' FULL_API_URL = API_URL + num res = requests.get(FULL_API_URL).json()['items'][0] #取得したデータ title = res['volumeInfo']['title'] authors_list = res['volumeInfo']['authors'] authors_str = '' for author in authors_list: authors_str += author + ' ' #returnするデータ res = {"mode":"input","book": { "box_num":"box1", "title":title, "authors":authors_str }} return resCloud Functionsこまごま
環境変数から取得
#環境変数から各トークンの取得 channel_secret = os.environ.get('LINE_CHANNEL_SECRET') channel_access_token = os.environ.get('LINE_CHANNEL_ACCESS_TOKEN')cloud functionsで環境変数に設定したものを取り出す処理です。
今回初めて知ったので備忘録的にメモさせてください。
QiitaとかGitHubに上げるときとかに、いちいちマスキングする必要がなくなるので便利そうですね。
参考:(https://qiita.com/spre55/items/da2ded18ac4652abb936)Cloud Vision APIへのPOST処理
#取得した写真をcloud visionへ送って文字取得 def vision_api(image_content): #基本パラメータ GOOGLE_CLOUD_VISION_API_URL = 'https://vision.googleapis.com/v1/images:annotate?key=' API_KEY = 'Cloud VisionのAPI Key' api_url = GOOGLE_CLOUD_VISION_API_URL + API_KEY #画像データをエンコード image_base64 = base64.b64encode(image_content.content).decode('utf-8') #POSTするJSONの用意 req_body = json.dumps({ 'requests': [ { 'image': { 'content': image_base64 }, 'features': [ { 'type': 'TEXT_DETECTION', 'maxResults': 1, } ] }] }) res = requests.post(api_url, data=req_body) return res.json()こちらがCloud Vision APIを叩く際の処理。
base64エンコードをしたり、jsonでdumpsするなどのお作法の実装に苦労しました。
テキスト検出がこんなに簡単にできるなんて便利な時代ですよね。
参考:https://qiita.com/atomyah/items/25db0c9c2ecd319218df
参考:「独学プログラマーのためのAIアプリ開発がわかる本」https://www.amazon.co.jp/%E7%8B%AC%E5%AD%A6%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AEAI%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E3%81%8C%E3%82%8F%E3%81%8B%E3%82%8B%E6%9C%AC-%E6%B2%B3%E5%90%88-%E5%A4%A7/dp/4046040076Google Books APIへのPOST処理
#取り出したISBN番号を利用して書籍情報を取得 def searchBooksFromISBN(num): #基本パラメータ API_URL = 'https://www.googleapis.com/books/v1/volumes?q=isbn:' FULL_API_URL = API_URL + num res = requests.get(FULL_API_URL).json()['items'][0]ISBNさえ手に入れば、書籍情報の引っこ抜きはお手軽にできるようです。
authorsやtitle以外の情報も何かに使ってみたいところ。
自分の読む本の傾向分析とかいいかもしれませんね。Google Apps Script
こちらがスプレッドシートと直接やり取りをする処理になります。
言語はjavascriptですね。function doPost(e){ //シートオブジェクト var spreadsheet = SpreadsheetApp.openById(スプレッドシートのID); var sheet = spreadsheet.getSheetByName("mode"); //POSTされた内容を確認 var str = e.postData.contents; var json = JSON.parse(str); var mode = json["mode"]; //①写真アップロードの場合 //①A 一時セルに本情報を保存して確認する if (mode == "input"){ inputTemp(json); res_list = searchTarget(json["book"]["title"]) return returnAsJSON(res_list.length); //①B 棚番号を応えてきたら、そこに本情報を記入する } else if (mode == "int"){ tempMode = sheet.getRange("B2").getValue(); //写真アップロード後ならば、棚に記入 if (tempMode == "input"){ sheet.getRange("B2").setValue("") inputSheet(json); return returnAsJSON("完了しました"); } //写真をアップロードしていないならば、エラーを返す else { return returnAsJSON("本の背表紙のアップロードをするか、「検索(スペース)(本の名前の一部)」で検索してください"); } //②検索の場合 } else if (mode == "search"){ res_list = searchTarget(json["target"]); res = buildResponse(res_list); return returnAsJSON(res); } } //共通パーツ //引数をJSONでくるんでGoogle functionsに返す処理 function returnAsJSON(res){ dict = {"content": res}; // dictデータをjsonに変換 // payloadをreturnするだけではだめ // ContentServiceを利用して、responseを作成 payload = JSON.stringify(dict); //正味よくわからない ContentService.createTextOutput(); var output = ContentService.createTextOutput(); output.setMimeType(ContentService.MimeType.JSON); output.setContent(payload); return output; } //検索処理 function searchTarget(target){ var ss = SpreadsheetApp.openById(スプレッドシートのID); //検索結果を入れるリスト var res_list = []; //検索してリストにぶち込む //シートを一つ一つ調べていく for (var sheetNum = 1; sheetNum < 6; sheetNum++){ var sheet = ss.getSheets()[sheetNum]; var textFinder = sheet.createTextFinder(target); var ranges = textFinder.findAll(); //検索結果から取り出してリストにぶち込む for (var i = 0; i < ranges.length; i++ ) { var row = Math.round(ranges[i].getRow()); var authorLocation = "A" + row; var titleLocation = "B" + row; author = sheet.getRange(authorLocation).getValue() title = sheet.getRange(titleLocation).getValue() res_list.push("\n棚"+sheetNum+":"+author+ "著 "+title); } } return res_list } //上記で作ったレスポンスのリストを文面にする処理 function buildResponse(res_list){ //検索内容がある場合とない場合で返答を変える res = "検索結果:" if (res_list.length == 0){ res = "該当するものはありませんでした"; } else { for (i = 0; i < res_list.length; i++){ res = res + res_list[i]; } } Logger.log(res); return res; } //一時セルにスプレッドシートに一時保存 function inputTemp(json) { var spreadsheet = SpreadsheetApp.openById(スプレッドシートのID); var modeLocation = "B2"; var authorLocation = "B3"; var titleLocation = "C3"; var sheet = spreadsheet.getSheetByName("mode"); var location = sheet.getLastRow() +1; //スプレッドシートに一時保存 sheet.getRange(modeLocation).setValue("input"); sheet.getRange(authorLocation).setValue(json["book"]["authors"]); sheet.getRange(titleLocation).setValue(json["book"]["title"]); } //一時セルから棚のシートに記入 function inputSheet(json) { var spreadsheet = SpreadsheetApp.openById(スプレッドシートのID); var sheet = spreadsheet.getSheetByName("mode"); //一時セルから本情報を取る author = sheet.getRange("B3").getValue(); title = sheet.getRange("C3").getValue(); //記入先のセル情報 var sheet = spreadsheet.getSheetByName("box"+json["int"]); var location = sheet.getLastRow()+1; var authorLocation = "A" + location; var titleLocation = "B" + location; //記入処理 sheet.getRange(authorLocation).setValue(author); sheet.getRange(titleLocation).setValue(title); }スプレッドシートの構成
写真から書籍情報を取り出して、linebotに「この書籍をどこに入れますか」と返す際に、取り出した書籍情報をどこかに保持する必要があるなあと思いました。
そこで今回はスプレッドシートにmodeというシートを作りました。
もっとスマートにやりたいんですが、この辺り何か良いアイデアがありましたら教えてください・・・!
こちらが本棚になります。
box1~box6までそれぞれシートを分けて書いております。
検索処理もここを調べて結果を返します。
今後やってみたいこと
ランダムで棚の中の本を出してくれる機能欲しいなあと思ってます。
処理自体はそれほど難しくないと思うんですが、まだまだ不慣れでGASとの疎通がうまくいかないときがしばしば。
本当になんでcloud functions挟んだろうか(白目)あとは本の情報を一元管理できるようになったら、自分の読む本の傾向とか分析してみたいなあと思います。
どんな傾向の本を読むのか分析して、違うジャンルの本を読み始めてみるとか。
機械学習モデルをどこかにデプロイして、自分向けリコメンドシステムを作ってみるとか面白いかも。
- 投稿日:2020-07-22T18:00:55+09:00
IE11でPDFを表示する
はじめに
IE11ではPDFビューアー機能を提供していない為、pdf.jsを使ってPDFの表示をしようと思います。
pdf.jsはPDFを表示する機能を提供できるライブラリでダウンロードして設置するだけでPDFビューアーの機能を導入できます。
ただし、今回はnpmを使って管理をしたかった為、ビューアーページは自作して実装を行います。pdf.jsのインストール
npmを使ってインストールします。
npm i pdfjs-distPDFビューアーの作成
以下の3つ作成します。
- レンダリング用のjs
- PDF変換用のjs
- ビューアー用のテンプレートレンダリング用のjs
IE11でも動作させる為にrequireをes5にしています。
viewer.js// pdf.jsのes5用を指定 const pdfJs = require("pdfjs-dist/es5/build/pdf"); // PDF変換用のjsを指定 // なぜ、このような書き方をしているかはPDF変換用のjsの箇所で説明しています pdfJs.GlobalWorkerOptions.workerSrc = '/js/worker.js'; let currentPage = 1; // PDFの現在のページ番号 let pages = []; // PDFの全ページデータ const container = document.getElementById('container'); // PDFをレンダリングしたいコンテナ要素 const pdfPath = '/test.pdf'; // 読み込むPDFのファイルパス pdfJs.getDocument(pdfPath).promise.then(function(pdf) { // 1ページ目からPDFをレンダリング pdf.getPage(currentPage).then(renderPage); // レンダリング処理 function renderPage(page) { // レンダリング設定 const viewport = page.getViewport({ scale: 1 }); const sideMargin = 0.8; // ブラウザウィンドウ横幅の20%がマージンとなるようにする const scale = (window.innerWidth * sideMargin) / viewport.width; const scaleViewport = page.getViewport({ scale: scale }); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.height = scaleViewport.height; canvas.width = scaleViewport.width; const renderContext = { canvasContext: context, viewport: scaleViewport }; // レンダリング処理 page.render(renderContext).promise.then(function () { if (currentPage < pdf.numPages) { // 最終ページでなければ、次ページ読み込み pages[currentPage] = canvas; // 次ページ読み込み currentPage++; pdf.getPage(currentPage).then(renderPage); } else if (currentPage === pdf.numPages) { // 最終ページでレンダリング pages[currentPage] = canvas; for (let i = 1; i < pages.length; i++) { container.appendChild(pages[i]); } } }); } });PDF変換用のJS
worker.jsrequire("pdfjs-dist/es5/build/pdf.worker"); // es5のファイルを指定requireを一行だけなら、
viewer.js
ですれば良いじゃないかと本来なら思いますが、
pdf.jsはPDF変換処理をバックグランドで動作させる為に別ファイルで作成する必要があります。
もし同じファイルで読み込んでしまうとブラウザに以下の警告が出ます。
Fake Worker for Web Wroker
この警告が出ても問題なく動作し、気にするほどパフォーマンスが低下する訳ではないようですが、やはり警告が出ると気になるので分けた方が良いと思います。Fake Worker for Web Wroker
PDF.js will fallback to "fake worker" when Web Worker is not available, the code is built in, not much works need to be done, only need to be aware that the parsing would occur in the same thread, not in background worker thread any longer. From my tests, paring performance is not a concern, either running as a web service or a command line, regular PDF forms (less than 8 pages) usually be parsed and serialized within a couple of hundreds milliseconds.ビューアー用のテンプレート
viewer.blade.php<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>PDF Viewer</title> <script src="{{ mix('js/viewer.js') }}" defer></script> <style> body { background-color: #787878; } #container canvas { margin: 15px auto; display: block; box-shadow: 3px 3px; } </style> </head> <body> <div id="container"></div> </body> </html>バンドルの設定
今回はLaravel Mixを使用しているので、Webpackでの設定は適宜変更してください。
webpack.mixconst mix = require('laravel-mix'); mix.js('resources/js/viewer.js', 'public/js/'); // レンダリング用 mix.js('resources/js/worker.js', 'public/js/'); // PDF変換用まとめ
IE11でPDFを表示させるためにpdf.jsを使って実装してみました。
Adobe Acrobat Reader
をインストールすれば今回のような対応は不要ですが、全てのユーザーがインストールしている訳ではないので、できればPDFビューアーは用意しておきたいです。
また、スマホサイトでのPDFビューアーとしても利用できますので、IE11が使われなくなっても有用です。
- 投稿日:2020-07-22T17:44:30+09:00
Vue.jsのコンポーネント設計に滅茶苦茶悩んだ話
はじめに
この度Vue.js(2系) + Vuetifyの構成(Laravelを使ったMPA)で社内で初めてVueを導入しての開発を行う事になったのですが、フロントエンドエンジニアが私一人ということもあり、コンポーネント設計で結構悩んだ話です。
そこで一応自分なりの結論が出たので、共有いたします。
まだまだ粗削りな部分はあるかと思いますが、少しでも皆さんの参考になれば幸いです。詳細設計
まずVueに限らずコンポーネント志向のフレームワークでは基本的にAtomic Designをベースに設計すると思います。
そのため初めはそれに則り設計していたのですが、コンポーネントの粒度をどうするかの部分がイマイチ決まらず一度設計を見直す事になりました。そこで、他の設計手法はないかと色々な企業様やエンジニアの方の記事を参考にさせていただきました。
※参考にさせていただいた記事
①Atomic Designが刺さる現場・刺さらない現場
②BASEのVue.jsコンポーネントの設計
③ワイ「何でそんな小っさいコンポーネント作ってるん?wコンポーネント設計の概要については①、③の記事で勉強させていただき、詳しい設計は主に②のBASE様のコンポーネント設計を参考にさせていただきました。
まず上記②を参考にコンポーネントの種類は、
- 唯一http通信とstoreへのアクセスを行え、機能単位でのstateを持つベースとなるコンポーネント(Base)
- 表示にのみ責務をもつコンポーネント(Presentational)
- 様々なコンポーネントで再利用される、Presentationalコンポーネント(CommonPresentational)
の三種類としました。
5つに分かれているAtomic Designと比べると大分少ないと思います。
ただ根本的な思想の部分は同じで、まず「Pages」と「Templates」に相当するのがBaseです。
上記の通り機能単位の比較的大きなstateを持ち、唯一http通信とstoreへのアクセスが行えます。次に「Organisms」と「Molecules」と「Atoms」に相当する、表示にのみ責務を持つのがPresentationalになります。
その中から再利用するものはCommonPresentationalに切り出し、あとは「Atoms」や「Molecules」単位で切り出す事を強制せず、必要に応じて新たなPresentationalを作るか、コンポーネントに直接記述します。そしてコンポーネントの命名規則ですが、Baseには頭に
base-
をつけ、Presentationalは親のBaseのbase-
以降の文字を受け継ぎます。
例:base-header
>header-bar
,header-drawer
またCommonPresentationalは頭にcommon-
を付ける事としました。
例:common-breadcrumbs
コード
ではコードの面で具体的にはどうなっているかですが、以下のサンプルページでの例をご覧ください。
-サンプルページ-
まずは全体の構成。
(※本来base-header
は全ページ共通なのでapp.blade.php
に記述し、base-contact
はルーティングで切り替える部分なので実際の構成とは異なっており、レイアウトやデータの受け渡し部分も分かりやすくするためにあえて端折っています。)app<template> <div id="app"> <base-header /> <base-contact /> </div> </template>次に
base-header
の中身は、base-header<template> <header class="header"> <header-bar /> <header-drawer /> </header> </template>最後に
base-contact
の中身は、base-contact<template> <div class="contact"> <common-breadcumbs /> <contact-form /> <div> </template>のようになっています。
ここで登場するコンポーネントは全部で6つで、画面とともに色分けしたものが以下です。
- Base
- base-header
- base-contact
- Presentational
- header-bar
- header-drawer
- contact-form
- CommonPresentational
- common-breadcrumbs
※コード量が少なく再利用もしない場合は、更に
contact-form
内をパーツ単位でPresentationalに切り出すことはせず、コンポーネントに直接記述する。このような感じで、機能単位でコンポーネントを分けています。
まとめ
いかがでしたでしょうか。
言ってしまえばAtomic Designの縛りを緩くして、「通信・storeアクセス・state保持用のコンポーネント」・「表示用のコンポーネント」・「表示用の共通コンポーネント」の3つに分けただけなのですが、今のところうまくハマっているのでやり直してよかったです。
それでは。
- 投稿日:2020-07-22T17:16:53+09:00
第三回 JavaScriptの非同期処理とシングルスレッド
まずはサンプルコードから
以下の様な JavaScript
index.js
を実行します。
- 【処理 1】ミリ秒で終わる処理を setTimeout() で 5 秒後に発火
- 【処理 2】ミリ秒で終わる処理を setTimeout() で 0 秒後に発火
- 【処理 3】10 秒かかる同期処理を実行
/** 本スクリプトの実行開始時間 */ const startTime = new Date().getTime() /** * 本スクリプトを実行してからの経過秒数を取得する。 * * @return {number} 経過秒数 */ function getSeconds() { return (new Date().getTime() - startTime) / 1000 } /** * 本スクリプトを実行してからの経過秒数を取得する(フォーマット済み版)。 * * @return {number} 経過秒数 */ function getSecondsFormatted() { return getSeconds().toFixed(6).padStart(10, ' ') } /** * 処理 1 (非同期, 5 秒後に発火). */ function func1() { console.log(`${getSecondsFormatted()} seconds --> 処理 1 (非同期, 5 秒後に発火)`) } /** * 処理 2 (非同期, 0 秒後に発火). */ function func2() { console.log(`${getSecondsFormatted()} seconds --> 処理 2 (非同期, 0 秒後に発火)`) } /** * 処理 3 (同期. 10 秒かかる). */ function func3() { while (getSeconds() < 10) { // consuming a single cpu for 10 seconds... } console.log(`${getSecondsFormatted()} seconds --> 処理 3 (同期, 10 秒かかる)`) } // メイン処理開始 console.log(`${getSecondsFormatted()} seconds --> index.js START`) // 処理 1 (非同期, 5 秒後に発火) setTimeout(func1, 5000) // 処理 2 (非同期, 0 秒後に発火) setTimeout(func2) // 処理 3 (同期, 10 秒かかる) func3() console.log(`${getSecondsFormatted()} seconds --> index.js END`)
期待値?
なんとなく 「こう動作するだろう...」 という思うのは下記でしょう。
0.000000 seconds --> index.js START 0.000000 seconds --> 処理 2 (非同期, 0 秒後に発火) 5.000000 seconds --> 処理 1 (非同期, 5 秒後に発火) 10.000000 seconds --> 処理 3 (同期, 10 秒かかる) 10.000000 seconds --> index.js END
実際は...
現実はこうです。何故でしょうか。
0.175104 seconds --> index.js START 10.000085 seconds --> 処理 3 (同期, 10 秒かかる) 10.000210 seconds --> index.js END 10.000955 seconds --> 処理 2 (非同期, 0 秒後に発火) 10.001161 seconds --> 処理 1 (非同期, 5 秒後に発火)
シングルスレッドなので順番に処理している
おおよそ、
JavaScript
の内部では下記のように処理がシングルスレッドで行われています。
- 最初のエントリ JavaScript index.js がタスクとして、未実行キューに乗ります
- 未実行キューから index.js タスクが取り出され、実行が開始されます
- setTimeout(処理1, 5秒) が実行され、【処理 1】がタイマーキューに追加されます
- setTimeout(処理2, 0秒) が実行され、【処理 2】がタイマーキューに追加されます
- 【処理 3】が同期的に実行され、10 秒間、CPU (シングルコア) を専有します
- index.js タスクの実行が終了します
- タイマータスクから 有効期限が切れたタスク【処理 2】 を取り出し、実行が開始されます
- 【処理 2】タスクの実行が終了します
- タイマータスクから 有効期限が切れたタスク【処理 1】 を取り出し、実行が開始されます
- 【処理 1】タスクの実行が終了します
結論
つまり、
setTimeout()
等の非同期タイマー処理は...指定した時間が来たら即座に Callback を実行する
ではなく指定した時間を 過ぎてたら Callback を できるだけ早く 実行する
です。それは
Promise
やfetch
でも同じでCallback が実行可能になってから (現在実行中の他の処理を待って) 順番が来たら実行開始する
です。
- 投稿日:2020-07-22T16:48:40+09:00
JavaScriptで二重ループの内側から抜け出す方法
入れ子構造になっている、二重ループの内側から抜け出したい時ってありますよね。
そんな時に使える「ラベル構文」というのをJavaScriptの勉強中に知りましたので、書いておきます。
今回は、12×12のかけ算をしていく中で、100を越えたらループを抜けるというコードを書いてみます。コード
index.html<!DOCTYPE html> <html> <head> <title>JavaScriptの勉強</title> </head> <body onload="proc();"> <p id="test-box"></p> <script> function proc(){ var testBox = document.getElementById("test-box"); timesTable : for(var i = 1; i <= 12; i++){ for (var j = 1; j <= 12; j++){ var k = i * j; if( k > 100){ break timesTable; } testBox.innerHTML += k + ','; } testBox.innerHTML += '<br>'; } } </script> </body> </html>普通にループを抜けようとすると・・・
index.jsfunction proc(){ var testBox = document.getElementById("test-box"); for(var i = 1; i <= 12; i++){ for (var j = 1; j <= 12; j++){ var k = i * j; if( k > 100){ break; } testBox.innerHTML += k + ','; } testBox.innerHTML += '<br>'; } }こういうふうに書くと、jのループから抜け出すことはできます。
しかし、その外側にあるiのループまで一緒に抜けだしたいときには、この書き方では不十分です。
こんな感じになってしまい、ループが続いていきます。1,2,3,4,5,6,7,8,9,10,11,12, 2,4,6,8,10,12,14,16,18,20,22,24, 3,6,9,12,15,18,21,24,27,30,33,36, 4,8,12,16,20,24,28,32,36,40,44,48, 5,10,15,20,25,30,35,40,45,50,55,60, 6,12,18,24,30,36,42,48,54,60,66,72, 7,14,21,28,35,42,49,56,63,70,77,84, 8,16,24,32,40,48,56,64,72,80,88,96, 9,18,27,36,45,54,63,72,81,90,99, 10,20,30,40,50,60,70,80,90,100, 11,22,33,44,55,66,77,88,99, 12,24,36,48,60,72,84,96,ラベル構文をつかったループ
こんな時に使うのがラベル構文というものです。
index.jsfunction proc(){ var testBox = document.getElementById("test-box"); // 文頭にラベルを付ける(ラベル名: ) timesTable : for(var i = 1; i <= 12; i++){ for (var j = 1; j <= 12; j++){ var k = i * j; if( k > 100){ break timesTable; // どのループを抜け出すか指定する } testBox.innerHTML += k + ','; } testBox.innerHTML += '<br>'; } }最初のfor文の文頭に、"timesTable : "というコードを追加し、breakのところにも"timesTable"を追加しました。
forやwhile,ifなどの頭にこのようなラベルをつけてやることで、どこをbreakするか指定してやることができます。
結果こうなりました。1,2,3,4,5,6,7,8,9,10,11,12, 2,4,6,8,10,12,14,16,18,20,22,24, 3,6,9,12,15,18,21,24,27,30,33,36, 4,8,12,16,20,24,28,32,36,40,44,48, 5,10,15,20,25,30,35,40,45,50,55,60, 6,12,18,24,30,36,42,48,54,60,66,72, 7,14,21,28,35,42,49,56,63,70,77,84, 8,16,24,32,40,48,56,64,72,80,88,96, 9,18,27,36,45,54,63,72,81,90,99,100以上の数字が出た瞬間にすべてのループから抜け出せていることがわかりますね。
whileでも試してみましたが、同じようにできていました。とはいえ、そもそも多重ループを使うのがわかりづらくなるうえ、ラベルでそのループを指定するとなると、なおさらわかりづらくなってしまうと思うので、できればラベルなど使わずに入れ子構造以外での処理を考えたほうが良いかもしれませんね。
ちなみに、英語での九九はtimes tableといって、12×12まで覚えたりするらしいですね。初めて知りました。
- 投稿日:2020-07-22T16:48:06+09:00
javascriptの基礎の基礎(英語)
JS for beginners
comment
// this is a comment
/* multiple line comment
multiple line comment
multiple line comment
*/Data Types and Variables
7 different data types and variables
- undifined
- null
- boolean
- string
- symbol
- number
- object: store a lot of data and functionsvar myName = 'Beau'
myName = 8
let ourName = 'freecodecamp'
const pi = 3.14the difference among them
var: to use the varible in all the program
let: only be used in the scope of where you declared that
const: some varible that should never change, if you try to change it you are going to get an errorStoring Values with Assignment Operator
declaring variables and assigning variables
declaring variblesvar a;
assigning variblesvar b = 2;
a=7;
b=a;
console.log(b);
Initializing Variables with Assignment Operator
var a = 9;
This is the way to do it.Uninitialized Variables
var a;
This is uninitialized we haven't defined its value.
var a = 1;
var b = 2;
var c = "I am a ";c = c + "string!"
case sensitivity in variables
var Study; STudy = 1; // Javascript code is case sensitive, so make it correct!We should always use Camel Case to name the variables.
adding numbers
var sum = 10 + 1;
var difference = 45-7;
incrementing and decrementing
var myVar = 10; myVar--; // so now, myVar is 9Decimal Numbers
var ourDeciaml = 5.1;
Multiply
`var p= 5.0 / 2.5;
Remainder
var remainder; remainder = 11 % 3; // so remainder is 2 now.Compound Assignment with Augmented Addition or Substraction
var a = 3; var b = 17; a = a + 12; //a += 12; is the same b = b + 9; // b += 9; is the same // -= is the same way as how "+=" is used here
- 投稿日:2020-07-22T16:45:54+09:00
リロードしないとJavaScriptが動かない?【Rails】
本記事について(お断り)
この記事は、しがないプログラミング初心者がお勉強よろしくプログラミング・技術に触れるべく、行ったことの備忘録として書き綴ったものです。至らぬ点、誤解を招く表現など、ご指摘がありましたら甘んじて受け入れます?♂️
問題
JQueryを使って背景画像をフェードインさせる実装を行った。
該当ページを呼び出すと、しっかりとフェードインしてきた。よしよし。
別ページに遷移して再度該当ページを開いてみる。あれ、真っ白だ。なぜ?原因
原因をググっていると、どうやらTurbolinksが関係しているっぽい。
Turbolinksとは、Rails4から標準装備されているgemで、ページ遷移の際に、CSSやJavaScriptの読み込みを省略してくれ、高速化をもたらしてくれるという素晴らしい機能。ただし、高速化が期待できる反面で、JavaScript関係でトラブルが起こる可能性も。初心者には取っ付きづらい機能、私のように。解決策
1.Turbolinksが提供しているイベント
turbolinks:load
を利用するjsファイル冒頭に
$(document).on('turbolinks:load', function() { ...もしくは
document.addEventListener('turbolinks:load', function() { ...と書いておけば、ロードしてくれるのでJavaScriptが機能するようになります。
2.Turbolinksを無効にする
・部分的にTurbolinksを無効にする
link_to
にオプションとして埋め込むことで<%= link_to 'hoge', hoge_path, data: {"turbolinks" => false} %>遷移先のページでtubolinksを無効にする。
・gemファイルから削除する
gem 'turbolinks', '~> 5'Gemfileから直接turbolinksを削除することでturbolinksを完全無効化する。
ただし、turbolinksは前述のとおり高速化をもたらしてくれている素晴らしい機能であることは違いない。コストとメリットを考えて、無効化するかを慎重に考えた方がいい。解決!
参考
Rails ページ移動した際にリロードしないとJSが機能しない
現場で使える Ruby on Rails 5速習実践ガイド
- 投稿日:2020-07-22T14:58:25+09:00
オブジェクトに昇順のIDをつけたい
※簡単なTodoリストの作成過程において自分用メモです。
間違い等ございましたらご指摘お願いいたします。
目的
Todoのアイテム(オブジェクト)を作成する際、それぞれのアイテムに数字のIDを昇順で持たせます。
ローカルストレージからオブジェクの配列を呼び出す場合は、IDの最大値+1からIDをつけていきます。//イメージ taskItems=[{ id : 1, name : 'タスク1', completed : false, }, { id : 2, name : 'タスク2', completed : false, }, { id : 3, name : 'タスク3', completed : true, },]方法
変数taskId=1を定義し、classで定義したタスクのひな形で新しいタスクを作る度にtaskIdが+1になるようにします。
let taskId = 1; class NewTask { constructor(task) { this.id = taskId++; this.name = task; this.completed = false; } }
ローカルストレージから配列を呼び出す場合について
呼び出した配列の要素数が1以上の場合は一番大きいidを検索して+1します。
配列が空の場合は初期値は1とします。//ローカルストレージからデータの取得するための関数 function loadStorage() { let getJson = localStorage.getItem('kye'); return JSON.parse(getJson); } } //ローカルストレージにデータがあれば取得 if (loadStorage()) { const localStorageDate = loadStorage(); taskItems = localStorageDate; //taskItemstが空でなければtaskIdは最大値+1、空の配列なら1、 taskId = taskItems.length ? Math.max.apply(null, taskItems.map(task => task.id)) + 1 : 1; }Math.max ()は与えられたそれぞれの引数の最大値を1つ返します。
可変長引数の関数であるため、配列を渡せないのでapplyを使用。
ES2015 以降ならスプレッド演算子(...)を用いて個々の値に分解することも可能です。applyはオブジェクトの関数を呼び出し、第1引数で関数配下のthisの参照先を変更できます。
nullを渡した場合は暗黙的にグローバルオブジェクトが渡されたものとします。
第2引数にtaskItemsの要素のidを配列にしたものを渡すため、mapを使用します。
mapは配列の要素をコールバック関数で順に加工し、最終的にできた新しい配列を返します。Math.max.apply(null, taskItems.map(task => task.id)) + 1これにより、taskItemsの要素ごとのidで配列を作成し、最大値を検索して+1することができます。
- 投稿日:2020-07-22T14:33:41+09:00
Node.js でお手軽スクレイピング 2020 年夏(cheerio版) ポエム
まずは環境から。
$ node -v v12.16.1そしてプロジェクトの初期化を行って、2 つほどライブラリをインストールします。
$ npm init -y $ npm install node-fetch cheerio --save-dev必要なライブラリが揃ったところで早速スクリプトを書いていきましょう。サンプルに気象庁の東京都の週間天気予報のページを選びました。
index.js#!/usr/bin/env node const fetch = require('node-fetch'); const cheerio = require('cheerio'); const main = async () => { const res = await fetch('https://www.jma.go.jp/jp/week/319.html'); if (res.status !== 200) { console.log(`error status:${res.status}`); return; } // jqueryチックに使えるように変換 const $ = cheerio.load(await res.text()); const nodes = $('#infotablefont tr:nth-child(4) td'); nodes.map(function (i) { console.log(nodes.eq(i).text().trim()); }); }; main();そのコードをスクリプトファイルに貼り付ければそれだけでもうスクレイピングの完成です。このスクリプトを実行すると以下のような結果が得られます。
$ ./index.js 曇時々雨 曇一時雨 曇 曇時々晴 曇時々晴 曇時々晴 曇時々晴
以上でスクリプトの解説は終わりです。
最後に、忘れてはならないのはスクレイピングは最終手段であるという事です。API が提供されているサービスであれば必ずそちらを使うべきですし、やむを得ずスクレイピングする際はサーバに過度な負荷を与えることの無いよう気をつけましょう。
参考記事
- 投稿日:2020-07-22T14:32:53+09:00
SvelteがTypeScript�を公式サポートしたので試してみる!
はじめに
SvelteがTypeScriptを公式サポートしたことを発表したのでさっそく試してみたいと思います!
Svelteって何?って方は僕が以前書いた記事があるので君はVue,Reactの次に来るSvelteを知っているか?を見てから帰ってきてください!ok, we've kept you in suspense long enough. we are absolutely cock-a-hoop to announce that Svelte now officially supports...
— Svelte (@sveltejs) July 21, 2020
??? TYPESCRIPT!!! ???https://t.co/Ze8hnp0Qgi
amazing, amazing work by @orta, dummdidumm, jasonlyu123, @UnwrittenFun, @halfnelson_au and @octrefVSCodeの拡張機能をインストールする
VSCodeを使って開発している方は、公式の拡張機能をインストールすることでTypeScriptの恩恵をより受けられるのでインストールしましょう!
Svelte for VS Code新規プロジェクトで作る
npx degit sveltejs/template svelte-typescript-app cd svelte-typescript-app node scripts/setupTypeScript.js
上記コマンドを実行することですぐにSvelte TypeScriptプロジェクトを開発することができます。
既存プロジェクトに追加する
npm install --save-dev @tsconfig/svelte typescript svelte-preprocess svelte-checkrollup.config.jsに設定を追加
rollup.config.js+ import autoPreprocess from 'svelte-preprocess'; + import typescript from '@rollup/plugin-typescript'; export default { ..., plugins: [ svelte({ + preprocess: autoPreprocess() }), + typescript({ sourceMap: !production }) ] }プロジェクトのルートにtsconfig.jsonを作成
tsconfig.json{ "extends": "@tsconfig/svelte/tsconfig.json", "include": ["src/**/*", "src/node_modules"], "exclude": ["node_modules/*", "__sapper__/*", "public/*"], }実際書いてみる
Header.svelte<script lang="ts"> export let userName: string; </script> <header> <h1>Hello {userName}</h1> </header>App.svelte<script lang="ts"> import Header from './Header.svelte' </script> <main> <Header userName={24}/> <p> Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps. </p> </main>型を試す為にuserNameはStringを定義しているのにNumberを渡してみる
VSCode上でエラーが確認できました!
コマンドライン上でチェックする
$ npx svelte-check Loading svelte-check in workspace: svelte-typescript-app Getting Svelte diagnostics... ==================================== svelte-typescript-app/src/App.svelte:6:11 Error: Type 'number' is not assignable to type 'string'. (ts) <Header userName={24}/> ==================================== svelte-check found 1 errorこちらでもエラーが確認できました!
さいごに
TypeScriptが公式サポートしたのは嬉しいですね!
Svelte使ってみたいけどTypeScriptで書きたいと思っていた方々は使い始めるきっかけになるんじゃないかなと思います!では、良いSvelteライフを〜
参考
- 投稿日:2020-07-22T13:35:19+09:00
ページ遷移をすると、jQueryが動かなくなる問題解決大臣
jQueryは問題なく発動するけど、ページを遷移すると発動しなくなる。しかしページの再読み込みをするとちゃんと動いてくれる問題
『環境』
- ruby:2.6.6
- rails:6.0.3.2今回の悪の根源
それはturbolinks
turbolinksって美味しいの??
なんとなくは聞いたことがあったけど理解してなくて調べてみた。
どうやらturbolinksはページの遷移を早くしてくれる便利なヤツらしいなんで早くなるん??
なぜ早いかと言うと、ページを遷移するときに全部のファイルを読み込んで遷移するわけじゃなくて、HTMLのbody要素だけを取っ替えて読み込んでくれているから早くなってくれているみたい。
つまり
ページを遷移するときにjavascriptファイルは読み込んでおらず、ページ遷移するとjQueryが発動しなくなっていたようだ。そこからページの再読み込みをすると、ようやくjavascriptファイルを読み込んでjQueryが発動したという流れ。
とりあえずturbolinksには休んでもらいましょう
qiita.rbrequire("@rails/ujs").start() #require("turbolinks").start() ← コメントアウト require("@rails/activestorage").start() require("channels") require('jquery')
- 投稿日:2020-07-22T13:13:38+09:00
Javascriptからphpへデータを渡す方法
- 投稿日:2020-07-22T12:42:25+09:00
JavaScript勉強してみた(基本編)
JavaScriptでできること
さまざまです。。。
- ブラウザ上でHTMLとCSSを操作してWebサイトの見た目を別のものにする。
- アニメーションさせる。
- 最近ではブラウザ以外でも、Node.jsなどサーバサイドでJavaScriptが使われている。
その他にも、
- ReactNativeやCordovaといったJavaScriptでスマートフォンアプリが作れる。
- Electronでデスクトップアプリが開発できる。
など、JavaScriptを学ぶことで 様々なことができる!
早速書いてみた
sample01.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>タイトル</title> </head> <body> <script> alert("アラートだよ!") </script> </body> </html>JavaScriptは、HTML内に記述したscript要素の内側に記述した。
ブラウザでアクセスしてみると命令どおりアラートが表示される
JavaScriptのファイルを独立させてみた
sample02.jsalert("アラート!")sample02.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>タイトル</title> </head> <body> <script src="sample02.js"></script> </body> </html>デバッグ
- chromeのブラウザの場合
F12を押すとデベロッパーツールが表示される。
わざと間違ったソースを作成し表示を確認。sample02.jsalrt("アラート!")Consoleタブにalrtなんてないよって感じでエラー表示。
変数
letとconst
- let ・・・再代入可能
- const ・・・再代入不可
letのほうが再代入できて便利な気がするが、再代入で値が変化するのでソースコードが複雑に見えてしまう可能性がある。
再代入が必要な場合のみletにして、不要なときはできるだけconstを利用するようにする。変数をブラウザに表示
sample03.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>タイトル</title> </head> <body> <div id="title"></div> <script src="sample03.js"></script> </body> </html>id属性[title]のdiv要素を追加
sample03.jsconst title = "Hello World" document.querySelector("#title").textContent = titledocument.querySelectorという命令でHTML要素を取得。
カッコの中には取得したいHTMLを表すCSSセレクタを文字列で指定。
取得したHTMLのtextContentにtitleを代入。真偽値
sample04.jsconst myString === "雨" const myBoolean = myString === "晴れ"2行目は、変数myStringの中身が"晴れ"かどうかを確認し、その結果をmyBoolean変数に格納している。
boolean型なので結果は、falseとなる。
配列
forで配列を取得
sample_array01.jsconst myAnimals = ["ねこ","いぬ","ぞう"] for (let i=0; i < myAnimals.length; i++){ console.log(myAnimals[i]) }forEachで配列を取得
sample_array02.jsconst myAnimals = ["ねこ","いぬ","ぞう"] myAnimals.forEach(function(animal,i){ console.log(animal) })配列の追加
先頭に追加(unshift)
sample_array03.jsconst myAnimals = ["ねこ","いぬ","ぞう"] myAnimals.unshift("きりん") console.log(myAnimals)最後尾に追加(push)
sample_array03.jsconst myAnimals = ["ねこ","いぬ","ぞう"] myAnimals.push("きりん") console.log(myAnimals)こういった命令をJavaScriptでは、メソッドという。
配列の後ろに.(ドット)を書きメソッド名を記述。配列の削除
先頭を削除(shift)
sample_array03.jsconst myAnimals = ["ねこ","いぬ","ぞう"] myAnimals.shift() console.log(myAnimals)最後尾を削除(pop)
sample_array03.jsconst myAnimals = ["ねこ","いぬ","ぞう"] myAnimals.pop() console.log(myAnimals)配列の削除、追加(splice)
任意の位置で削除、追加
削除
sample_array04.jsconst myAnimals = ["ねこ","いぬ","ぞう"] myAnimals.splice(1,1) console.log(myAnimals)
- 第1引数・・・削除したい配列の番号
- 第2引数・・・削除する項目数
配列1から1つ削除となる
追加
sample_array05.jsconst myAnimals = ["ねこ","いぬ","ぞう"] myAnimals.splice(1,0,"きりん","ぱんだ") console.log(myAnimals)配列順序の逆転(reverse)
sample_array06.jsconst myAnimals = ["ねこ","いぬ","ぞう","きりん","ぱんだ"] myAnimals.reverse() console.log(myAnimals)並び順が逆転します。
50音順に変更(sort)
sample_array07.jsconst myAnimals = ["ねこ","いぬ","ぞう","きりん","ぱんだ"] myAnimals.sort() console.log(myAnimals)正確には、Unicodeコードポイントと呼ばれる文字に割り当てられている番号順。
Unicodeコードポイントで並べる際は、ひらがな・カタカナ・漢字で読み順とならない場合があるので要注意。配列の切り取り(slice)
sample_array08.jsconst myAnimals = ["ねこ","いぬ","ぞう","きりん","ぱんだ","ごりら"] const newAnimals = myAnimals.slice(3) console.log(newAnimals)第1引数にマイナスを指定すると
sample_array09.jsconst myAnimals = ["ねこ","いぬ","ぞう","きりん","ぱんだ","ごりら"] const newAnimals = myAnimals.slice(-2) console.log(newAnimals)中間
sample_array10.jsconst myAnimals = ["ねこ","いぬ","ぞう","きりん","ぱんだ","ごりら"] const newAnimals = myAnimals.slice(2,5) console.log(newAnimals)配列のフィルタリング(filter)
sample_array11.jsconst myAnimals = ["ねこ","いぬ","ぞう","きりん","ぱんだ","ごりら"] const newAnimals = myAnimals.filter(function(animal){ return animal !== "きりん" }) console.log(newAnimals)きりん以外が出力される。
配列の中身を変換(map)
sample_array12.jsconst myAnimals = ["cat","dog","elephant","giraffe","pand","gorilla"] const newAnimals = myAnimals.map(function(animal){ return animal.toUpperCase() }) console.log(newAnimals)toUpperCaseメソッドで配列の中身が小文字から大文字になる。
配列の結合(concat)
sample_array13.jsconst myAnimals = ["ねこ","いぬ","ぞう"] const yourAnimals = ["きりん","ぱんだ","ごりら"] const newAnimals = myAnimals.concat(yourAnimals) console.log(newAnimals)配列「myAnimals 」と「yourAnimals」が結合される。
配列の位置を取得
sample_array14.jsconst myAnimals = ["ねこ","ごりら","ぞう","きりん","ぱんだ","ごりら"] const index = myAnimals.indexOf("ごりら") console.log(index)indexOfメソッドは、最初に一致した位置が返却される。
→1sample_array15.jsconst myAnimals = ["ねこ","ごりら","ぞう","きりん","ぱんだ","ごりら"] const index = myAnimals.lastIndexOf("ごりら") console.log(index)lastIndexOfメソッドは、最後に一致した位置を返却。
→5特定の条件にマッチするか調べる(some,every)
some
sample_array16.jsconst myAnimals = ["ねこ","ごりら","ぞう","きりん","ぱんだ","ごりら"] const isExist = myAnimals.some(function(animal){ return animal === ("ごりら") }) console.log(isExist)引数に関数を指定し、条件判定の結果をreturnで返却して、コンソールに出力している。
every
sample_array17.jsconst myAnimals = ["ねこ","ごりら","ぞう","きりん","ぱんだ","ごりら"] const isExistAll = myAnimals.every(function(animal){ return animal.length === 3 }) console.log(isExistAll)everyは、配列の各項目がすべて条件にマッチするか確認するメソッド。
結果は、3文字以外もあるのでfalseとなる。find
sample_array18.jsconst myAnimals = ["ねこ","ごりら","ぞう","きりん","ぱんだ","ごりら"] const threeLengthAnimal = myAnimals.find(function(animal){ return animal.length === 3 }) console.log(threeLengthAnimal)配列内で3文字の項目を探してきて最初の項目を出力する。
→「ごりら」が出力される。
関数
いきなり雑いけどこんな感じ。
sample_func01.jsfunction getAddNum(num1,num2){ const answer = num1 + num2 return answer } const answer1 = getAddNum(1,29) console.log(answer1) const answer2 = getAddNum(18,12) console.log(answer2) const answer3 = getAddNum(23,41) console.log(answer3)変数のスコープ
sample_func02.jsconst baseNum = 10; function getAddNum(num){ const answer1 = baseNum + num return answer1 } const answer2 = getAddNum(12) console.log(answer2)関数外で定義したbaseNumは関数内で利用できる。
逆に、関数内で定義したanswer1は関数外では利用できない。関数以外の変数スコープ
sample_func03.jsconst myWeather = "雪"; if(myWeather === "雪"){ const text = "傘いるね" } else { const text = "傘いらないね" } console.log(text) ```sample_func04.js 関数以外にもスコープは存在し、letやconstは、波カッコ{...}の間でしか有効ではない。 そのため、これはエラーとなる。 [Uncaught ReferenceError: text is not defined]→波カッコ{...}の外で宣言すればletやconstを利用することは可能です。
var
sample_func05.jsconst myWeather = "雪"; if(myWeather === "雪"){ var text = "傘いるね" } else { var text = "傘いらないね" } console.log(text)これは波カッコ以外からも参照できるが、スコープが大きくなり、予期せぬ場所で目的の変数を上書きするバグの原因になるので、特段の理由がなければ利用しない方が良い。
関数宣言と関数式
関数宣言
test.jsfunction 関数名(){ //処理 }関数式
test.jsconst 関数名 = function(){ //処理 }例
sample.jsconst getAddNum = function(num1,num2){ const answer = num1 + num2 return answer } const answer1 = getAddNum(1,20) console.log(answer1)
- 関数宣言と関数式の定義場所の違い 定義方法以外で1点だけ違うのは、関数宣言はスコープ内のどこで定義しても参照できるのに対し、関数式は定義後にしか利用できない。
無名関数
関数名がなく他の場所から呼び出す必要のない時に使われる。
引数に関数を指定する必要があり、その場以外では使わない処理について無名関数として書くのが一般的。sample.jsconst newAnimals = myAnimals.filter(function(animal){ return animal !== "ごり" })→fileterメソッドは引数に関数を指定しなければならない。
- 投稿日:2020-07-22T11:36:32+09:00
[HTML/CSS/Javascript]??ユーザー入力値をもとにわにさんをプールに放つハンズオン??[値取得/表示/DOM操作]
目的
- ユーザーの入力値取得や表示ができるようになる。
今日やること
- getElementByIdやaddEventListenerやinnerHTMLについて理解を深める
- CSSでマウスホバーやセレクタを正しく選択できるようにする。
ゴールイメージ
テキストエリアに自然数をいれるとわにさんがプールに放たれる。
HTML
このような感じでつくってみました。
index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>わにわにわにさん</title> <link rel="stylesheet" href="gators.css"> </head> <body> <form action="javascript:void(0)" method="post"> <p>わにさん何匹放つ?</p> <input type="text" id="number"> <input id = "id-submit" type="submit" value="Unleash gators"> </form> <div> <p id ="gatorPool" ></p> </div> <script src="gators.js"></script> </body> </html>画面が一瞬消えるんだけど?(初見ホイホイ案件)
下記のようなコードの場合、submitした瞬間にaction="#"が呼ばれてしまう。
もう少し詳しくいうと、#はそのページの最上部を意味しているので、
index.htmlがリロードされ、その結果一瞬表示されるが最終的にはリフレッシュされた画面が表示される。index.html... <form action="#" method="post"> <p>わにさん何匹放つ?</p> <input type="text" id="number"> <input id = "id-submit" type="submit" value="Unleash gators"> </form> ...解決① action="javascript:void(0)"とする
- void 演算子は式を評価し、評価した結果必ず undefind 値を返します。
- action値に undefind 値を指定した場合には何も動作しません。
解決② event.preventDefault()とする
ブラウザが持つデフォルトの動作をキャンセルします。
- フォーム送信して送信先のページに遷移する動作がキャンセルされます。
でも、ブラウザのデフォルトの動作をキャンセルするなら逆に面倒かもしれない。
この辺りの使い分けはまだわかっていません。ごめんなさい。Javascript
このような感じでつくってみました。
gatorPool.jslet sub = document.getElementById('id-submit') sub.addEventListener("click", () => { const numberOfGators = document.getElementById('number').value; if (isNaN(numberOfGators)) { alert('数字のみを入力してください'); } if(numberOfGators<=0 || Number.isInteger(numberOfGators)){ alert('自然数で入力してください') } let addGators = ""; for (let i = 0; i < numberOfGators; i++) { addGators += '?'; } document.getElementById("gatorPool").innerHTML = addGators })にクリックイベントが発生した際の動作を作り、その中の処理を書いていきます。
1:イベント発火させる記述を作るために、
<input id="submit"...>
を取得
2-1:イベント発火時の処理を書く:ユーザーの入力値を取得
2-2:イベント発火時の処理を書く:値を表示
2-3:イベント発火時の処理を書く:ユーザーに正しい入力値を促すユーザーのイベント発火時の操作を作る。
イベント発火はこちらを使います。
element.addEventListener(eventhadle,callback)addEventListenerメソッドは、特定のイベントが対象に配信されるたびに呼び出される関数を設定します。 対象としてよくあるものは Element, Document, Window ですが、イベントに対応したあらゆるオブジェクトが対象になることができます (XMLHttpRequestなど)。引用:MDN
また、element:イベント発火するHTML要素を取得します。
eventhandle:どのイベント(ユーザーがWEB上でのクリックなどの操作)をしたときに反応するかを決めます。イベントハンドラの一覧はこちらを参考にしてみてください。イベント発火するHTML要素を取得
イベントは
<input id="submit"...>
で起こるので、発火させるためにHTMLを取得します。
HTMLの取得は、document.getElementById("")を使います。Document の getElementById() メソッドは、id プロパティが指定された文字列に一致する要素を表す Element オブジェクトを返します。要素の ID は指定されていれば固有であることが求められているため、特定の要素にすばやくアクセスするには便利な方法です。引用:MDN
gatorPool.jslet sub = document.getElementById('id-submit')このsubを使って、クリックイベントで発火する処理を書きます。
gatorPool.jslet sub = document.getElementById('id-submit') sub.addEventListener("click", () => { //この中に、clickしたときの処理を書く }document.getElementByIdのNullの対処
解決① ID名は間違っていないか
指定されたIDが見つからなかった場合、Nullを返します。
解決② HTMLを見た時に、Jsファイルで扱うHTMLの前に、jsファイルを呼び込んでいないか
JSファイルは置かれる前までのHTML要素を探すので、
指定したIDが<script src="gatorsPool.js"></script>
の後ろにある場合、Nullを返します。OK<input id = "submit"> ... <script src="gatorsPool.js"></script> //subは<input id = "submit">NG<script src="gatorsPool.js"></script> //subがNull ... <input id = "submit">イベント発火時の処理を書く
ユーザー入力値の取得
クリックで発火する関数は作れたので、発火時の処理を書いていきましょう。
ユーザー入力値を取得するには、こちらを使います。
document.getElementById('id').valuegatorPool.jslet sub = document.getElementById('id-submit') sub.addEventListener("click", () => { const numberOfGators = document.getElementById('number').value; }入力値によってわにを放つ数を変更したいので、入力値に応じたわにの文字列を作ります。
gatorPool.jslet sub = document.getElementById('id-submit') sub.addEventListener("click", () => { const numberOfGators = document.getElementById('number').value; let addGators = ""; for (let i = 0; i < numberOfGators; i++) { addGators += '?'; } }値を出力する
値をHTMLに出力するには、こちらを使います。
element.innerHTML
こちらもgetElementByIdと同様に、書き換えたいHTML要素が必要なので取得して貼り付けます。
今回は、subのように分割せずに、document.getElementByIdで取得したHTML要素をinnerHTMLに渡しています。gatorPool.jslet sub = document.getElementById('id-submit') sub.addEventListener("click", () => { const numberOfGators = document.getElementById('number').value; let addGators = ""; for (let i = 0; i < numberOfGators; i++) { addGators += '?'; } document.getElementById("gatorPool").innerHTML = addGators })ユーザに正しい入力を促す
残りは、正しい入力を促すための条件分岐を記載します。
0以下、英文字は弾けますが、小数点に対応できていません?♂️gatorPool.jslet sub = document.getElementById('id-submit') sub.addEventListener("click", () => { const numberOfGators = document.getElementById('number').value; if (isNaN(numberOfGators)) { alert('数字のみを入力してください'); } if(numberOfGators<=0 || Number.isInteger(numberOfGators)){ alert('自然数で入力してください') } let addGators = ""; for (let i = 0; i < numberOfGators; i++) { addGators += '?'; } document.getElementById("gatorPool").innerHTML = addGators })おつかれさまです!
Javascriptはこちらで終了です。CSS
マウスを置いた際に表示を変えたい場合、
:hover
を使います。gatorPool.cssform { /* display: flex; */ font-family:"游ゴシック","YuGothic"; margin: 0 auto; background-color:#6FB98F; width: 200px; height:200px; text-align: center; border-radius: 4px; } p{ padding:25px; } input{ font-family:"游ゴシック","YuGothic"; background-color:seashell; margin: 10px 0; border-bottom: solid 4px salmon; border-right: solid 4px sandybrown; border-radius: 4px; } input#id-submit:hover{ font-family:"游ゴシック","YuGothic"; background-color:snow; border-right:none; border-bottom: none; transform: translate3d(0, 3px, 0); border-radius: 4px; } div { margin: 0 auto; background-color:cornflowerblue; width: 300px; border-radius: 4px; }Tips 背景色を角丸にしてフォントを変えるとそれだけで画面にまとまりがでる気がします。
背景色角丸、フォント変更後
- 投稿日:2020-07-22T10:02:54+09:00
ジェイソン・ステイサムで妄想するのが日課になっていたので、いっそBOTにしてみた。
ジェイソン・ステイサムとは?
イギリス出身のハリウッド俳優です。主にアクション映画に出演していて、代表作に「ワイルドスピード」シリーズ、「トランスポーター」シリーズなどがあります。スタントマンを使わず、自身でアクションシーンを演じることがほとんど。鍛えぬいた体が素晴らしいです。。。
(参照:https://ja.wikipedia.org/wiki/%E3%82%B8%E3%82%A7%E3%82%A4%E3%82%BD%E3%83%B3%E3%83%BB%E3%82%B9%E3%83%86%E3%82%A4%E3%82%B5%E3%83%A0 )なぜジェイソン・ステイサムのBOTを作るのか
ステイサムを好きになって10年ほど経ちました。ワイルドな顔とマッチョなボディはもちろんですが、彼の声と演技、そしてストイックなプロ意識が大好きなのです。近年その想いが加速し、やる気を出したいとき、疲れて癒されたいとき、キュンキュンしたいときなどに、ステイサムの画像を検索して、妄想するのが日課になってしまいました。
どうせなら、そんな妄想を具現化して、さらなる高みを目指したいと思い、今回LINE BOTを作ることにしました。完成デモ
(私の中のステイサムたんは、ちょっとSです。)
環境
Visual Studio Code v1.47.0
node v14.5.0
@line/bot-sdk作り方
LINE BOTアカウントを作成
「1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest」
https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d▲こちらの記事を参考に、まずLINE BOTアカウントを作成
(きゃぁ、もうこれだけでもカッコイイ!)
Node.jsでプロジェクト作成~トンネリング
先ほどの記事をまた参考にして、ngrokでトンネリングするところまで行いました!
ジェイソン・ステイサムの画像を取得する
色んなステイサムに会いたいとなると、大量の画像が必要となります。
今回のLINE BOTで一番重要なポイントと言っても過言ではないでしょう。「Google Chrome検索結果画像をコマンド使わず一括でダウンロードした話」
https://qiita.com/ka0ru19/items/64bfee2c7e904b9524d2▲こちらの記事を参考に、ステイサムの画像をGoogleの画像検索から一括ダウンロードしました。
ファイル形式がJFIFになっていたのですが、LINE BOTではjpgのみしか使えないので、
コマンドプロンプトで一括でファイル形式を変換
(参考:Windows10 拡張子を一括で変更する方法 https://petitcc.exblog.jp/26140363/ )
キレイに全部jpgになりました!画像URLも必要なので、はてなさんのサービスを使って、URLを作成しました。
https://f.hatena.ne.jp/ステイサムとの妄想をひたすら打ち込む
私が話しかけたら、画像とテキストで理想の答えが返ってくるように、
とにかく思いつくレパートリーをif文で打ち込みました。if(event.message.text.match('おはよう')) { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184554.jpg', previewImageUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184554.jpg', }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: 'おはよう。ラジオ体操の時間だ。', }) //テキスト } if(event.message.text === '痩せたい') { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081414.jpg', previewImageUrl:'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081414.jpg' }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: '一緒に筋トレだ' }) //テキスト } if(event.message.text === 'もうやだ') { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184654.jpg', previewImageUrl:'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184654.jpg' }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: '弱音を吐くな。死にたいのか?' }) //テキスト }はぁ・・・最高すぎますね。
おわりに
本当はAPI連携して、Wikiから情報引っ張ってきたり、
「本日のステイサム占い」でランダムに画像が出てくる仕様とかも試みたりしたのですが、うまくいかず…。
7月26日がステイサムの誕生日なので、それまでに完成できたらいいなと思っています。最後にとりあえず今日までに作れたところまでコード全文載せておきます!
私利私欲に満ちた記事でしたが、最後までご覧いただきありがとうございました!!!サンプルコード全文
"use strict"; const express = require("express"); const line = require("@line/bot-sdk"); const PORT = process.env.PORT || 3000; const config = { channelSecret: "チャンネルシークレットをコピペ", channelAccessToken: "アクセストークンをコピペ", }; const app = express(); app.get("/", (req, res) => res.send("Hello LINE BOT!(GET)")); //ブラウザ確認用(無くても問題ない) app.post("/webhook", line.middleware(config), (req, res) => { console.log(req.body.events); //ここのif分はdeveloper consoleの'接続確認'用なので削除して問題ないです。 if ( req.body.events[0].replyToken === "00000000000000000000000000000000" && req.body.events[1].replyToken === "ffffffffffffffffffffffffffffffff" ) { res.send("Hello LINE BOT!(POST)"); console.log("疎通確認用"); return; } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result) ); }); const client = new line.Client(config); async function handleEvent(event) { if (event.type !== "message" || event.message.type !== "text") { return Promise.resolve(null); } if(event.message.text.match('おはよう')) { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184554.jpg', previewImageUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184554.jpg', }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: 'おはよう。ラジオ体操の時間だ。', }) //テキスト } if(event.message.text === '痩せたい') { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081414.jpg', previewImageUrl:'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081414.jpg' }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: '一緒に筋トレだ' }) //テキスト } if(event.message.text === 'もうやだ') { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184654.jpg', previewImageUrl:'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184654.jpg' }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: '弱音を吐くな。死にたいのか?' }) //テキスト } if(event.message.text.match('かっこいい')) { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081123.jpg', previewImageUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081123.jpg', }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: 'ほう。ご褒美の上裸だ。', }) //テキスト } if(event.message.text === 'トランスポーター') { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184550.jpg', previewImageUrl:'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184550.jpg' }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: 'ルール1 契約厳守\nルール2 名前は聞かない\nルール3 荷物は開けない' }) //テキスト } if(event.message.text === 'メカニック') { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721190805.jpg', previewImageUrl:'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721190805.jpg' }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: 'Victory Loves Preparation\n周到な準備が勝利を招く' }) //テキスト } if(event.message.text.match('うしろ')) { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081128.jpg', previewImageUrl:'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081128.jpg' }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: 'え?' }) //テキスト } if(event.message.text.match('好き')) { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081635.jpg', previewImageUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721081635.jpg', }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: '彼女いるんで', }) //テキスト } if(event.message.text.match('愛してる')) { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184638.jpg', previewImageUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184638.jpg', }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: '子供いるんで', }) //テキスト } if(event.message.text.match('罵って')) { await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184853.jpg', previewImageUrl: 'https://cdn-ak.f.st-hatena.com/images/fotolife/u/unias_tawa/20200721/20200721184853.jpg', }); //画像 return client.replyMessage(event.replyToken, { type: 'text', text: 'うわ~なんてだらしない体してんだ、10キロ泳いでこい', }) //テキスト } } app.listen(PORT); console.log(`Server running at ${PORT}`);
- 投稿日:2020-07-22T08:53:40+09:00
Koa.js ミドルウェアの作り方
Koa ミドルウェアの作り方。
前回、Koaのミドルウェアは積み重ねる事ができる関数だと言うことを説明しました。
今回は具体的にミドルウェアを作ってみます。ちょっと面倒くさくなったので、Typescriptで書く事にしました。
環境構築
必要なもののインストール
//適当なディレクトリを作成に移動 $ mkdir koa-sample $ cd koa-sample $ npm init -y //koa to インストール $ npm i koa // typescipt typescript // ts-node テスト用 // @types/koa koa用の型定義 $ npm i -D typescript ts-node @types/koa // .tsconfig を作成 $ npx tsc -inittsconfig.json の編集
allowSyntheticDefaultImports のコメントアウトを外しておいてください。
(Nodeのバージョンによっては不要かもしれない。tsconfig.json{ -//allowSyntheticDefaultImports : true +allowSyntheticDefaultImports : true }とりあえず サーバーを作成して、ts-nodeで起動できることを確認します。
app.tsimport Koa from "koa" type Context = {} type State = {} const app = new Koa<Context,State>() app.use( async(ctx,next) => { ctx.body = "koa app." } ) app.listen(3000)//サーバの起動 $ npx ts-node app.ts
ブラウザで http://localhost:3000 にアクセスした際に koa app. と表示されれば成功です。
確認できたらCTL+B等でサーバを終了してください。ここから本題 ミドルウェアと作ってみる
ルーティング仕様
今回は単純なルーティングを作りながら説明します。今回の仕様はこんな感じでGETのみでURLによって出力する内容を変更するAPIとします。
メソッド パス レスポンス GET /name {name:"headphone"} GET /price {price:13000} GET /brand {brand:"M.I.C"} ※ 実際にはルーティングkoa-route/koa-router等のライブラリを使うことが多いので実用的なものでは無いです。
ミドルウェア作成考え方
今回のミドルウェア は 以下の事を実現すれば完成するはずです。
- リクエストされたURL(パス)を取得する
- リクエストされたHTTP Method を取得する
- 上2つが一致する場合レスポンスを返す。
ctx には リクエスト/レスポンスがうまくラップされており、パス及びメソッドは簡単に取得する事ができます。よく使われるものは ctxのプロパティとして取得できるようになっていることが多いです。
method や url についてはドンズバなプロパティが用意されています。また ctx.bodyに値をセットすることでクライアントへのレスポンスとなります。
Context API
https://github.com/koajs/koa/blob/master/docs/api/context.mdifを使って愚直に書いてみます。
import Koa from "koa" const app = new Koa() app.use( async (ctx,next) => { const url = ctx.url const method = ctx.method if(method === "GET" && url === "/name"){ ctx.body = {name:"headphone"} return } if(method === "GET" && url === "/price"){ ctx.body = {price:13000} return } if(method === "GET" && url === "/brand"){ ctx.body = {brand:"M.I.C"} return } } ) app.listen(3000)再度サーバを起動して作成したエンドポイント3つを開いてみましょう。
//サーバの起動 $ npx ts-node app.ts
うまく表示されたでしょうか?
表示された場合、特に指定をしていないにも関わらず Content-Type が自動で設定されていることに
気づくかもしれません。ctx.bodyにオブジェクトを指定した場合は自動的にJSONと判断されます。便利ですね。これでKoaのミドルウェアデビューを果たす事が出来ました。(パチパチ
実際これで何の問題も無いのですが、もう少しKoaのミドルウェアらしく書いてみたいと思います。
例えば今回の場合もう一つの引数nextを用いていませんよね、これを利用して各レスポンスを独立したミドルウェアとして書くことができます。
今回のようなルーティングの場合『自分が処理出来ないリクエストだったら、次のミドルウェアに任せる』イメージで書き換える事ができます。
import Koa from "koa" const app = new Koa() app.use( async (ctx,next) => { const url = ctx.url const method = ctx.method if(method === "GET" && url === "/name"){ ctx.body = {name:"headphone"} return } next(); }) app.use( async(ctx,next) => { const url = ctx.url const method = ctx.method if(method === "GET" && url === "/brand"){ ctx.body = {brand:"M.I.C"} return } next() }) app.use( async(ctx,next) => { const url = ctx.url const method = ctx.method if(method === "GET" && url === "/price"){ ctx.body = {price:13000} return } next() }) app.listen(3000)起動して確認してみます、if文を使ったミドルウェアと変わらないレスポンスが得られることがわかると思います。
//サーバの起動 $ npx ts-node app.ts
このように各ミドルウェアを薄く書いていくと独立性が高くKoaのミドルウェアぽい書き方になります。
とはいえ、単純にコードが増えてしまったのでこういった場合は高階関数やクラスの出番です。
今回の例では method と path で レスポンスが変わる点が共通していますので、ミドルウェアを作成する高階関数(createRoute)を作成してみます。import Koa , {Middleware} from "koa" const app = new Koa() const createRoute = (targetMethod : string,targetPath : string, response:any) : Middleware => async (ctx,next) => { const { url ,method } = ctx if(targetMethod === method && targetPath === method){ ctx.body = response } next() } app.use(createRoute("GET","/name",{name:"headphone"})) app.use(createRoute("GET","/price",{price:13000})) app.use(createRoute("GET","/brand",{brand:"M.I.C"})) app.listen(3000)大分スッキリ書くことができるようになりました。高階関数で今回は書きましたが、ミドルウェアのルールは単純なので classを使うことも出来ます。
まだ 少し app.use の部分が冗長に感じますよね、そういった場合はkoa-composeライブラリを利用して複数のミドルウェアから、新しいミドルウェアを作る事が出来ます。
import Koa , {Middleware} from "koa" import compose from "koa-compose" const app = new Koa() const createRoute = (targetMethod : string,targetPath : string, response:any) : Middleware => async (ctx,next) => { const { url ,method } = ctx if(targetMethod === method && targetPath === url){ ctx.body = response } next() } const route = compose([ createRoute("GET","/name",{name:"headphone"}), createRoute("GET","/price",{price:13000}), createRoute("GET","/brand",{brand:"M.I.C"}) ]) app.use(route) app.listen(3000)Classでも書いてみる
import Koa , {Middleware} from "koa" import compose from "koa-compose" const app = new Koa() class Route { routes : Middleware[] = [] get(targetPath:string,response:any){ this.routes.push(async (ctx,next) => { const url = ctx.url if(targetPath === url){ ctx.body = response } next() }) } use(){ return compose(this.routes) } } const router = new Route() router.get("/name",{name:"headphone"}) router.get("/price",{price:13000}) router.get("/brand",{brand:"M.I.C"}) app.use(router.use()) app.listen(3000)レスポンスにJSONしか書き出せないのがナンセンスですが、よく見るルーターっぽい書き味に仕上がりました。koa-composeはミドルウェアを別ファイルでまとめたい場合等にすごく重宝します。
まとめ
手続き型っぽい書き方から、少しずつつkoaらしい書き方へ変化していく感じをなんとなく翻訳調でお送りしました。
- ミドルウェアを手続き型っぽく書くこともできる
- next を使うと 各ミドルウェアを薄くできる事がある
- koa-compose で ミドルウェアをまとめられる
- 高階関数が便利
- 投稿日:2020-07-22T07:40:40+09:00
jsでパイプライン演算子もどき7種
javascriptでパイプライン演算子を使いたい
こういうやつ。
text = '{"0": "a", "1": "b"}'; text |> JSON.parse |> Object.entries |> (a=>new Map(a)) |> console.log;// これ // console.log(new Map(Object.entries(JSON.parse(json)))); と同等まだステージ1で使えないので、代替を考えてみる。
関数で実装
まずは素直に、初期値と関数を渡すと返り値を返すだけの関数を実装してみる。
define.jsconst chain = (start, ...fns)=>{ let result = start for (const fn of fns) { result = fn(result) } return result }使ってみる。
how-to-use.jsconst result = chain(text, JSON.parse, Object.entries, a=>new Map(a) ) console.log(result)シンプルで見通しがいい。もうこれでいいんじゃないかな。
メソッドチェーン(もどき?)
せっかくなので、別の方法でも実装してみる。
define.jsconst start = (val)=>{ return { chain(fn) {return start(fn(val))}, end() {return val} } }how-to-use.jsconst result = start(text) .chain(JSON.parse) .chain(Object.entries) .chain(a=>new Map(a)) .end() console.log(result)これはこれで、分かりやすくて良いと思う。
prototype拡張
直接メソッドチェーンしてみよう。
define.jsObject.defineProperty(Object.prototype, '_chain', { configurable: false, enumerable: false, value: function(fn){return fn(this)} });how-to-use.jsconst result = text ._chain(JSON.parse) ._chain(Object.entries) ._chain(a=>new Map(a)) console.log(result)Objectのprototypeを直接拡張するのは弊害が大きすぎるので、絶対やめましょう。prototype拡張を行うライブラリを読み込んでいる場合、名前がバッティングした時に死にます。
prototype拡張 その2
symbolを使う方法。
define.jsconst chain = Symbol('chain'); Object.defineProperty(Object.prototype, chain, { configurable: false, enumerable: false, value: function(fn){return fn(this)} });how-to-use.jsconst result = text [chain](JSON.parse) [chain](Object.entries) [chain](a=>new Map(a)) console.log(result)symbolを用いてprototype拡張すれば、ライブラリ等とバッティングせずに済むため安全安心。
但し、使いやすくは…ない。Array.prototype.map()使用
一旦配列に入れると、配列操作系の関数を使うことができる。
how-to-use.jsconst result = [text] .map(JSON.parse) .map(Object.entries) .map(a=>new Map(a)) [0] console.log(result)配列から取り出すために、最後に[0]を付ける必要がある。いつか書き忘れそうで怖い。
Array.prototype.reduce()使用
reduceも使える。今度は関数の方が配列に入っているので注意。
how-to-use.jsconst result = [ JSON.parse, Object.entries, a=>new Map(a) ].reduce((val, fn)=>fn(val), text) console.log(result)そろそろパイプライン演算子とは程遠くなってきている。
Promise使用
Promiseのthen()の引数には関数を渡している。この仕様を、関数呼び出しのためだけに利用してみる。
how-to-use.js//返り値を使用しない場合 new Promise(r=>r(text)) .then(JSON.parse) .then(Object.entries) .then(a=>new Map(a)) //返り値を受け取る場合 (async function(){ const result = await new Promise(r=>r(text)) .then(JSON.parse) .then(Object.entries) .then(a=>new Map(a)) console.log(result) })()返り値を使いたいときに.then()で繋げるか、async関数内でawaitする必要がある。
非同期処理と関係ないところにPromiseをいきなり使ったら混乱しそうだなとは思う。
- 投稿日:2020-07-22T05:33:27+09:00
依存パッケージの依存パッケージのバージョンを変えたい時[package.json resolutions]
困った
精力的に更新がされているような、発展途上のパッケージなんかを使っていると、たまにこういうことが起きる。
- 意気揚々とYYYというイカしたパッケージをインストールし、引くほどイケイケの機能を実装する。
- 「あれ? なんか動かない」
- エラー文でググる。
- Issueを読むと、XXXというパッケージの、すでに修正されているバグらしい。
- package.jsonを確認すると、そもそもそのパッケージが入ってない、もしくは、修正後のバージョンが既に入っている。
- 詰む。
Why Japanese people!?
この場合、yarn.lockなどを確認すると、YYYのパッケージの依存パッケージとして、XXXの古いバージョンが指定されていることがある。
(指定されていないときは、この記事とは関係ないかも)
ここで役に立つのが、resolutionsである。
resolutionsは、各々のパッケージ内で指定されているバージョンを無視して、プロジェクト全体に指定したパッケージのバージョンを強制する機能である。resolutionsで解決を試みる
YYYというパッケージの中で、XXXの1.0.0が使用されているが、発生しているバグがXXX1.0.1で治っているとき。
package.json{ "dependencies": { "YYY": "^1.0.0" }, "resolutions": { "XXX": "^1.0.1" } }これで問題は解決される……はずだ。
あくまでもYYY内のXXXの依存バージョンが更新されるまでの応急処置なので、YYYに更新が入ったら外そう。
注意すべきは、これによって別の問題が発生しないとも限らないことだろう。
- 投稿日:2020-07-22T04:40:24+09:00
JavaScript で数字を漢数字に変換する例
111011101110
を"一一一〇一一一〇一一一〇"
ではなく"千百十億千百十万千百十"
とするタイプです。toKanjiNumber.js
const kanjiNumeral = [ // これ以上は Number.MAX_SAFE_INTEGER を超えるため考慮しない。 [1000000000000, "兆"], [100000000, "億"], [10000, "万"], [1000, "千"], [100, "百"], [10, "十"], ]; const kanjiDigits = ["", "一", "二", "三", "四", "五", "六", "七", "八", "九"]; const kanjiRecursive = (value) => { let current = value; let str = ""; kanjiNumeral.forEach(([base, char]) => { const num = Math.floor(current / base); if (!num) return; let recurredStr = kanjiRecursive(num); if ("千百十".includes(char) && recurredStr.endsWith("一")) { recurredStr = str.substring(0, recurredStr.length - 1); } current -= num * base; str = `${str}${recurredStr}${char}`; }); return `${str}${kanjiDigits[current]}`; }; const toKanjiNumber = (value) => { let num = Number(value); if (num === 0) return "〇"; let positive = true; if (num < 0) { positive = false; num = Math.abs(num); } const str = kanjiRecursive(num); return positive ? str : `-${str}`; }; export default toKanjiNumber;