- 投稿日:2020-12-18T23:57:53+09:00
HTML/Canvasでオラオラオラオラ無駄無駄無駄無駄のフキダシを作る
この記事は Web グラフィックス Advent Calendar 2020 の18日目の記事です。
はじめに
筆者は趣味でベクター系のお絵描きツールを作っています。いずれ文字入れやフキダシの機能を入れたいと思っていまして、今回の記事ではCanvasで文字とフキダシの描画を実装してみたものを紹介します。
やっていること
あまり理論的に高度な内容はありませんが、以下のようになります。
今回はあまり意味はありませんが、こうすることで後の色々な処理がやりやすくなります。もっと複雑な形状に対応するには必要になることもあるはずです。
下のような感じでa、b1、b2をランダムに決めてベジエ曲線を計算するという処理をストロークに対して時計回りで繰り返しています。b2を次の繰り返しのb1として使うことで、連続した波線になります。
④文字の厳密な寸法の計測
canvasにはgetImageDataというメソッドがあり、canvasのピクセルデータを取得できます。これを利用して、実際に描画されるフォントそのもののサイズを計測します。
measureTextというメソッドもあるのですが、できるだけ正確な寸法を取るためにこうしてみました。⑤文字の描画
ひたすら描画します。実は原作ではやってないみたいですが、フキダシをはみ出す表現もやってみました。
context.globalCompositeOperation = 'source-atop'globalCompositeOperationでアルファ値が無い部分の描画がされないようにしています。
他のフキダシと被ると描画されてしまうのが問題ですけど・・・動くもの
See the Pen OraMudaFukidashi by 柏崎ワロタロ (@warotarock) on CodePen.
終わりに
今回はCanvasでフキダシを描画してみました。ルビを入れたところがちょっとこだわりでした!
それでは皆様よいお年を・・・(二回目)
- 投稿日:2020-12-18T23:31:44+09:00
デイトラ Web制作コース初級編 DAY1〜9
DAY1〜9ではHTML、CSSの基礎・コーディングの練習を行いました。
重要だと思った部分を整理していきたいと思います。上手な質問の仕方
コーディングの話ではないですが、とても重要な部分でした。
自己解決能力
正しく情報を伝えることにより手助けをしてもらい素早く解決する能力がエンジニアにはとても重要です。
どの職業でもそうですが、質問をすることは他人の時間を奪うことなので、まず自分で調べてそれでも分からない場合は問題を分かりやすく伝え、素早い解決に繋げることが双方にとって大切です。
だからと言って分からないことは分からないですし、質問することにより質問された側の勉強になることもあるので上手な質問の仕方を今のうちに身につけることが重要です。
デイトラでの質問点は今のところ過去の回答を見ることによって解決できています。
marginとpadding
marginは外側の余白
paddingは内側の余白なのは分かりますが、使い分けについてはまだ難しい部分が多いので
その都度使用する理由を考えていきます。VS CodeのLive Server
デイトラをはじめる前もVS Codeを少し使っていましたが、
Live ServerというHTMLやCSSファイルの更新をライブプレビューできるようにするプラグインは初めて知りました。ファイルを編集すると同時に変更が反映するのでとても楽です!
Emmet
Emmetについてはデイトラをはじめる前も知っていましたが、そこまで重要なものだという認識はしていませんでした。
ですがコーディング速度を上げる、タイプミスなどを減らす意味でもとても重要なので、はじめのうちから使用することが大切です。float
「floatさせたら親要素の高さが0になってズレる」
???です。float=浮く
ブロックの構成から浮いてしまう=下の要素が詰まってしまいます。
なのでCSSの共通部分に書いてクリアにします。☆
・擬似要素afterかbefore使う
・contentという空要素
・display:block
→clearをつけた要素の後にリセットされる
(afterはこの要素の終わりの直前に入る)(beforeはこの要素の始まった直後)
HTMLはfloatする部分の親要素のclass名に付け加える.clear::after{ content:""; clear: both; display: block; }<div class="floatする部分の親要素のclass名 clear">なかなか難しいですね笑
Flexbox
日本語対応!CSS Flexboxのチートシートを作ったので配布します
印刷して手元に置いて復習します。お問い合わせフォーム
<form action="移動するページのURL" method="POST"> <input type="email" name="email" id="email"> <button type="submit"></button> </form>・お問い合わせフォームはmethodがPOSTが一般的
・typeは表示されるテキストフィールドの種類を指定
・nameはPHPなどで入力された値を送信する際に必要
・idはCSSやJSでタグを指定する時に必要
・buttonのsubmitでサーバー側や別ページに送信することができるまとめ
コーディングする上で重要なことは
HTMLを書き上げてからCSSをあて調整するHTMLを書く際は
完成形をイメージ・スタイルの当て方を考える・レスポンシブも見越す設計図がなければ家は建てられないです。
この点を意識してコーディングすることが重要です。さいごに
まだはじめたばかりですが、
デイトラをスタートしてから以前の完全独学からの軌道修正がかなりできています。
また、勉強を継続する習慣がついてきました。
一人でコーディングは現状難しいので、どれだけできるようになるか今後が楽しみです!
- 投稿日:2020-12-18T22:43:50+09:00
【初心者向け】フォームページを作るときに気を付けるべきポイント3つ
どうも7noteです。文字の上揃え、下揃えが効かないときに確認する3つの事
vertical-align: middle;
を使って、文字や画像の上下の位置を上揃えにしたり、下揃えにしたりしたいのに上手く効かない!
そんな時に確認しておきたい3つのポイントをご紹介したいと思います。1.
vertical-align: center;
をブロック要素に効かせようとしているstyle.cssp { vertical-align: middle; /* これは効きません! */ }基本的に、インライン要素、ブロック要素(とtable-cell要素)にしか
vertical-align
は効きません
もし間違えてspanじゃなくpタグに書いていた!という場合はそれを直すだけで一発解決かも?2. 親要素のflexboxの影響を受けてる
style.cssp { display: flex; /* 親要素にflexboxが効いていると・・・ */ } p span { vertical-align: middle; /* これは効きません! */ }親要素で位置を調節しているので、優先順位が
display: flex;
が優先されてしまいvertical-align
が効かなくなります。
併用するときは注意が必要!!3. 揃えたい要素が浮いている
style.cssp span { position: absolute; /* position: fixed;等も */ float: left; /* floatも一緒に使っていると・・・ */ vertical-align: middle; /* これは効きません! */ }イメージとしては
position: absolute;
やfloat
は浮いている状態なので、浮いているものに対してvertical-align
を指定しても効きません。
こちらも注意しましょう。まとめ
初心者の方は結構ひっかかりやすい
vertical-align
の罠ですが、今回紹介したポイントさえしっかり抑えていれば難しいことはないと思います!
ただ忘れやすいので、「vertical-align
はブロック要素には効かない」っと3回くらい唱えて覚えてもいいかもしれません。おそまつ!
~ Qiitaで毎日投稿中!! ~
【初心者向け】WEB制作のちょいテク詰め合わせ
- 投稿日:2020-12-18T22:43:50+09:00
【初心者向け】文字の上揃え、下揃えが効かないときに確認する3つの事
どうも7noteです。文字の上揃え、下揃えが効かないときに確認する3つの事
vertical-align: middle;
を使って、文字や画像の上下の位置を上揃えにしたり、下揃えにしたりしたいのに上手く効かない!
そんな時に確認しておきたい3つのポイントをご紹介したいと思います。1.
vertical-align: center;
をブロック要素に効かせようとしているstyle.cssp { vertical-align: middle; /* これは効きません! */ }基本的に、インライン要素、ブロック要素(とtable-cell要素)にしか
vertical-align
は効きません
もし間違えてspanじゃなくpタグに書いていた!という場合はそれを直すだけで一発解決かも?2. 親要素のflexboxの影響を受けてる
style.cssp { display: flex; /* 親要素にflexboxが効いていると・・・ */ } p span { vertical-align: middle; /* これは効きません! */ }親要素で位置を調節しているので、優先順位が
display: flex;
が優先されてしまいvertical-align
が効かなくなります。
併用するときは注意が必要!!3. 揃えたい要素が浮いている
style.cssp span { position: absolute; /* position: fixed;等も */ float: left; /* floatも一緒に使っていると・・・ */ vertical-align: middle; /* これは効きません! */ }イメージとしては
position: absolute;
やfloat
は浮いている状態なので、浮いているものに対してvertical-align
を指定しても効きません。
こちらも注意しましょう。まとめ
初心者の方は結構ひっかかりやすい
vertical-align
の罠ですが、今回紹介したポイントさえしっかり抑えていれば難しいことはないと思います!
ただ忘れやすいので、「vertical-align
はブロック要素には効かない」っと3回くらい唱えて覚えてもいいかもしれません。おそまつ!
~ Qiitaで毎日投稿中!! ~
【初心者向け】WEB制作のちょいテク詰め合わせ
- 投稿日:2020-12-18T21:42:27+09:00
20代後半女性 Web制作・デザインで転職を目指す
はじめに
はじめまして。
早速ですが、この記事は
20代後半 女性 未経験 地方 働きながら
Web制作・デザインを学びエンジニア転職を実現する ものとなっております!学習記録などを都度共有しますので、参考になる部分は活用していただけると嬉しいです。
この記事を書く前
2020年9月〜Progate
10月〜ドットインストール、模写開始模写に入ったところで、
「サイトの見た目は合わせることが出来るけど、答え合わせが難しい」
「どこまで何をやればいいの?」
「そもそもの基礎がよく分かっていない」
など効率を考えるあまり情報の精査に時間を取られしまっていました。デイトラ入会・本格的に勉強開始(2020年12月〜)
「独学では情報の精査などに時間が取られ効率的ではない」
「質問できる環境やコミュニティが欲しい」
そこで興味のあったデイトラWeb制作・デザインコースに入会しました。なのでここからはデイトラとともにフロントエンドエンジニア(デザイン含む)
転職を目指していきたいと思います!
- 投稿日:2020-12-18T19:32:40+09:00
【メモ】HTMLとJSでジャンケンする
はじめに
知り合いからプログラミングの課題の協力をして欲しいと言われたので自分なりに書いたコードの復習と説明の代わりになればと思い書きます。
用件①画面リロードするとランダムで自分の手とPCの手を決定し、どちらが勝ったのかを表示する
用件②ユーザーに自分の手を選ばせ、PCの手を決定し、どちらが勝ったのかを表示する
用件①
まず簡単に
index.html
を用意します。index.html<html> <body> <img id="you" src="" width="10%" /> <p id="you_text"></p> <img id="pc" src="" width="10%" /> <p id="pc_text"></p> <p id="result"></p> </body> </html>▼ 表示してみる
もちろんなにも表示されませんね。ここにscriptを書いていきます。
index.html<html> <body> <img id="you_img" src="" width="10%" /> <p id="you_text"></p> <img id="pc_img" src="" width="10%" /> <p id="pc_text"></p> <p id="result"></p> <script> //ぐー const rock = { img: "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/8b692708-7744-36b8-ed83-1ee22f4ce7f3.jpeg", text: "ぐー", }; //ちょき const scissors = { img: "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/698c6320-ef35-48c7-1f3e-3166ddbdff8a.jpeg", text: "ちょき", }; //ぱー const paper = { img: "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/675e2d7d-f599-d21d-e39b-417748fc0fb7.jpeg", text: "ぱー", }; //それぞれの手を一個の配列にする const Item = [rock, scissors, paper]; //それぞれのidと連携する const youImg = document.getElementById("you_img"); const youText = document.getElementById("you_text"); const pcImg = document.getElementById("pc_img"); const pcText = document.getElementById("pc_text"); const result = document.getElementById("result"); //画面がリロードされるとRockPaperScissors関数を叩く window.onload = RockPaperScissors; // ジャンケンをする関数 function RockPaperScissors() { // youの出すものを決める const youChose = Item[Math.floor(Math.random() * Item.length)]; //手の画像とテキストを設定する youImg.src = youChose.img; youText.textContent = "あなたが出したのは" + youChose.text + "です"; //pcの出すもの決める const pcChose = Item[Math.floor(Math.random() * Item.length)]; //手の画像とテキストを設定する pcImg.src = pcChose.img; pcText.textContent = "パソコンが出したのは" + pcChose.text + "です。"; let judge = ""; //ジャンケンは全てで9通り //そのうち引き分けになるのは3通り if (youChose.text === pcChose.text) { judge = "あいこです"; } //勝つ3通りを出す //ぐーで勝つ else if (youChose.text === "ぐー" && pcChose.text === "ちょき") { judge = "あなたの勝ちです"; } else if (youChose.text === "ちょき" && pcChose.text === "ぱー") { //ちょきで勝つ judge = "あなたの勝ちです"; } else if (youChose.text === "ぱー" && pcChose.text === "ぐー") { //ぱーで勝つ judge = "あなたの勝ちです"; } else { //あいこでも勝ちでもない場合 judge = "あなたのまけです"; } //結果を反映する result.textContent = judge; } </script> </body> </html>▼ 表示結果
See the Pen zYKwRjv by ようかん / Yosuke Inoue (@inoue2002) on CodePen.
無事用件通り表示出来ましたね。何度かリロードして、それぞれの出す手や判定が変わることを確認してみましょう。
用件②
先ほどと大まかには似ている実装になりますが、異なるところは
you
が出す部分を実際に決められるようにするという点です。
画面がリロードされたら、まずユーザに選択させるダイアログを表示させます。
ダイアログにはwindow.prompt
を使います。index.html<html> <head> </head> <body> <img id="you_img" src="" width="10%" /> <p id="you_text"></p> <img id="pc_img" src="" width="10%" /> <p id="pc_text"></p> <p id="result"></p> <script> const rock = { img: "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/8b692708-7744-36b8-ed83-1ee22f4ce7f3.jpeg", text: "ぐー", }; const scissors = { img: "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/698c6320-ef35-48c7-1f3e-3166ddbdff8a.jpeg", text: "ちょき", }; const paper = { img: "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/675e2d7d-f599-d21d-e39b-417748fc0fb7.jpeg", text: "ぱー", }; const Item = [rock, scissors, paper]; const youImg = document.getElementById("you_img"); const youText = document.getElementById("you_text"); const pcImg = document.getElementById("pc_img"); const pcText = document.getElementById("pc_text"); const result = document.getElementById("result"); //画面がリロードされたら window.onload = promptYou; function promptYou() { // 入力ダイアログを表示 + 入力内容を user に代入 user = window.prompt( "あなたの出す手を選んでください。 ぐー:1,ちょき:2,パー:3", "" ); let youChose = ""; //youchoseがぐー if (user == "1") { youChose = Item[0]; } //youchoseがちょき else if (user == "2") { youChose = Item[1]; } //youchoseがぱー else if (user == "3") { youChose = Item[2]; } youImg.src = youChose.img; youText.textContent = "あなたが出したのは" + youChose.text + "です"; //pcの手を決める RockPaperScissors(youChose.text); } function RockPaperScissors(youChoseText) { //pcの出すもの決める const pcChose = Item[Math.floor(Math.random() * Item.length)]; pcImg.src = pcChose.img; pcText.textContent = "パソコンが出したのは" + pcChose.text + "です。"; let judge = ""; //ジャンケンは全てで9通り //そのうち引き分けになるのは3通り if (youChoseText === pcChose.text) { judge = "あいこです"; } //勝つ3通りを出す //ぐーで勝つ else if (youChoseText === "ぐー" && pcChose.text === "ちょき") { judge = "あなたの勝ちです"; } else if (youChoseText === "ちょき" && pcChose.text === "ぱー") { //ちょきで勝つ judge = "あなたの勝ちです"; } else if (youChoseText === "ぱー" && pcChose.text === "ぐー") { //ぱーで勝つ judge = "あなたの勝ちです"; } else { //あいこでも勝ちでもない場合 judge = "あなたのまけです"; } //結果を反映する result.textContent = judge; } </script> </body> </html>
無事自分の手は選んで、PCもランダムに手を出して結果を表示するところまで完成しました。
See the Pen qBamygy by ようかん / Yosuke Inoue (@inoue2002) on CodePen.
最後に
時間もなかったのでさくっと作りましたが、もっと可愛いモーションとか
リトライボランとか追加して盛り上げてみたいですね。何かの参考になれば幸いです。
CodePenの方で実際に試すこともできるので是非試してみてください。
- 投稿日:2020-12-18T17:13:39+09:00
【MicroPython】ESP32で入力フォームを作成
はじめに
ESP32 MicroPythonでWi-FiアクセスポイントとWebサーバーを立ち上げ入力フォームページを作成してみました.
↓こんな感じ
前提条件
- ESP32-WROOM-32
- MicroPython v1.13
プログラム
main.pyimport socket import network import ssl import re ESSID = "ESP32" PASSWORD = "ESPWROOM32" IP = "192.168.5.1" def wifi(essid, pwd, ip, mask, gw, dns): ap = network.WLAN(network.AP_IF) ap.config(essid = essid, authmode = 3, password = pwd) ap.ifconfig((ip,mask,gw,dns)) # print("(ip,netmask,gw,dns)=" + str(ap.ifconfig())) ap.active(True) return ap wifi(ESSID, PASSWORD, IP, '255.255.255.0', IP, '8.8.8.8') def web_page(): html = """ <html lang = "ja"> <head> <title>test</title> <meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="data:,"> <style> </style> </head> <body> <h1>test page</h1> <h2>入力フォーム</h2> <form action = "" method = "GET"> <p><input type="text" name="test" size="40"></p> <p><input class="button" type="submit" value="送信"></p> </form> </body> </html> """ return html s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('', 80)) s.listen(5) while True: conn, addr = s.accept() response = web_page() conn.send('HTTP/1.1 200 OK\n') conn.send('Content-Type: text/html\n') conn.send('Connection: close\n\n') conn.sendall(response) request = str(conn.recv(1024)).lower() # print("request : "+ request) m = re.search(r'test=(\w+)', request) if m != None : word = m.group(0) print(word[5:]) conn.close()コード解説
def wifi(essid, pwd, ip, mask, gw, dns)
Wi-Fiアクセスポイントを立ち上げs.bind(('', 80))
HTTPポート80番で接続を待機m = re.search(r'test=(\w+)', request)
クライアントの入力データを取得word = m.group(0)
入力データを変数に入れる実行
スマホなどからESP32のアクセスポイントに接続しブラウザで
192.168.5.1
にアクセスしてください.おわりに
おわりです.
- 投稿日:2020-12-18T14:40:45+09:00
JQuery左右のslide
久しぶりにJQuery触りました。JQueryが優秀すぎて、自分がおバカになりそうだったのでしばらく触ってなかったのですが、とはいえ「楽に実装できるなぁ」って思いました。
正直将来性は感じませんがwまだまだlegacyな会社(悪い意味じゃ無いよ)では、使われいるライブラリーですし、何よりも初心者が一番とっつきやすいツールでもあります。
本題
やることは掲題のとおり、左右のSlideです。JQueryには
slideUp
,slideDwon
はあるのですが、左右のSlideがなかったと思います。(違っていたらすいません。)なので練習も含めてこの機能を実装してみました。是非参考にして下さい。
html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>slideX_JQuery</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Google Font --> <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet"> <!-- CSS --> <link rel="stylesheet" href="./css/style.css"> </head> <body> <!-- Left Image --> <div class="left-img" id="leftImg"><img src="./img/chick.png"></div> <!-- Right Nav --> <nav> <div class="right-nav navToggle">MENU</div> <div class="nav-close navToggle">×CLOSE</div> <ul> <li><a href="#">manu1</a></li> <li><a href="#">manu2</a></li> <li><a href="#">manu3</a></li> <li><a href="#">manu4</a></li> <li><a href="#">manu5</a></li> </ul> </nav> <!-- JS --> <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> <script src="./js/main.js"></script> </body> </html>htmlはすごくシンプルです。念の為、
head
部分まで載せときますので、階層さえ整えてくれれば、最悪コピペでいけます。css
@charset "UTF-8"; /* reset */ *{ margin: 0; padding: 0; box-sizing: border-box; font-family: 'Noto Sans JP', sans-serif; line-height: 1; } ul{ list-style: none; } a{ text-decoration: none; } /* Left Image */ .left-img{ transition: .3s; position: absolute; top: 50%; left: -60px; } .left-img.show{ left: 0; } /* Right Navigation */ nav{ height: 100%; /* 初期値は幅0 */ width: 0; background: beige; border-left: 2px solid #c9caca; /* 右上に常時配置させる */ position: fixed; top: 0; right: 0; transition: .3s; } nav.show{ /* showクラスが追加されると、幅200pxに上書きされる */ width: 200px; } .right-nav{ width: 30px; height: 100px; /* ボーダーは右側不要なので0を上書き */ border: 2px solid #c9caca; border-right: 0; border-radius: 5px 0 0 50%; /* テキスト縦書き */ writing-mode: vertical-rl; text-orientation: upright; text-align: center; vertical-align: middle; line-height: 30px; color: #e6b422; /* navの外側に相対配置。相対配置なので、navの挙動分は同時に動く。 */ position: relative; top: 10px; left: -30px; } .nav-close{ /* 初期値は非表示 */ display: none; padding: 0 15px 0 1px; border-bottom: 1px solid black; /* bodyの右上に絶対配置 */ position: absolute; top: 10px; right: 10px; } .nav-close.show{ /* navが開閉されると、CLOSEボタンが表示される。 */ display: block; } nav ul{ /* liがnavの画面外でスタンバッているため、ul外の要素は非表示。 */ overflow: hidden; } nav ul.show{ /* liの枠外からのアニメーションを実行したいため、hiddenを通常のvisibleに上書き。 */ overflow: visible; } nav li{ background: #e6b422; border-top: 1px solid #c9caca; /* 枠外からスライドインするため、初期値では左側に相対配置。 */ position: relative; top: 50px; right: 300px; transition: 1s; } nav li.show{ background: #f5f5dc; /* navが開閉されたと同時に、相対配置のまま、位置指定を全て0に戻す。 */ position: relative; top: 0; right: 0; } nav li:last-child{ border-bottom: 1px solid #c9caca; } nav a{ display: block; padding-left: 20px; line-height: 50px; color: #e6b422; transition: .3s; } nav a:hover{ background: #e6b422; color: white; }これも
Reset
まで載せときます。難しそうなところはコメント書いてるんで見て下さい。ポイントとしては
nav ul
のoverflow
の使い方でしょうか?
コメントにも書いていますが、li
が時間差で左側からSlideするような機能を実装しているため、ulの枠外にliが初期配置されています。このままだと枠外に置いてある
li
が丸見えになってしまうので、ul
のoverflow
で枠外の要素を全て非表示にしています。ただしすっと非表示だと今度は、
nav
メニュー開閉時のアニメーションが見えなくなってしまうため、開閉時のみhidden
から初期値のvisible
に上書きしています。それ以外は難しいことは無いかと思います。
JQuery
$(function(){ // 変数宣言(let宣言でもOKだが今回は再代入不要なのでconstが望ましい) const leftImg = $('#leftImg'); const navToggle = $('.navToggle'); const liTags = $('nav').find('li'); const showCls = 'show'; // Left Image leftImg.on('click', function(){ // クリックでshowクラスを追加。 $(this).toggleClass(showCls); }); // Right Navigation $.each(navToggle, function(i, btn){ // 開閉ボタン2つに対して、繰り返し処理。 $(btn).on('click', function(){ // 先にnavを開閉。CLOSEボタン表示。ulのoverflow解除。 $('nav').toggleClass(showCls); $('.nav-close').toggleClass(showCls); $('nav ul').toggleClass(showCls); // ulのoverflow解除後、liのアニメーション処理を実行。 $.each(liTags, function(i, li){ setTimeout(function(){ $(li).toggleClass(showCls); },200 * i); }); }); }); })ここも難しいことは無いです。
nav開閉時のアクションとして、先にnav
,nav-close
,nav ul
にそれぞれshowクラス
を追加し、その後にli
に対して処理を投げます。
li
の処理は以下の様な流れになっています。
・nav
内のli
を取得。それを配列化。
・$.each()
で配列のアイテム(li
1つ1つ)に対し、showクラス
を追加。
・showクラス
の追加は、setTimeout(func, n)
によって、nミリ秒ずつ処理を遅らせます。ざっくりとこんなところです。
最後に
今回はJQueryで書きましたが、時間があったら通常のjavascriptの記述に変換したものを載せたいと思います。
やっぱり今後の需要としては他のモダンJSもやっていくべきなので、コードの変換は凄く勉強になります。
特にjavascriptをやると、JQueryの裏側でどういった処理が流れてるのかを知れるので、ロジカルに何で動いているのかが何となく分かります。
今回は簡単ですが、この辺で終わりにします。お疲れ様でした。
****
- 投稿日:2020-12-18T11:58:24+09:00
【HTML Form】ポップアップウィンドウへ結果を表示させる場合の button タグ 注意点
発生した事象
検索結果をポップアップウィンドウに表示する際、Javascriptでポップアップウィンドウを作り、target をそのウィンドウに合わせるという常套手段を書いていたのですが、結果が呼び出し元のウィンドウにも同時に表示されるという状態になってしまった。具体的にはこんな感じです。
後ほど紹介する検証コードで、解決はみているのですが、なんでこんなシンプルな話が上手く行かないのかハマってしまいました。
検証環境
xampp (Windows) 7.2.34
Egde 87 , Chrome 87 , Firefox 84結論
- button タグを使う場合 formtarget 属性が必要
- input タグは特に属性を必要とせず想定通りの挙動となる
検証コード
abc.php<?php // フォームからPOSTで受け取った場合は、OK! というテキストだけを表示する。 if(isset($_POST['popup'])){ echo 'OK!'; exit; } ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>input VS button</title> <script> function result_pop() { var frmPost = document.getElementById('frmpost'); var printform = window.open('about:blank','pop','width=300,height=200'); frmpost.target = 'pop'; frmpost.submit(); printform.focus(); frmpost.target = ""; } </script> </head> <body> <!-- 以下 formタグ action のURL に #result とあるのは、製品のコードにそう書いてあったため。 書いてなくても挙動に変化はありませんでしたが、一応記述。 --> <form id="frmpost" name="frmpost" action="./abc.php#result" method="post"> <input type="hidden" id="popup" name="popup" value="dummy"> <input onClick="result_pop();" type="button" value=" [A] input tag. "> inputタグの利用<br> <button onClick="result_pop();"> [B] button tag only. </button> buttonタグの利用 属性なし<br> <button onClick="result_pop();" formtarget="pop"> [C] button tag and formtarget attr. </button> buttonタグ と formtarget 属性の利用. </form> </body> </html>結論にも書いたとおり、input タグ([A]のボタン)は、もともと想定したように、ポップアップウィンドウに結果が表示されます。
ハマったのは[B]のタイプでボタンを作っていたからでした。この時、冒頭のキャプチャ画面にあるように、同じ結果が呼び出し元画面、ポップアップ画面の2画面とも表示されてしまう。
buttonタグで実装する場合は[C]ボタンのように、formtarget 属性を付加し、ポップアップするウィンドウに付けたターゲット名を指定することで解決です。
上記のコードでは、"pop"がそれにあたります。以上
言い訳と反省
HTML5以前の古いコードをメンテしていながら、buttonタグを使って簡易検証してたのが良くなかった。検証はHTML5以前を意識しないとダメなのが反省と糧ですね。
- 投稿日:2020-12-18T10:19:16+09:00
Flexboxを用いた要素の並び
Flexboxを使うことで、要素を左右中央の並びを変更
index.html<div id="sample1" class="parent"> <div class="children">要素</div> </div> <div id="sample2" class="parent"> <div class="children">要素</div> </div> <div id="sample3" class="parent"> <div class="children">要素</div> </div>style.css.parent { padding: 10px; background-color: #9eefff; display: flex; } #sample1 { justify-content: flex-start; } #sample2 { justify-content: flex-end; } #sample3 { justify-content: center; }親要素のparentクラスにdisplay: flex;を付与
→ 並びを変更したい要素の親要素を「Flexbox化」することで、要素を柔軟に変更できるそして、id名毎に異なる並びの種類を設定
「sample1」には、
justify-content: flex-start;
→ 要素を左寄せ「sample2」には、
justify-content: flex-end;
→ 要素を右寄せ「sample3」には、
justify-content: center;
→ 要素を中央寄せ
- 投稿日:2020-12-18T00:53:34+09:00
Web Audio APIでリップシンク(もどき)を作ってみた話
初めまして。Qiita&アドベントカレンダー初投稿です。よろしくお願い致します
今回はWeb Audio API を利用してキャラクター(VOICEROID)に口パク(リップシンク)を実装したので、その実装過程を紹介したいと思います。
何でこんなもの作ったか
個人用で作っていたwebアプリにガイド的な存在としてキャラクターイラスト付きでVOICEROIDを登場させ、音声案内をしてもらっていました。
しかし、イラストなので当然VOICEROIDがしゃべっても、動きがないんですよね。特に口が。
そこで、どうにか音声に合わせて口を動かせないかと思い、実装に至りました。完成品
ともあれ、まずは完成品をご覧ください。
https://youtu.be/E6miOvn_ha4
どうですか?母音に合わせてやってるわけではないので、正確ではありませんが、僕は結構いい線行っていると思います。では実際にやったことを紹介します。
画像&声の準備
今回登場するVOICEROIDは琴葉葵ちゃんです。
使用イラストは使用用途が自由だったMtU様のを利用させて頂きます。
https://seiga.nicovideo.jp/seiga/im9664585イラストでどうやってリップシンクさせるかですが、
イラストを体レイヤー・顔レイヤー・口レイヤーに分けて独立した画像にします。
各レイヤーが重なるように画像を配置、音声に合わせて口レイヤーの画像を差し替えることで口が動いているように見せます。
加えて、顔と口を分けることでリップシンクを保ちつつ、顔全体の表情を差し替えられるようにします(今回は表情の差し替えはしていませんが...)。使用イラストはPSD形式(目、口、頬など各レイヤー分けされている画像形式)だったのでレイヤー事に画像を切り出しました。
声はVOICEROID2を利用して作成しました。わかる方向けですが、VOICEROIDの調声パラメータは以下になります。
どうやって声とリップシンクさせるか
精密にリップシンクするなら、母音に応じて口の形を変える方法が良さそうですが、母音の判別は難しく一筋縄ではいかなそうでした。(フォルマント?というものを使えば判別できそうでしたが、求め方がわかりませんでした。)
そのため、もっと単純なものにして、音の強度(スペクトル)が強ければ開口、弱ければ閉口する方式にします。
その強度を取得にはWeb Audio API
のAnalyserNode
を利用します。Web Audio API
今回の主役です。Webで音を再生する際は
<audio>
タグが一般的ですが、このAPIを利用することで音の再生のみならず、加工や解析が行えるようになります。
使い方や概念ははMDN docsに詳しく書かれています。簡単に説明すると
1.音声の入力ノードと加工ノード、出力ノードを定義する
2.入力ノード→加工ノード→出力ノードの順に接続すると音声が再生される
3.加工ノードの種類を変えることで音声に対して加工や解析を行えるようになる
という具合です。AnalyserNode
Web Audio APIの加工ノードの一つです。入力ノードと出力ノードの間に挟むことで高速フーリエ変換(FFT)を行い、音声の波形やスペクトル周波数を取得することができます。
リップシンク実装
ソースは長いのでgithubに上げました。全文はそちらをご覧ください。
https://github.com/Iroha71/character_lipsync4web以下、ポイントを抜粋します。
AnalyserNodeの準備
- index.html: 52行目~
/* 音声データをAudioBufferに変換 */ preparedBuffer = async (voice_path) => { ctx = new AudioContext() const res = await fetch(voice_path) const arrayBuffer = await res.arrayBuffer() const audioBuffer = await ctx.decodeAudioData(arrayBuffer) return audioBuffer } /* 入力ノードとAnalyserノードを生成し、出力層に接続 */ buildNodes = (audioBuffer) => { audioSrc = new AudioBufferSourceNode(ctx, { buffer: audioBuffer }) analyser = new AnalyserNode(ctx) analyser.fftSize = 512 audioSrc.connect(analyser).connect(ctx.destination) }AnalyserNodeを使うために
preparedBuffer
とbuildNodes
という関数を用意しました。
Web Audio APIに音声ファイルを使う場合はAudioBufferに変換する必要があるため、preparedBuffer
関数で音声ファイルをAudioBufferに変換しています。次に
buildNodes
関数で変換されたAudioBufferを入力するノード(AudioBufferSourceNode
)、音声解析に使うAnalyserNode
を生成します。AnalyserNodeにはfftSize
を指定します。
fftSizeは高速フーリエ変換を行う際の周波数領域の大きさで32, 64, 128, 256, 512, 1024, 2048のいづれかを指定します。値が大きいほどより細かく周波数や波形情報が情報が得られるようになります。音声解析→音の強度(スペクトル)の取得
- index.html: 97行目~
sampleInterval = setInterval(() => { let spectrums = new Uint8Array(analyser.fftSize) analyser.getByteFrequencyData(spectrums) syncLip(spectrums) }, 50)AnalyserNodeの
getByteFreqencyData
を使うことで引数に渡した変数(上記コードだとspectrums)に符号なし整数(Uint8)の配列であらわされたスペクトル情報が格納されます。
スペクトルはsetIntervalで50ms毎に取得するようにします。リップシンク
- index.html: 71行目~
syncLip = (spectrums) => { let totalSpec = 0 const totalSpectrum = spectrums.reduce(function(a, x) { return a + x }) if (totalSpectrum > prevSpec) { mouseElement.src = './mouse_open.png' } else { mouseElement.src = './mouse_close.png' } prevSpec = totalSpectrum }肝心のリップシンク処理です。AnalyserNodeによる音声解析で取得したスペクトル情報をもとにイラストの口レイヤーを開口、閉口画像に切り替えます。
開口/閉口の判定は前回サンプリングしたスペクトルの合計(prevSpec)と現在サンプリングしたスペクトルの合計(totalSpec)を比較し、前回よりスペクトルの合計が大きい(=音の強度が高い)時は開口、反対に小さい(=音の強度が弱くなった)時は閉口するようにします。もうひと手間加える
↑のリップシンク処理の結果はこのようになりました。
リップシンク自体はそれなりにできていますが、どうしてもパクパク感がでています。
そこで一工夫入れます。
先ほどは閉口 or 開口でしたが、その中間を加えます。
まず、中間の口の画像を用意します。開き具合の小さい画像にします。
そして、リップシンクの開口・閉口判定処理に中間の条件を追加します。
中間の条件は前回とのスペクトラムの差分がそこまで大きくない場合にします。syncLip = (spectrums) => { let totalSpec = 0 const totalSpectrum = spectrums.reduce(function(a, x) { return a + x }) if (totalSpectrum > prevSpec) { mouseElement.src = './mouse_open.png' } else if (prevSpec - totalSpectrum < 1000) { mouseElement.src = './mouse_open_light.png' } else { mouseElement.src = './mouse_close.png' } prevSpec = totalSpectrum }この条件で行った結果
こんな感じで冒頭の動画のような滑らかなリップシンクになりました。おわりに
- 初めてのqiita投稿の割に癖の強い記事になりましたが、面白い取り組みができました。
- Web Audio APIで音声解析までできるのは驚きでした。波形が取れるので例えばビジュアライザーみたいなのを作ってサイトの演出の一部に使うなんてことも面白そうです。(最近は自動再生が禁止になっているのでそのあたり考えないといけない部分はあるかもしれませんが。)
- スペクトラムを使うリップシンクもどきでしたが、言葉を発している・発していないに合わせる分には十分表現できていると思いました。とても満足です。いや、満足どころか感動しています。
長くなりましたが、以上です。ありがとうございました
参考文献