Piękny kod to radość pisania, ale trudno jest dzielić się tą radością z innymi programistami, nie mówiąc o nie-programistach. W czasie wolnym od pracy i czasu rodzinnego bawiłam się pomysłem na wiersz programistyczny, wykorzystując element canvas do rysowania w przeglądarce. Istnieje wiele terminów opisujących eksperymenty wizualne na komputerze, takich jak grafika, szkic kodu, demo i sztuka interaktywna, ale ostatecznie zdecydowałem się na programowanie wiersza, aby opisać ten proces. Ideą wiersza jest dopracowany kawałek prozy, który łatwo się udostępnia, jest zwięzły i estetyczny. To nie jest na wpół ukończony pomysł w szkicowniku, ale spójny utwór przedstawiony widzowi dla przyjemności. Wiersz nie jest narzędziem, ale istnieje, by wywołać emocje.

Dla własnej przyjemności czytałem książki o matematyce, obliczeniach, fizyce i biologii. Nauczyłem się bardzo szybko, że kiedy wdycham pomysł, ludzie szybko nudzą. Wizualnie mogę wziąć niektóre z tych pomysłów, które uważam za fascynujące i szybko dać każdemu poczucie cudowności, nawet jeśli nie rozumieją teorii stojącej za kodem i koncepcjami, które go napędzają. Nie potrzebujesz rękojeści z żadnej twardej filozofii czy matematyki, żeby napisać wiersz programistyczny, tylko chęć zobaczenia czegoś żywego i oddychania na ekranie.

Kod i przykłady, które zestawię poniżej, pomogą w zrozumieniu, jak właściwie zrealizować ten szybki i wysoce satysfakcjonujący proces. Jeśli chcesz podążać za kodem, możesz pobierz tutaj pliki źródłowe.

Podstawową sztuczką podczas tworzenia wiersza jest zachowanie lekkości i prostoty. Nie spędzaj trzech miesięcy na budowaniu naprawdę fajnego demo. Zamiast tego utwórz 10 wierszy, które rozwijają ideę. Napisz eksperymentalny kod, który jest ekscytujący i nie bój się zawieść.

Wprowadzenie do płótna

Aby uzyskać szybki przegląd, płótno jest zasadniczo 2-bitowym elementem obrazu bitmapowym, który żyje w DOM, który można narysować. Rysowanie można wykonać za pomocą kontekstu 2d lub kontekstu WebGL. Kontekst jest obiektem JavaScript, którego używasz, aby uzyskać dostęp do narzędzi do rysowania. Zdarzenia JavaScript dostępne na płótnie są bardzo proste, w przeciwieństwie do tych dostępnych dla SVG. Każde wywołane zdarzenie dotyczy elementu jako całości, a nie niczego rysowanego na płótnie, tak jak normalnego elementu obrazu. Oto podstawowy przykład na płótnie:

var canvas = document.getElementById('example-canvas');var context = canvas.getContext('2d');//Draw a blue rectanglecontext.fillStyle = '#91C0FF';context.fillRect(100, // x100, // y400, // width200 // height);//Draw some textcontext.fillStyle = '#333';context.font = "18px Helvetica, Arial";context.textAlign = 'center';context.fillText("The wonderful world of canvas", // text300, // x200 // y);

Rozpoczęcie pracy jest całkiem proste. Jedyną rzeczą, która może być trochę zagmatwana, jest to, że kontekst musi być skonfigurowany z ustawieniami, takimi jak fillStyle, lineWidth, font i strokeStyle, zanim zostanie użyte faktyczne wywołanie losowania. Łatwo zapomnieć o aktualizacji lub zresetowaniu tych ustawień i uzyskać niezamierzone wyniki.

Robienie rzeczy

Pierwszy przykład wykonano tylko raz i narysowano statyczny obraz na płótnie. To jest w porządku, ale kiedy naprawdę robi się fajnie, to jest aktualizowane z prędkością 60 klatek na sekundę. Nowoczesne przeglądarki mają wbudowaną funkcję requestAnimationFrame, która synchronizuje niestandardowy kod rysunkowy z cyklami rysowania w przeglądarce. Pomaga to pod względem wydajności i gładkości. Celem wizualizacji powinien być kod, który szumi wzdłuż 60 klatek na sekundę.

(Uwaga na temat wsparcia: dostępne są niektóre proste wieloramienne, jeśli potrzebujesz obsługi starszych przeglądarek).

var canvas = document.getElementById('example-canvas');var context = canvas.getContext('2d');var counter = 0;var rectWidth = 40;var rectHeight = 40;var xMovement;//Place rectangle in the middle of the screenvar y = ( canvas.height / 2 ) - ( rectHeight / 2 );context.fillStyle = '#91C0FF';function draw() {//There are smarter ways to increment time, but this is for demonstration purposescounter++;//Cool math below. More explanation in the text following the code.xMovement = Math.sin(counter / 25) * canvas.width * 0.4 + canvas.width / 2 - rectWidth / 2;//Clear the previous drawing resultscontext.clearRect(0, 0, canvas.width, canvas.height);//Actually draw on the canvascontext.fillRect(xMovement,y,rectWidth,rectHeight);//Request once a new animation frame is available to call this function againrequestAnimationFrame( draw );}draw();

To miłe, że jest trochę więcej wewnętrznej struktury kodu, ale tak naprawdę nie robi nic o wiele bardziej interesującego. W tym miejscu pojawia się pętla. W obiekcie sceny utworzymy nowy obiekt DotManager . Przydatne jest zbieranie tej funkcjonalności w oddzielnym obiekcie, ponieważ jest łatwiejsze i bardziej przejrzyste, ponieważ coraz więcej złożoności zostaje dodane do symulacji.

