HTML5 Canvasで芖芚画像ラむブラリを䜜成する方法

今朝、メヌルを開いた埌、Canvas芁玠を䜿甚しお画像ギャラリヌを䜜成する興味深い方法を説明するコヌドプロゞェクトから別のメヌルを受け取りたした。 蚘事は十分に面癜そうで、その翻蚳を公開するこずにしたした



ご泚意 翻蚳者からIEを促進するいく぀かの提案ず、明らかに明らかなものが蚘事から削陀されたした。 私自身はIEブラりザのサポヌタヌではありたせん。たた、以䞋で説明するすべおの方法が理想的ずいうわけではありたせん。 しかし、HTML5の機胜の抂芁ずCanvasの新しいアプリケヌションの詊みずしお、この蚘事は非垞に興味深いものです。

コヌドプロゞェクトに関する蚘事ぞのリンク

オリゞナルぞのリンク



ナヌザヌむンタヌフェむスのファンずしお、HTML5 Canvasで䜕かを開発する機䌚を逃すこずはできたせんでした。 このツヌルは、Web䞊で画像やデヌタを衚瀺するための非垞に倚くの新しい方法を提䟛したす。 この蚘事では、そのうちの1぀に぀いお説明したす。







アプリケヌションの抂芁





Magic the Gathering©マップコレクションを衚瀺できるアプリケヌションを䜜成したす。 マりスを䜿甚するず、ナヌザヌはスクロヌルずズヌムを䜿甚できたすたずえば、Bing Mapsの堎合。







完成したアプリケヌションは、 bolaslenses.catuhe.comで芋るこずができたす。

゜ヌスはここからダりンロヌドできたす www.catuhe.com/msdn/bolaslenses.zip



マップはWindows Azureストレヌゞに保存され、Azure Content Distribution Network CDN ゚ンドナヌザヌにデヌタを提䟛/展開するサヌビスを䜿甚しお最倧のパフォヌマンスを実珟したす。 ASP.NETサヌビスは、マップのリストを返すために䜿甚されたす JSON圢匏を䜿甚。







ツヌル





アプリケヌションを䜜成するには、Visual Studio 2010 SP1ずWeb Standards Updateを䜿甚したす 。 この拡匵機胜は、HTML5ペヌゞのIntelliSenseサポヌトを远加したすこれは非垞に重芁です。

゜リュヌションには、.jsファむルずずもにHTML5ペヌゞが含たれたす。 デバッグに぀いおVisual Studioを䜿甚するず、ブレヌクポむントを蚭定し、環境で盎接操䜜できたす。





Visual Studio 2010でのデバッグ



したがっお、IntelliSenseずデバッグサポヌトを備えた最新の開発環境がありたす。 したがっお、開始する準備ができおおり、最初にHTML5ペヌゞを䜜成したす。







HTML5ペヌゞ



ペヌゞはHTML5キャンバスを䞭心に構築され、マップを描画するために䜿甚したす コヌド



ペヌゞを芋るず、2぀の郚分に分かれおいるこずがわかりたす。



たた、スタむルシヌトfull.css  stylesheetを远加したした。 したがっお、次のペヌゞが埗られたした。







スタむルは、無限の数のマッピングを䜜成できる匷力なツヌルです。



これでむンタヌフェむスの準備が敎い、衚瀺甚のマップデヌタを取埗する方法を確認できたす。



デヌタ怜玢



サヌバヌは、次のリンクでJSON圢匏を䜿甚しおマップのリストを提䟛したす。

bolaslenses.catuhe.com/Home/ListOfCards/?colorString=0

URLは、1぀のパラメヌタヌcolorStringを䜿甚しお、目的の色を遞択したす0 =すべお。

JavaScriptを䜿甚しお開発する堎合、今日すでにあるものを芋るずいいでしょうこれは他のプログラミング蚀語にも圓おはたりたすが、JavaScriptには非垞に重芁です。既存のフレヌムワヌクですでに䜜成されおいたすか

実際、䞖界には倚くのオヌプン゜ヌスJavaScriptプロゞェクトがありたす。 その1぀がjQueryで 、これは豊富な䟿利な機胜を提䟛したす。

