この人頭いいなと思ったエピソード

いつもお世話になっております。
jQueryでシンセサイザーのような鍵盤アプリを作ろうとしています。
スマホで画面をなぞった時にドレミの音が次々に鳴るようにするにはどうすれば良いですか。

例えば、次のような3個の要素に順に触れるようになぞった時には、ド、レ、ミ、と順に鳴るようにするにはどうすれば良いですか。
<style>div { border: 1px solid; padding: 1em }</style>
<div></div>
<div></div>
<div></div>

色々調べて、次のようなコードを書いたのですが、まだうまく動きません。
const key = $('div')
key.on('touchstart', function() {$(this).css('background', '#000')})
key.on('touchmove', function(e) {
const tc = e.originalEvent.touches[0], top = $(this).offset().top
if(tc.clientY < top || top + $(this).height() < tc.clientY) {
$(this).trigger('touchend')
$(document.elementFromPoint(tc.clientX, tc.clientY)).trigger('touchstart')
}
})
key.on('touchend', function() {$(this).css('background', '#fff')})

このコードは要素に触れるとその要素の色が変わり、離すと元の色に戻るようにしようとしています。
どうも標準の動きでは、タッチ点が次の要素上に来ても、指を離すまでは次の要素でのタッチイベントは発火しないようですので、touchmoveでタッチ点を監視し、タッチ点が次の要素に切り替わった時に、前の要素で強制的にtouchendを発火させ、次の要素で強制的にtouchstartを発火させています。
これにて、1個目の要素から2個目の要素に移った時に、意図した通りに色が変わりました。
しかし、そのまま指を離さずに3個目の要素までなぞった時には、意図した通りに色が変わりません。
この時、2個目の要素の色が消え、3個目の要素に色が付くようにしたいのですが、実際には2個目と3個目の両方の要素に色が付いてしまいます。

また、このコードでは、やむなく強制的にイベントを発火させるという暴挙に出ていますが、可能であればもっと単純な書き方をご教示いただきたく存じます。

A 回答 (5件)

No3です。



連投失礼。
スクリプト部はこちらの方が少しだけ簡略化できているかも・・?
(内容的には同じですけれど・・)

<script>
const
wrap = document.getElementById('wrap'),
divs = [...wrap.children],
handler = e => {
e.preventDefault();
const pos = [...e.touches].map( t =>
divs.indexOf(document.elementFromPoint(t.clientX, t.clientY))
);
divs.forEach((e, i) => { e.className = pos.includes(i)?'on':''; });
};

['touchstart', 'touchend', 'touchmove'].forEach( t => {
wrap.addEventListener(t, handler, {passive: false});
});
</script>
    • good
    • 0
この回答へのお礼

前のコードもそうですが、JavaScriptの組み込み関数だけで、ここまで簡単に書けるのですね。

ここまでjQueryで作ってきましたが、何だかjQueryを使うのが阿呆臭くなり、しばらく考え込んでしまいました。

うーん。。。

お礼日時:2024/08/01 08:06

No4です。



>ここまでjQueryで作ってきましたが、何だかjQueryを使うのが阿呆臭くなり~
かって、ブラウザ間の仕様の差異が大きかった頃は、自前でこれらを吸収する記述をするのは煩雑でjQuery等のライブラリが重宝されました。
また、アニメーションやajax等を簡単な記述で実装できる点でも便利でした。

近年はブラウザ間の差もあまりなくなり、Vanilla JSの機能が拡張されたこともあって、差異吸収のメリットはほぼなくなりました。
更に、ajaxなどのAPIが整備され、CSSアニメーションが実装されたことにより、これらのメリットもさほどではなくなりました。
jQueryにはメソッドチェーンが可能という記述上の利点もありますけれど、全体的には利用するメリットは減少したと言えると思います。

時々、要素取得のためだけにjQueryを利用しているようなものを見かけますが、そのような使い方なら(以前からもそうですが)利用する意味もなく、かえって、わざわざ読み込むデメリットの方が大きいように感じます。