var DotManager = function( numberOfDots, scene ) {this.dots = [];this.numberOfDots = numberOfDots;this.scene = scene;for(var i=0; i < numberOfDots; i++) {this.dots.push( new Dot(Math.random() * this.canvas.width,Math.random() * this.canvas.height,this.scene));}};DotManager.prototype = {update : function( dt ) {for(var i=0; i < this.numberOfDots; i++) {this.dots[i].update( dt );}}};

Teraz w scenie, zamiast tworzyć i aktualizować Dot , tworzymy i aktualizujemy DotManager . Stworzymy 5000 punktów, aby rozpocząć.

function Scene() {...this.dotManager = new DotManager(5000, this);...};Scene.prototype = {...update : function( dt ) {this.dotManager.update( dt );}...};

Dla każdej nowej tworzonej kropki, weź jej początkową pozycję i ustaw jej odcień w miejscu, w którym znajduje się wzdłuż szerokości płótna. Funkcja Utils.hslToFillStyle jest małą funkcją pomocniczą, którą dodałem, aby przekształcić niektóre zmienne wejściowe w poprawnie sformatowany ciąg fillStyle . Już rzeczy wyglądają bardziej ekscytująco. Kropki ostatecznie zlewają się ze sobą i tracą efekt tęczy, kiedy mają czas na rozproszenie. Ponownie, jest to przykład jazdy wizualnej z odrobiną matematyki lub zmiennych danych wejściowych. Naprawdę lubię robić kolory za pomocą modelu kolorów HSL z generatywną sztuką zamiast RGB ze względu na łatwość użycia. RGB jest trochę abstrakcyjne.

Interakcja użytkownika za pomocą myszy

Do tej pory nie było żadnej rzeczywistej interakcji użytkownika.

var Mouse = function( scene ) {this.scene = scene;this.position = new THREE.Vector2(-10000, -10000);$(window).mousemove( this.onMouseMove.bind(this) );};Mouse.prototype = {onMouseMove : function(e) {if(typeof(e.pageX) == "number") {this.position.x = e.pageX;this.position.y = e.pageY;} else {this.position.x = -100000;this.position.y = -100000;}}};

Ten prosty obiekt zawiera logikę aktualizacji myszy od reszty sceny. Aktualizuje on tylko wektor pozycji w ruchu myszy. Reszta obiektów może następnie pobierać próbki z wektora pozycji myszy, jeśli zostaną przekazane odniesienie do obiektu. Jedyne zastrzeżenie, które tutaj ignoruję, to to, że szerokość płótna nie jest równa jeden do jednego z wymiarami pikselowymi DOM, tj. Płótno o zmiennej rozdzielczości lub płótno o wyższej gęstości pikseli (siatkówka) lub jeśli płótno nie znajduje się w lewy górny. Współrzędne myszy muszą być odpowiednio dostosowane.

var Scene = function() {...this.mouse = new Mouse( this );...};

Jedyne, co pozostało myszy, to stworzenie obiektu myszy wewnątrz sceny. Teraz, gdy mamy mysz, przyciągnijmy do niej kropki.

function Dot( x, y, scene ) {...this.attractSpeed = 1000 * Math.random() + 500;this.attractDistance = (150 * Math.random()) + 180;...}

Dodałem kilka wartości skalarnych do kropki, aby każdy z nich zachowywał się nieco inaczej w symulacji, aby nadać mu nieco realizmu. Baw się z tymi wartościami, aby uzyskać inne odczucie. Teraz przejdź do metody przyciągania myszy. Trochę długo trwa z komentarzami.

attractMouse : function() {//Again, create some private variables for this methodvar vectorToMouse = new THREE.Vector2(),vectorToMove = new THREE.Vector2();//This is the actual public methodreturn function(dt) {var distanceToMouse, distanceToMove;//Get a vector that represents the x and y distance from the dot to the mouse//Check out the three.js documentation for more information on how these vectors workvectorToMouse.copy( this.scene.mouse.position ).sub( this.position );//Get the distance to the mouse from the vectordistanceToMouse = vectorToMouse.length();//Use the individual scalar values for the dot to adjust the distance movedmoveLength = dt * (this.attractDistance - distanceToMouse) / this.attractSpeed;//Only move the dot if it's being attractedif( moveLength > 0 ) {//Resize the vector to the mouse to the desired move lengthvectorToMove.copy( vectorToMouse ).divideScalar( distanceToMouse ).multiplyScalar( moveLength );//Go ahead and add it to the current position now, rather than in the draw callthis.position.add(vectorToMove);}};}()

Ta metoda może być nieco myląca, jeśli nie jesteś na bieżąco z matematyki wektorowej. Wektory mogą być bardzo wizualne i mogą pomóc, jeśli wyciągniesz trochę literki na poplamionym kawą skrawku papieru. W prostym języku angielskim ta funkcja uzyskuje odległość między myszą a kropką. Następnie przesuwa kropkę nieco bliżej kropki, w zależności od tego, jak blisko jest już kropka i czas, który upłynął. Robi to, określając odległość do poruszania się (normalna liczba skalarna), a następnie mnoży ją przez znormalizowany wektor (wektor o długości 1) kropki wskazującej na mysz. Ok, to ostatnie zdanie niekoniecznie było po angielsku, ale to dopiero początek.