質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

Firefox

Mozilla Foundationによって作られた無料、オープンソース、クロスプラットフォームなウェブブラウザ

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

1回答

3405閲覧

requestAnimationFrameの使用方法

west826

総合スコア14

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

Firefox

Mozilla Foundationによって作られた無料、オープンソース、クロスプラットフォームなウェブブラウザ

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2017/12/21 15:22

編集2017/12/22 03:15

やりたいこと

JavaScriptを使用して
ボタンを押すとそれぞれの効果が動画にかかるようなものを作りたい。

![イメージ説明

疑問点

requestAnimationFrame を使用して
モノクロボタンを押した後にセピアボタンを押すと
モノクロボタンの処理が行われないようにする為には
どの様にしたらよいのかがわかりません。

現状

それぞれの効果のプログラムを
setInterval を使用して作成することはできたが
なかなかうまく処理ができません。
例えば、モノクロを選択している時に
モノクロの動画とカラーの動画が交互に表示されて
チカチカしてしまいます。
その為
requestAnimationFrame を使用しようと思っています。

HTML

1<!DOCTYPE html> 2<html lang="ja"> 3 <head> 4 <meta charset="utf-8"> 5 <title>test</title> 6 7 <style type="text/css"> 8 #v{ 9 display:none; 10 } 11 #screen{ 12 width:480px; 13 height:270px; 14 background-color:#000000; 15 } 16 button{ 17 width:100px; 18 height:50px; 19 } 20 21 </style> 22 </head> 23 24 <body> 25 <video controls id="v" width="480" height="270"> 26 <source src="movie.mp4" width="480" height="270"> 27 </video> 28 <div id="screen"> 29 <canvas id="c" width="480" height="270"></canvas> 30 </div> 31 <button onClick="playVideo()">play/stop</button> 32 <button onClick="restart()">restart</button> 33 <button onClick="no()" id="gray">なし</button> 34 <button onClick="grayscale()" id="gray">モノクロ</button> 35 <button onClick="outline()">アウトライン</button> 36 37 <input id="color" type="color" value="#ff0000" /> 38 <input id="distance" type="number" value="10" /> 39 40 <script type="text/javascript"> 41 42 //play/stopボタンの設定 43function playVideo(){ 44 var video = document.getElementById("v"); 45 if(video.paused){ 46 video.play(); 47 }else{ 48 video.pause(); 49 } 50 setInterval(function(){ 51 var canvas = document.getElementById("c"); 52 canvas.getContext("2d").drawImage(video, 0, 0, 480, 270); 53 }, 1000/30); 54} 55 //restartボタンの設定 56function restart() { 57 var video = document.getElementById("v"); 58 video.currentTime = 0; 59} 60 61 62 var reqId = null; 63 64 //モノクロ 65 function grayscale(){ 66 cancelRender(); 67 grayFilter(); 68 }; 69 //アウトライン 70 function heatmap(){ 71 cancelRender(); 72 heatmap1(); 73}; 74 75function cancelRender() { 76 if(reqId) { 77 cancelAnimationFrame(reqId); 78 } 79} 80 81 //モノクロ 82 function grayFilter(){ 83 loop(); 84 85 function loop(){ 86 monokuro(); 87 reqId = requestAnimationFrame(loop); 88 }; 89 90 // 描画内容に対して、式を当てはめながらrgbの値を計算する。 91 function monokuro(){ 92 // 対象のCanvasを取得し、contextも取得する。 93 var canvas = document.getElementById("c"); 94 var context = canvas.getContext("2d"); 95 video = document.getElementById("v"); 96 97 // Canvasから描画内容を保持するimageDataを取得する。 98 var imageData = context.getImageData(0, 0, canvas.width, canvas.height); 99 100 var d = imageData.data; 101 for (var i = 0; i < d.length; i+=4) { 102 var g = d[i] * 0.2126 + d[i+1] * 0.7152 + d[i+2] * 0.0722; 103 d[i] = d[i+1] = d[i+2] = g; 104 // d[i+3]に格納されたα値は変更しない 105 } 106 107 108 // 計算結果でCanvasの表示内容を更新する。 109 context.putImageData(imageData, 0, 0); 110 } 111 } 112 113 //アウトライン 114function heatmap1(){ 115(function () { 116 //Canvasの準備 117 var canvas = document.getElementById("c"); 118 var context = canvas.getContext("2d"); 119 var video = document.getElementById("v"); 120 121 // 境界線とする閾値 122 var outlineColor = {r: 255, g: 0, b: 0}, 123 complementaryColor = {r: 0, g: 255, b: 255}, 124 colorDistance = 10; 125 126 draw(); 127 128 129 // videoの映像をcanvasに描画する 130 function draw() { 131 //context.drawImage(video, 0, 0, canvas.width, canvas.height); 132 // ここでアウトライン処理をする 133 outline(); 134 requestAnimationFrame(draw); 135 }; 136 137 // 境界線とする閾値 138 var outlineColor = {r: 255, g: 0, b: 0}, 139 complementaryColor = {r: 0, g: 255, b: 255}, 140 colorDistance = 10; 141 142 // アウトライン処理 143 function outline() { 144 var imageData = context.getImageData(0, 0, canvas.width, canvas.height), 145 data = imageData.data; 146 147 // dataはUint8ClampedArray 148 // 長さはcanvasの width * height * 4(r,g,b,a) 149 // 先頭から、一番左上のピクセルのr,g,b,aの値が順に入っており、 150 // 右隣のピクセルのr,g,b,aの値が続く 151 // n から n+4 までが1つのピクセルの情報となる 152 153 var currentOutlineColor = outlineColor; 154 155 for (var i = 0, l = data.length; i < l; i += 4) { 156 157 // この条件の時、currentは右端の色、nextは1px下の段の左端の色になるので透明にしてスキップする 158 if ((i / 4 + 1) % canvas.width === 0) { 159 data[i + 3] = 0; 160 continue; 161 } 162 163 // 段が変わったら色を変える 164 // 一段ずつoutlineColorからcomplementaryColorにグラデーションにする 165 if ((i / 4) % canvas.width === 0) { 166 var row = (i / 4) / canvas.width, 167 r = (outlineColor.r - complementaryColor.r) / canvas.height, 168 g = (outlineColor.g - complementaryColor.g) / canvas.height, 169 b = (outlineColor.b - complementaryColor.b) / canvas.height; 170 171 currentOutlineColor = { 172 r: outlineColor.r - (r * row), 173 g: outlineColor.g - (g * row), 174 b: outlineColor.b - (b * row) 175 }; 176 } 177 178 var currentIndex = i, 179 nextIndex = currentIndex + 4, 180 underIndex = currentIndex + (canvas.width * 4), 181 // チェックするピクセルの色 182 current = { 183 r: data[currentIndex], 184 g: data[currentIndex + 1], 185 b: data[currentIndex + 2] 186 }, 187 // 右隣の色 188 next = { 189 r: data[nextIndex], 190 g: data[nextIndex + 1], 191 b: data[nextIndex + 2] 192 }, 193 // 下の色 194 under = { 195 r: data[underIndex], 196 g: data[underIndex + 1], 197 b: data[underIndex + 2] 198 }; 199 200 // 現在のピクセルと右隣、下の色の三次元空間上の距離を閾値と比較する 201 // 閾値より大きい(色が遠い)場合、境界線とみなしそのピクセルをcurrentOutlineColorに変更 202 // 閾値より小さい(色が近い)場合、そのピクセルを消す 203 if (getColorDistance(current, next) > colorDistance || getColorDistance(current, under) > colorDistance) { 204 data[i] = currentOutlineColor.r; 205 data[i + 1] = currentOutlineColor.g; 206 data[i + 2] = currentOutlineColor.b; 207 } else { 208 // alpha値を0にすることで見えなくする 209 data[i + 3] = 0; 210 } 211 } 212 213 // 書き換えたdataをimageDataにもどし、描画する 214 imageData.data = data; 215 context.putImageData(imageData, 0, 0); 216 }; 217 218 // r,g,bというkeyを持ったobjectが第一引数と第二引数に渡される想定 219 function getColorDistance(rgb1, rgb2) { 220 // 三次元空間の距離が返る 221 return Math.sqrt( 222 Math.pow((rgb1.r - rgb2.r), 2) + 223 Math.pow((rgb1.g - rgb2.g), 2) + 224 Math.pow((rgb1.b - rgb2.b), 2) 225 ); 226 }; 227 228 var color = document.getElementById('color'); 229 color.addEventListener('change', function () { 230 // フォームの値は16進カラーコードなのでrgb値に変換する 231 outlineColor = color2rgb(this.value); 232 complementaryColor = getComplementaryColor(outlineColor); 233 }); 234 235 var color2rgb = function (color) { 236 color = color.replace(/^#/, ''); 237 return { 238 r: parseInt(color.substr(0, 2), 16), 239 g: parseInt(color.substr(2, 2), 16), 240 b: parseInt(color.substr(4, 2), 16) 241 }; 242 }; 243 244 // 補色の計算 245 var getComplementaryColor = function (rgb) { 246 var max = Math.max(rgb.r, rgb.g, rgb.b), 247 min = Math.min(rgb.r, rgb.g, rgb.b), 248 sum = max + min; 249 return { 250 r: sum - rgb.r, 251 g: sum - rgb.g, 252 b: sum - rgb.b 253 }; 254 }; 255 256 var distance = document.getElementById('distance'); 257 distance.style.textAlign = 'right'; 258 distance.addEventListener('change', function () { 259 colorDistance = this.value; 260 }); 261})(); 262} 263 </script> 264 </body> 265</html> 266

requestAnimationFrame を初めて使用しているため
何度も質問してしまうかもしれませんが
わかる方がいらっしゃいましたら教えていただけると幸いです。
よろしくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

requestAnimationFrame()は、フレーム更新時に実行する関数を登録するというもので、
setIntervalとは違い、1回のrequestAnimationFrame()実行につき1回しか引数で指定した関数(例ではrender())の登録を行いません。
ですので、指定した関数内で改めて再度、requestAnimationFrame()を実行して、また次のフレームで実行する関数の登録を行う。これにより、毎フレーム関数が実行されるようになります。

コードを載せていないため、どのようなコードを書いているのかはわかりませんが、
想像するに

js

1var reqId = requestAnimationFrame(render); 2 3button.onclick = evt => { 4 cancelAnimationFrame(reqId); 5}; 6 7function render() { 8 requestAnimationFrame(render); 9 // ... 10}

とコードを組んでいるかと思われます。
1行目でrequestAnimationFrame()を実行し、そのreqId変数にidを保持していますが、フレームの更新が行われたらもうそのidは無効になりますので、buttonクリックして、cancelAnimationFrameでキャンセルしても無意味なものとなります。
buttonクリックで、正しくキャンセルされるようにするには、

js

1var reqId = requestAnimationFrame(render); 2 3button.onclick = evt => { 4 cancelAnimationFrame(id); 5}; 6 7function render() { 8 reqId = requestAnimationFrame(render); 9 // ... 10}

render関数内で実行しているrequestAnimationFrameのidでreqId変数の内容を更新させます。
これで、buttonクリックで停止させることができます。

で、ボタンクリックで、別の描画処理を行わせたい場合は、

js

1var reqId = null; 2 3monochromeButton.onclick = function() { 4 cancelRender(); 5 monochromeRender(); 6}; 7 8sepiaButton.onclick = function() { 9 cancelRender(); 10 sepiaRender(); 11}; 12 13function cancelRender() { 14 if(reqId) { 15 cancelAnimationFrame(reqId); 16 } 17} 18 19function monochromeRender() { 20 reqId = requestAnimationFrame(render); 21 // ... 22} 23 24function sepiaRender() { 25 reqId = requestAnimationFrame(render); 26 // ... 27}

といった感じに組みます。

投稿2017/12/21 16:06

編集2017/12/21 16:14
turbgraphics200

総合スコア4267

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

west826

2017/12/22 03:17

わかりやすいご説明ありがとうございます。 教えていただいたように変更したのですが アウトライン→モノクロへの処理がうまくいきません どこか間違っているのでしょうか。
west826

2017/12/22 04:01

何度も申し訳ございません。 解結しました。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問