Spis treści:

Kategoria:HTMLJavaScriptCanvas


Wykrywanie obiektów w elemencie canvas w HTML5

Zarządzanie własnymi obiektami

Wiemy juz, z poprzednich wpisów, że element canvas daje nam dużą swobodę. Można rysować w zasadzie wszystko, czego zapragniemy i w miejscu, które sobie wymarzymy. Samo rysowanie to jednak nie wszystko - jeszcze większe możliwości daje jakaś analiza narysowanych elementów. Możemy narysować graf a następnie przeanalizować go pod kątem długości ścieżek, możemy narysować mapę i pozwolić użytkownikowi wskazać na niej jakieś konkretne miejsca, możemy stworzyć prostą grę i interpretować kliknięcie myszy jako strzał. Każdy z tych scenariuszy wymaga określenia pozycji jakiegoś elementu względem kursora myszy, a być może względem innego elementu (lub układu takich elementów). Zastosowania mogą być różne, a sposób rozwiązania problemu wystarczy jeden. To spróbuję dzisiaj pokazać.

Wykrywanie obiektów geometrycznych

Przykłady z elementem canvas stają się coraz bardziej złożone, a zastosowania coraz bardziej rozległe. Przejdźmy zatem do rzeczy. Celem przykładu jest pokazanie, jak określić moment najechania kursorem myszy na jeden z obiektów. Po najechaniu obiekt zostanie wyróżniony. Nic nie stoi na przeszkodzie, aby w momencie kliknięcia wykonać na nim jakąś akcję, ale o tym innym razem. Żeby pokazać pewną spójną część całego rozwiązania zastosuję trzy różne obiekty: kwadrat, koło i punkt. Przyjrzyjmy się najpierw końcowemu efektowi a następnie, krok po kroku, pokażę jak to jest zrobione:

Rys 1. Wykrywanie obiektów geometrycznych w elemencie canvas.

Po najechaniu wskaźnikiem myszy element powinien się podświetlić. Jeżeli trzeba wygenerować inny układ elementów, wystarczy odświeżyć stronę.

Zasada działania aplikacji

Zasada działania jest prosta i można ją opisać w kilku krokach:

  • Rysujemy 10 losowo wybranych elementów graficznych (kwadrat, koło, punkt) w losowych miejscach.
  • Nasłuchujemy zdarzeń ruchu myszką.
  • Jeżeli myszka znajduje się nad elementem graficznym, oznaczamy go zmieniając jego kolor.

Aplikacja ma wiele wspólnego z pokazaną wcześniej internetową wersją Painta (Rysowanie myszką po canvas w HTML5). Tak jak we wskazanym artykule przydadzą się wymiary elementu canvas, podobny jest sposób określania współrzędnych myszy, taki sam jest sposób pobierania kontektu 2D, na którym mozemy rysować. Tak jak tam, cała akcja zaczyna się po załadowaniu strony i pełnym rozmieszczeniu wszystkich elementów HTML. Nie będę po raz kolejny omawiał tych samych zagadnień - jeżeli coś okaże się niejasne, warto zajrzeć pod wskazane hiperłącze. Gdyby i tam odpowiedzi nie było, można skorzystać z komentarza. Przejdźmy zatem do najważniejszego, do kodu. Wiem, że wiele osób tylko na to czeka.

<html>
<head><title>Wykrywanie obiektów w elemencie canvas w HTML5</title></head>
<body>
<h2>Wykrywanie narysowanych obiektów geometrycznych</h2>
<canvas id="paintwidth="320height="320/>
<script type="text/javascript">
var cName = "paint";
var canvas = document.getElementById(cName);
var ctx = canvas.getContext("2d");
var mouse = new Object();
var controlBounds;
var items;

//Sekcja obsługi koła
function Circle(x,y,r){
  this.X = x;
  this.Y = y;
  this.R = r;
}
//Funkcja definiująca relację zawierania
Circle.prototype.Contains = function(x,y){
  return Math.sqrt(Math.pow(x - this.X, 2) + Math.pow(y - this.Y, 2)) < this.R;
}
//Funckja generująca losowy obiekt umieszczony losowo
//Współrzędne zawierają się w przedziale 0-x i 0-y
Circle.prototype.Random = function(x,y){
  return new Circle(Math.floor(Math.random()*x),
                    Math.floor(Math.random()*y),
                    Math.floor(Math.random()*15)+10);
}
//Funkcja rysująca obiekt
Circle.prototype.Draw = function(){
  ctx.beginPath();
  if (this.Contains(mouse.X, mouse.Y))
    ctx.fillStyle = "rgb(200,0,0)";
  else
    ctx.fillStyle = "rgb(0,0,100)";
  ctx.arc(this.X,this.Y,this.R,0,2*Math.PI);
  ctx.fill();
}

//Sekcja obsługi kwadratu
function Square(x,y,width,height){
  this.X = x;
  this.Y = y;
  this.Width = width;
  this.Height = height;
}
Square.prototype.Contains = function(x,y){
  return x > this.X && y > this.Y &&
    x < this.X + this.Width && y < this.Y + this.Height;
}
Square.prototype.Random = function(x,y){
  var size = Math.floor(Math.random()*30)+20;
  return new Square(Math.floor(Math.random()*x)-size/2,
                    Math.floor(Math.random()*y)-size/2,
                    size, size);
}
Square.prototype.Draw = function(){
  if (this.Contains(mouse.X, mouse.Y))
    ctx.fillStyle = "rgb(200,0,0)";
  else
    ctx.fillStyle = "rgb(0,100,0)";
  ctx.fillRect(this.X,this.Y,this.Width,this.Height);
}