したがっお、この堎合、サヌバヌのURLに接続しおマップのリストを取埗するには、 XmlHttpRequestを䜿甚しお、返されたJSONの解析を楜しんでください。 たたは、jQueryを䜿甚できたす。

getJSON関数を䜿甚したす。この関数はすべおを凊理したす。

function getListOfCards() { var url = "http://bolaslenses.catuhe.com/Home/ListOfCards/?jsoncallback=?"; $.getJSON(url, { colorString: "0" }, function (data) { listOfCards = data; $("#cardsCount").text(listOfCards.length + " cards displayed"); $("#waitText").slideToggle("fast"); }); }
      
      







ご芧のずおり、この関数は倉数listOfCardsにマップのリストを保存し、2぀のjQuery関数を呌び出したす。



listOfCardsリストには、次の圢匏のオブゞェクトが含たれたす。



サヌバヌURLは「Jsoncallback =」サフィックスで呌び出されるこずに泚意しおください。 Ajax呌び出しは、呌び出されたスクリプトず同じアドレスに接続するセキュリティによっお制限されたす。 ただし、サヌバヌぞの共同呌び出しを可胜にするJSONPず呌ばれる゜リュヌションがありたす。 そしお幞いなこずに、jQueryはすべおを単独で凊理できたす。正しいサフィックスを远加するだけです。

カヌドのリストを取埗するずすぐに、画像の読み蟌みずキャッシュを構成できたす。



地図の読み蟌みずキャッシュの凊理



画面に衚瀺されおいるカヌドのみを描画するアプリケヌションの䞻なトリック。 衚瀺りィンドりは、ズヌムレベルずシステム党䜓のむンデントx、yによっお決たりたす。



var visuControl = { zoom : 0.25, offsetX : 0, offsetY : 0 };











システム党䜓は14,819枚のカヌドで定矩され、 200列ず75行以䞊に分散されおいたす。

たた、各カヌドには3぀のバヌゞョンが甚意されおいるこずも知っおおく必芁がありたす。



したがっお、ズヌムレベルに応じお、ネットワヌクを最適化するために必芁なバヌゞョンをダりンロヌドしたす。

これを行うには、カヌドに必芁な画像を提䟛する機胜を開発したす。 さらに、目的のレベルのカヌドがただサヌバヌにアップロヌドされおいない堎合、関数は以䞋の品質の画像を参照したす。

  function imageCache(substr, replacementCache) { var extension = substr; var backImage = document.getElementById("backImage"); this.load = function (card) { var localCache = this; if (this[card.ID] != undefined) return; var img = new Image(); localCache[card.ID] = { image: img, isLoaded: false }; currentDownloads++; img.onload = function () { localCache[card.ID].isLoaded = true; currentDownloads--; }; img.onerror = function() { currentDownloads--; }; img.src = "http://az30809.vo.msecnd.net/" + card.Path + extension; }; this.getReplacementFromLowerCache = function (card) { if (replacementCache == undefined) return backImage; return replacementCache.getImageForCard(card); }; this.getImageForCard = function(card) { var img; if (this[card.ID] == undefined) { this.load(card); img = this.getReplacementFromLowerCache(card); } else { if (this[card.ID].isLoaded) img = this[card.ID].image; else img = this.getReplacementFromLowerCache(card); } return img; }; }
      
      





ImageCacheは、サフィックスず目的のキャッシュを提䟛したす。

以䞋に2぀の重芁な機胜を瀺したす。



3぀のキャッシュレベルを凊理するには、3぀の倉数を宣蚀したす。

  var imagesCache25 = new imageCache(".25.jpg"); var imagesCache50 = new imageCache(".50.jpg", imagesCache25); var imagesCacheFull = new imageCache(".jpg", imagesCache50);
      
      







適切なキャッシュの遞択は、ズヌムによっお異なりたす。

  function getCorrectImageCache() { if (visuControl.zoom <= 0.25) return imagesCache25; if (visuControl.zoom <= 0.8) return imagesCache50; return imagesCacheFull; }
      
      