>スマホをお持ちでないのですか!?
>にもかかわらず、ここまで的確なコードが書けるとは!?
さすがに、スマホは持ってはいますよ。(^^;
ただ、サーバ環境がないのでテストするのが面倒なのと、デバッグツールもないので、スマホ用のスクリプトを書いたことが無いだけです。
(なので、ほぼわかっていません。)
ブラウザもChromeだけなので、複数での確認もできません。
(それよりもAndroid、iphoneの違いがありそうな気もしますが・・)

Touchイベントの仕様もほとんど知らなかったので、今回のご質問を題材にしてお勉強をさせていただきました。
単純な動作確認程度のテストはしていますけれど、どうやらジェスチャー機能の全てをキャンセルできてはいないかもしれません。
ですので、あまりきちんとはしていない可能性がありますので、あくまでも要領の参考程度としてお考えください。
    • good
    • 0
この回答へのお礼

jQueryの意義について、解説をありがとうございます。
これは、今後の方針を検討するために重要な情報です。
やはりVanilla JSの成長に伴い、jQueryの意義がだんだん薄れてきたのですね。
CSSアニメーションは初めて知ったのですが、これは驚愕です。
このたびのシンセサイザーの実装に大いに活用できそうです。

メソッドチェーンは好きです。
ですから、まだ使う意義は少しあると思いました。

要素取得のためだけに使う事も多々あります。
むしろ、そのためのライブラリーと認識していました^^;

スマホについて、私を含めPCのヘビーユーザーはあまりスマホを使いこなしていない気がします^^
外出先などで、普段PCでやっている事をスマホでやろうとすると、思いのほか手間取り、イライラします。
Androidで動かしながら作って、たまにiphoneでテストすると、表示や動きがかなり違う事がありむかつきます^^

お礼日時:2024/08/01 23:36

こんにちは



>順に触れるようになぞった時には、ド、レ、ミ、と順に
>鳴るようにするにはどうすれば良いですか。
指がある位置を判別できれば良いということと解釈しました。

前回も書きましたが、タッチイベントは複数の指に対応しているので、順に調べるようにするのが良いでしょう。
いずれにしろ、指の位置から対応する要素を調べなければなりませんが、ご提示のような単純矩形ならcanvasでもHTML要素でもあまり変わりはないと思いますが、複雑な形状になるとcanvasでの判別も面倒になると思いますし、色を変えるなどいろいろな変化を予定しているのなら、HTMLやSVGの方が要素として独立しているので扱いやすいのではないかと思います。
(もちろん、canvasでも可能ですが・・)

ひとまず、ご例示にあるように該当要素の色を変えるものとして解釈しました。
ご提示のdiv要素で、class=onなら着色(=黄)、それ以外は元のまま(=白)として、スクリプトからはクラスを変えるだけの仕組みとして考えました。
イベントの範囲を限定するために、div要素群のラッパーとして#wrapを追加して、この範囲でtouchstartが発生したものだけを対象にしています。

スマホはあまりよくわかっていないのですけれど・・・
指定範囲内ではスクロールやピンチが発生しないように、preventDefaultを実行してあります。
以下は、ごく簡単な要領のみの例です。
(テスト環境もありませんので、AndroidのChromeで簡単な確認のみ)

<!DOCTYPE HTML>
<html lang="ja">
<head><title>Sample</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#wrap { width: 12em; }
#wrap div { border: 1px solid; padding: 1em; }
#wrap div.on { background-color: yellow; }
</style>
</head>

<body>
<div id="wrap">
<div></div><div></div>
<div></div><div></div>
<div></div><div></div>
<div></div><div></div>
<div></div><div></div>
</div>

<script>
const
divs = [...document.querySelectorAll('#wrap>div')],
handler = e => {
let n, pos = [];
e.preventDefault();
[...e.touches].forEach( t => {
n = divs.indexOf(document.elementFromPoint(t.clientX, t.clientY));
if(n > -1) pos[n] = 1;
});
divs.forEach((e, i) => {
pos[i]? e.classList.add('on'): e.classList.remove('on');
});
};

['touchstart', 'touchend', 'touchmove'].forEach( t => {
divs[0].parentNode.addEventListener(t, handler, {passive: false});
});
</script>
</body>
</html>
    • good
    • 0
この回答へのお礼

お返事ありがとうございます。
ご指摘の利点に加え、最初にcanvasで作った時には、指を離してもたまに音が止まらなかったり、まだバグ潰しが必要な事もあり、私としても、できればこのまま1鍵1要素で実装していきたいと考えていました。

恐れながら先生はスマホをお持ちでないのですか!?
にもかかわらず、ここまで的確なコードが書けるとは!?

頂いたコードは、私の要望を全て満たしています
\(^o^)/

お礼日時:2024/08/01 08:00

> 同時押しに対応させるのが難しく



同時押しに対応するには、タッチイベントを繰り返し読み取る必要があると思いますが、ややこしい書き方になると思います。
classなどを使って、ある程度は見通しよく書くことはできると思います。

教えてgooのようなサイトは、文字数制限があったりするため、規模の大きなプログラミングのノウハウは、学ぶことが難しいと感じています。
本屋さんに行って、classなどを使ったプログラミングの本を読んでみることをおすすめします。(class そのものの説明をしている本ではなく、具体的に使い方が説明されている本の方が良いです)

少し古い本ですが、「グラフィックスプログラミング入門」という本は、シューティングゲームを作ることを題材にして、規模の大きなプログラミングをするときにどんな書き方をするのか勉強になりました。
    • good
    • 0
この回答へのお礼

お返事ありがとうございます。
classの使い方の本は何冊か持っているのですが、未だにclassのメリットが良く分かっておらず、classはあまり使っていません^^;

お礼日時:2024/07/31 09:47

divごとに発火しないならば、canvas等で鍵盤を作って判別するしかないのかなと思います。



<canvas id="$canvas" width="300" height="500" sytle="width:100%;max-width:300px;height:auto;"></canvas>
<script>
'use strict';
const ctx = window.$canvas.getContext('2d');
const key = {
num: 5, // キーの数
width: 300, // canvasの横幅に合わせる
height: 100, // canvasの縦の長さをキーの数で割る
now: -1, // 今押しているキー
}
// イベントの変わり目ごとに実行する
function touchEvent(event){
if(event.type === 'touchstart') {
// 押し始めたとき
const touchKey = event.touches[0].clientY / key.height | 0;
key.now = touchKey;
}else if(event.type === 'touchmove') {
// 押し続けているとき
const touchKey = event.touches[0].clientY / key.height | 0;
if (key.now === touchKey) return;
key.now = touchKey;
}else if(event.type === 'touchend') {
// 押し終わったとき
key.now = -1;
}
sound(key);
}
// 押されたキーの色を変える
function sound({num, width, height, now}){
for (let i = 0; i < num; i++) {
const y = i * height;
if (now === i){
// key.nowが、iと一致するときだけ色をグレーにする
ctx.fillStyle = '#555';
} else {
ctx.fillStyle = '#fff';
}
ctx.fillRect(0, y, width, height);
ctx.strokeStyle = '#000';
ctx.strokeRect(0, y, width, height);
}
}
// 実行
sound(key);
window.$canvas.addEventListener('touchstart', touchEvent, false);
window.$canvas.addEventListener('touchmove', touchEvent, false);
window.$canvas.addEventListener('touchend', touchEvent, false);
</script>
    • good
    • 0
この回答へのお礼

お返事ありがとうございます。
やはり、このような鍵盤アプリを作るならcanvasを使うのが妥当でしょうか。
私も、最初はcanvasで作っていたのですが、同時押しに対応させるのが難しく、途中で鍵を1個1個divで作るように変更したという経緯があります。
しかし、画面をなぞって演奏できるようにするには、私もcanvas以外には思いつかないので、
少し悩みましたが、やはりここはcanvasに戻そうと思いました。

頂いたコードは1行1行全て読ませていただき、ノウハウを蓄えさせていただきました^^

お礼日時:2024/07/30 23:18

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています


おすすめ情報

このQ&Aを見た人がよく見るQ&A