//Punkt definiujemy jako małe kółko
function Point(x,y){
  return new Circle(x,y,3);
}

//Kontrolka posiada współrzędne X, Y, szerokość i wysokość
function Control(c){
  var e = document.getElementById(c);
  //ustaw pola obiektu
  this.X = e.offsetLeft;
  this.Y = e.offsetTop;
  this.Width = e.width;
  this.Height = e.height;
}

//Zdarzenie poruszania myszą, aktywne tylko wtedy,
//gdy poruszamy się wewnątrz elementu canvas
mouse.Move = function(e){
  mouse.X = e.offsetX;
  mouse.Y = e.offsetY;
  DrawAll();
};

//Wygeneruj losowe obiekty mieszczęce się w elemencie canvas
InitItems = function(){
  items = new Array();
  for (var i=0; i<10; i++){
    var obj = Math.floor(Math.random()*3);
    if (obj == 0)
      items[i] = Circle.prototype.Random(controlBounds.Width, controlBounds.Height);
    else if (obj == 1)
      items[i] = Square.prototype.Random(controlBounds.Width, controlBounds.Height);
    else
      items[i] = new Point(Math.floor((Math.random()*controlBounds.Width)+1),
                           Math.floor((Math.random()*controlBounds.Height)+1));
  }
}
//Narysuj wszystkie elementy
function DrawAll(){
  for (var i=0; i<10; i++)
    items[i].Draw();
}

//Zaczynamy zabawę: nasłuchujemy zdarzeń związanych z poruszaniem się myszy
canvas.addEventListener("mousemove", mouse.Move, false);
window.addEventListener("load"function(){
  controlBounds = new Control(cName);
  InitItems();
  DrawAll();
  }, false);
</script>
</body>
</html>

Kodu jest więcej niż zwykle, ale można w nim wyróżnić pewne powtarzające się elementy. To właśnie przez obecność różnych obiektów geometrycznych. Przejdźmy teraz do wyjaśnień.

Jak to działa?

Kluczowe fragmenty aplikacji opatrzone są komentarzami, ale warto im się dokładniej przyjrzeć. Po pierwsze, każdy obiekt (kwadrat, koło) zawiera dwie podstawowe metody:

  • Contains - określa, czy obiekt zawiera przekazany w postaci argumentów punkt.
  • Draw - rysuje wskazany obiekt na elemencie canvas.

Warto wspomnieć nieco o obiekcie punktu - uznałem, że jest on za mały, aby mógł być godnie reprezentowany graficznie. Punkt jest wobec tego pokazany jako małe koło.

Oprócz wskazanych metod każdy obiekt posiada również metodę Random. Jest to metoda pomocnicza do generowania losowego obiektu w losowym miejscu - oczywiście w ramach obszaru zajmowanego przez element canvas. To, że każdy z obiektó posiada taki sam zestaw metod pozwala nam ujednolicić sposób zarządzania tymi obiektami. Nie ma znaczenia, z jakim obiektem mamy do czynienia - zwyczajnie wywołujemy na jego rzecz metodę Contains lub Draw i pozwalamy samemu obiektowi decydować. To pewnego rodzaju polimorfizm (wielopostaciowość), pojęcie znane doskonale w językach obiektowych, rzadko stosowane w odniesieniu do języka JavaScript. Uważam jednak, że w tym przypadku doskonale pasuje do zastosowanej techniki. Technikę widać doskonale w funckji DrawAll() - wywołuje ona metody Draw() kolejnych obiektów nie pytając ich o tożsamość. Nie ma osobnych tablic, nie ma osobnych funkcji, a zarządzanie silnikiem graficznym znajduje się w jednym miejscu. Warto tę technikę zapamiętać, bo będę ją często wykorzystywał. Im bardziej rozbudowana aplikacja, tym bardziej sprawdzają się takie drobne triki.

Powiem jeszcze co nieco na temat zawierania - pokazane implementacje wynikają z prostej, logicznej zależności (kwadrat) lub równie prostej, matematycznej zależności (koło). Rozszerzając zakres obiektów również warto poszukać odpowiednich wzorów. Elipsa, trójkąt - te figury mają jakiś obszar, każdy wielokąt można wyrazić za pomocą skończone liczby trójkątów, linia - podobnie jak dla punktu można zastosować pewien margines. Obrazek - taki obrazek najczęściej jest prostokątem, a szczegółowe badanie zawierania może dotyczyć również poszczególnych pikseli zawartych w obrazku (tylko widocznych). Zawartość obrazka można naturalnie reprezentować przez kilka prostych figur - trójkątów, elips. O ile starczy czasu, postaram się temat rozbudować, pokazując to wszystko na konkretnym przykładzie. W chwili obecnej zachęcam do eksperymentowania i dzielenia się spostrzeżeniami w komentarzach.

Kategoria:HTMLJavaScriptCanvas

, 2013-12-20

Komentarze:

Traper (2016-06-27 12:56:45)
Cóż, moim zdaniem robiąc taki tutorial powinno ograniczyć się do minimum przykład. Czyli jedno kółko narysowane 'ręcznie', oraz jeden kwadrat. Przez to kod traci na czytelności dla początkujących programistów i nie wiadomo co jest co.
Dodaj komentarz
Wyślij
Ostatnie komentarze
bardo ciekawe , można dzięki takim wpisom dostrzec wędkę..
Bardzo dziękuję za jasne tłumaczenie z dobrze dobranym przykładem!
Dzieki za te informacje. były kluczowe
Dobrze wyjaśnione, dzięki !