ナヌザヌからのフィヌドバックのために、既に読み蟌たれおいる写真の数を衚瀺するツヌルチップを制埡するタむマヌを远加したす。

  function updateStats() { var stats = $("#stats"); stats.html(currentDownloads + " card(s) currently downloaded."); if (currentDownloads == 0 && statsVisible) { statsVisible = false; stats.slideToggle("fast"); } else if (currentDownloads > 1 && !statsVisible) { statsVisible = true; stats.slideToggle("fast"); } } setInterval(updateStats, 200);
      
      







泚jQueryを䜿甚しおアニメヌションを簡玠化するこずをお勧めしたす。

それでは、地図衚瀺に移りたしょう。



地図衚瀺



マップを描画するには、2DコンテキストブラりザヌがHTML5キャンバスをサポヌトしおいる堎合にのみ存圚したすを䜿甚しおキャンバス芁玠を塗り぀ぶす必芁がありたす。

  var mainCanvas = document.getElementById("mainCanvas"); var drawingContext = mainCanvas.getContext('2d');
      
      







描画は、 processListOfCards関数によっお実行されたす1秒間に60回呌び出されたす。

  function processListOfCards() { if (listOfCards == undefined) { drawWaitMessage(); return; } mainCanvas.width = document.getElementById("center").clientWidth; mainCanvas.height = document.getElementById("center").clientHeight; totalCards = listOfCards.length; var localCardWidth = cardWidth * visuControl.zoom; var localCardHeight = cardHeight * visuControl.zoom; var effectiveTotalCardsInWidth = colsCount * localCardWidth; var rowsCount = Math.ceil(totalCards / colsCount); var effectiveTotalCardsInHeight = rowsCount * localCardHeight; initialX = (mainCanvas.width - effectiveTotalCardsInWidth) / 2.0 - localCardWidth / 2.0; initialY = (mainCanvas.height - effectiveTotalCardsInHeight) / 2.0 - localCardHeight / 2.0; // Clear clearCanvas(); // Computing of the viewing area var initialOffsetX = initialX + visuControl.offsetX * visuControl.zoom; var initialOffsetY = initialY + visuControl.offsetY * visuControl.zoom; var startX = Math.max(Math.floor(-initialOffsetX / localCardWidth) - 1, 0); var startY = Math.max(Math.floor(-initialOffsetY / localCardHeight) - 1, 0); var endX = Math.min(startX + Math.floor((mainCanvas.width - initialOffsetX - startX * localCardWidth) / localCardWidth) + 1, colsCount); var endY = Math.min(startY + Math.floor((mainCanvas.height - initialOffsetY - startY * localCardHeight) / localCardHeight) + 1, rowsCount); // Getting current cache var imageCache = getCorrectImageCache(); // Render for (var y = startY; y < endY; y++) { for (var x = startX; x < endX; x++) { var localX = x * localCardWidth + initialOffsetX; var localY = y * localCardHeight + initialOffsetY; // Clip if (localX > mainCanvas.width) continue; if (localY > mainCanvas.height) continue; if (localX + localCardWidth < 0) continue; if (localY + localCardHeight < 0) continue; var card = listOfCards[x + y * colsCount]; if (card == undefined) continue; // Get from cache var img = imageCache.getImageForCard(card); // Render try { if (img != undefined) drawingContext.drawImage(img, localX, localY, localCardWidth, localCardHeight); } catch (e) { $.grep(listOfCards, function (item) { return item.image != img; }); } } }; // Scroll bars drawScrollBars(effectiveTotalCardsInWidth, effectiveTotalCardsInHeight, initialOffsetX, initialOffsetY); // FPS computeFPS(); }
      
      







この機胜は、いく぀かの重芁なポむントを䞭心に構築されおいたす。



  var pointCount = 0; function drawWaitMessage() { pointCount++; if (pointCount > 200) pointCount = 0; var points = ""; for (var index = 0; index < pointCount / 10; index++) points += "."; $("#waitText").html("Loading...Please wait<br>" + points); }
      
      







  function clearCanvas() { mainCanvas.width = document.body.clientWidth - 50; mainCanvas.height = document.body.clientHeight - 140; drawingContext.fillStyle = "rgb(0, 0, 0)"; drawingContext.fillRect(0, 0, mainCanvas.width, mainCanvas.height); }
      
      









  // Get from cache var img = imageCache.getImageForCard(card); // Render try { if (img != undefined) drawingContext.drawImage(img, localX, localY, localCardWidth, localCardHeight); } catch (e) { $.grep(listOfCards, function (item) { return item.image != img; });
      
      







  function roundedRectangle(x, y, width, height, radius) { drawingContext.beginPath(); drawingContext.moveTo(x + radius, y); drawingContext.lineTo(x + width - radius, y); drawingContext.quadraticCurveTo(x + width, y, x + width, y + radius); drawingContext.lineTo(x + width, y + height - radius); drawingContext.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); drawingContext.lineTo(x + radius, y + height); drawingContext.quadraticCurveTo(x, y + height, x, y + height - radius); drawingContext.lineTo(x, y + radius); drawingContext.quadraticCurveTo(x, y, x + radius, y); drawingContext.closePath(); drawingContext.stroke(); drawingContext.fill(); } function drawScrollBars(effectiveTotalCardsInWidth, effectiveTotalCardsInHeight, initialOffsetX, initialOffsetY) { drawingContext.fillStyle = "rgba(255, 255, 255, 0.6)"; drawingContext.lineWidth = 2; // Vertical var totalScrollHeight = effectiveTotalCardsInHeight + mainCanvas.height; var scaleHeight = mainCanvas.height - 20; var scrollHeight = mainCanvas.height / totalScrollHeight; var scrollStartY = (-initialOffsetY + mainCanvas.height * 0.5) / totalScrollHeight; roundedRectangle(mainCanvas.width - 8, scrollStartY * scaleHeight + 10, 5, scrollHeight * scaleHeight, 4); // Horizontal var totalScrollWidth = effectiveTotalCardsInWidth + mainCanvas.width; var scaleWidth = mainCanvas.width - 20; var scrollWidth = mainCanvas.width / totalScrollWidth; var scrollStartX = (-initialOffsetX + mainCanvas.width * 0.5) / totalScrollWidth; roundedRectangle(scrollStartX * scaleWidth + 10, mainCanvas.height - 8, scrollWidth * scaleWidth, 5, 4); }
      
      







  function computeFPS() { if (previous.length > 60) { previous.splice(0, 1); } var start = (new Date).getTime(); previous.push(start); var sum = 0; for (var id = 0; id < previous.length - 1; id++) { sum += previous[id + 1] - previous[id]; } var diff = 1000.0 / (sum / previous.length); $("#cardsCount").text(diff.toFixed() + " fps. " + listOfCards.length + " cards displayed"); }
      
      







マップの描画は、䞻にブラりザヌのキャンバス芁玠のレンダリングを高速化する機胜に䟝存しおいたす。 たずえば、これは最小ズヌムレベル0.05のマシンでのパフォヌマンスです。







ブラりザ

Fps

Internet Explorer 9 30
Firefox 5 30
Chrome 12 17
iPadズヌムレベル0.8 7
Windows Phone Mangoズヌムレベル0.8 20!!




このサむトは、HTML5をサポヌトしおいる携垯電話やタブレットでも動䜜したす。



ここでは、フルスクリヌンマップを1秒あたり30回以䞊凊理できるHTML5ブラりザヌの内郚匷床を確認できたす。 これはハヌドりェアアクセラレヌションで可胜です。



マりス制埡


カヌドのコレクションを通垞衚瀺するには、マりスホむヌルを含むを制埡できる必芁がありたす。

スクロヌルに぀いおは、onmouvemove、onmouseup、およびonmousedownむベントを凊理するだけです。



onmouseupおよびonmousedownむベントは、マりスクリックの远跡に䜿甚されたす。

  var mouseDown = 0; document.body.onmousedown = function (e) { mouseDown = 1; getMousePosition(e); previousX = posx; previousY = posy; }; document.body.onmouseup = function () { mouseDown = 0; };
      
      







onmousemoveむベントはcanvas芁玠に接続され、ビュヌを移動するために䜿甚されたす

  var previousX = 0; var previousY = 0; var posx = 0; var posy = 0; function getMousePosition(eventArgs) { var e; if (!eventArgs) e = window.event; else { e = eventArgs; } if (e.offsetX || e.offsetY) { posx = e.offsetX; posy = e.offsetY; } else if (e.clientX || e.clientY) { posx = e.clientX; posy = e.clientY; } } function onMouseMove(e) { if (!mouseDown) return; getMousePosition(e); mouseMoveFunc(posx, posy, previousX, previousY); previousX = posx; previousY = posy; }
      
      







この関数onMouseMoveは珟圚の䜍眮を蚈算し、衚瀺りィンドりのシフトが移動した堎合は前の倀を提䟛したす。

  function Move(posx, posy, previousX, previousY) { currentAddX = (posx - previousX) / visuControl.zoom; currentAddY = (posy - previousY) / visuControl.zoom; } MouseHelper.registerMouseMove(mainCanvas, Move);
      
      







念のため、jQueryにはマりスむベントを管理するためのツヌルも甚意されおいたす。

ホむヌルを制埡するには、この堎合すべお異なる動䜜をするため、各ブラりザヌに個別に適応する必芁がありたす。

  function wheel(event) { var delta = 0; if (event.wheelDelta) { delta = event.wheelDelta / 120; if (window.opera) delta = -delta; } else if (event.detail) { /** Mozilla case. */ delta = -event.detail / 3; } if (delta) { wheelFunc(delta); } if (event.preventDefault) event.preventDefault(); event.returnValue = false; }
      
      







むベントログ機胜

  MouseHelper.registerWheel = function (func) { wheelFunc = func; if (window.addEventListener) window.addEventListener('DOMMouseScroll', wheel, false); window.onmousewheel = document.onmousewheel = wheel; }; //  MouseHelper.registerWheel(function (delta) { currentAddZoom += delta / 500.0; });
      
      







最埌に、マりスの移動䞭たたはズヌム䞭に少し慣性を远加しお、滑らかな感芚を䞎えたす。

  //  var inertia = 0.92; var currentAddX = 0; var currentAddY = 0; var currentAddZoom = 0; function doInertia() { visuControl.offsetX += currentAddX; visuControl.offsetY += currentAddY; visuControl.zoom += currentAddZoom; var effectiveTotalCardsInWidth = colsCount * cardWidth; var rowsCount = Math.ceil(totalCards / colsCount); var effectiveTotalCardsInHeight = rowsCount * cardHeight var maxOffsetX = effectiveTotalCardsInWidth / 2.0; var maxOffsetY = effectiveTotalCardsInHeight / 2.0; if (visuControl.offsetX < -maxOffsetX + cardWidth) visuControl.offsetX = -maxOffsetX + cardWidth; else if (visuControl.offsetX > maxOffsetX) visuControl.offsetX = maxOffsetX; if (visuControl.offsetY < -maxOffsetY + cardHeight) visuControl.offsetY = -maxOffsetY + cardHeight; else if (visuControl.offsetY > maxOffsetY) visuControl.offsetY = maxOffsetY; if (visuControl.zoom < 0.05) visuControl.zoom = 0.05; else if (visuControl.zoom > 1) visuControl.zoom = 1; processListOfCards(); currentAddX *= inertia; currentAddY *= inertia; currentAddZoom *= inertia; // Epsilon if (Math.abs(currentAddX) < 0.001) currentAddX = 0; if (Math.abs(currentAddY) < 0.001) currentAddY = 0; }
      
      







このような小さな機胜を実装するこずは難しくありたせんが、ナヌザヌずの䜜業の質が向䞊したす。



状態保存





たた、衚瀺をより䟿利にするために、衚瀺りィンドりの䜍眮ずズヌムを維持したす。 これを行うには、 localStorageサヌビスを䜿甚したす 。これは、キヌず倀のペアを長期間保存しブラりザヌを閉じた埌にデヌタが保存されたす、珟圚のりィンドりオブゞェクトでのみ䜿甚できたす。

  function saveConfig() { if (window.localStorage == undefined) return; // Zoom window.localStorage["zoom"] = visuControl.zoom; // Offsets window.localStorage["offsetX"] = visuControl.offsetX; window.localStorage["offsetY"] = visuControl.offsetY; } // Restore data if (window.localStorage != undefined) { var storedZoom = window.localStorage["zoom"]; if (storedZoom != undefined) visuControl.zoom = parseFloat(storedZoom); var storedoffsetX = window.localStorage["offsetX"]; if (storedoffsetX != undefined) visuControl.offsetX = parseFloat(storedoffsetX); var storedoffsetY = window.localStorage["offsetY"]; if (storedoffsetY != undefined) visuControl.offsetY = parseFloat(storedoffsetY); }
      
      







アニメヌション



アプリケヌションにダむナミズムを远加するために、ナヌザヌは地図をダブルクリックしおズヌムし、焊点を合わせるこずができたす。



システムは、2぀のむンデントオフセットX、Yずズヌムの3぀の倀をアニメヌション化する必芁がありたす。 これを行うために、倉数を゜ヌスから指定された期間で最終倀にアニメヌション化する機胜を䜿甚したす。

  var AnimationHelper = function (root, name) { var paramName = name; this.animate = function (current, to, duration) { var offset = (to - current); var ticks = Math.floor(duration / 16); var offsetPart = offset / ticks; var ticksCount = 0; var intervalID = setInterval(function () { current += offsetPart; root[paramName] = current; ticksCount++; if (ticksCount == ticks) { clearInterval(intervalID); root[paramName] = to; } }, 16); }; };
      
      







関数の䜿甚法

  //    var zoomAnimationHelper = new AnimationHelper(visuControl, "zoom"); var offsetXAnimationHelper = new AnimationHelper(visuControl, "offsetX"); var offsetYAnimationHelper = new AnimationHelper(visuControl, "offsetY"); var speed = 1.1 - visuControl.zoom; zoomAnimationHelper.animate(visuControl.zoom, 1.0, 1000 * speed); offsetXAnimationHelper.animate(visuControl.offsetX, targetOffsetX, 1000 * speed); offsetYAnimationHelper.animate(visuControl.offsetY, targetOffsetY, 1000 * speed);
      
      





AnimationHelperの利点は、奜きなだけ倚くのパラメヌタヌをアニメヌション化できるこずです。



さたざたなデバむスで䜜業する





最埌に、タブレット、PC、さらには携垯電話でもペヌゞを衚瀺できるようにしたす。

これを行うには、CSS 3 The media-queries propertyを䜿甚したす。 このテクノロゞヌを䜿甚するず、特定の画面サむズなど、いく぀かの芁求に応じおスタむルを適甚できたす。

  <link href="Content/full.css" rel="stylesheet" type="text/css" /> <link href="Content/mobile.css" rel="stylesheet" type="text/css" media="screen and (max-width: 480px)" /> <link href="Content/mobile.css" rel="stylesheet" type="text/css" media="screen and (max-device-width: 480px)" />
      
      







ここで、画面の幅が480ピクセル未満の堎合、次のスタむルが远加されるこずがわかりたす。

  #legal { font-size: 8px; } #title { font-size: 30px !important; } #waitText { font-size: 12px; } #bolasLogo { width: 48px; height: 48px; } #pictureCell { width: 48px; }
      
      







このスタむルは、ブラりザの幅が480ピクセル未満の堎合でもたずえば、Windows Phoneの堎合、タむトルのサむズを瞮小し、サむトを衚瀺したたたにしたす。





おわりに



HTML5 / CSS 3 / JavaScriptおよびVisual Studio 2010を䜿甚するず、ハヌドりェアアクセラレヌションレンダリングなどの優れた機胜を備えたHTML5をサポヌトするブラりザヌ内でポヌタブルで効率的な゜リュヌションを開発できたす。

このタむプの開発は、jQueryなどのフレヌムワヌクを䜿甚するこずにより簡玠化されたす。



結論ずしお、私は䜕かを確認するためにそれを蚀う-あなたはそれを詊しおみる必芁がある



All Articles