Статьи

Рисование с Two.js

В наши дни продвинутая графика является важной частью Интернета, но в миксе есть несколько разных рендеров. Вы могли бы использовать холст, конечно; но SVG и WebGL тоже варианты. В этом уроке мы рассмотрим относительно новую библиотеку рисования two.js , которая предоставляет один API, который делает то же самое со всеми тремя этими средствами визуализации. Если вы готовы, давайте проверим это!


Первым шагом является создание two экземпляров и размещение их на странице. Two contstructor принимает объект с рядом параметров:

1
2
3
var two = new Two({
   fullscreen: true
});

В этом случае мы используем fullscreen режим, который заставляет область рисования занимать все окно браузера. Если бы мы хотели, чтобы наша область рисования имела определенный размер, мы могли бы вместо этого использовать свойства width и height ; они оба принимают число за значение пикселя. Также есть параметр autostart ; если для этого параметра установлено значение true, анимация будет запускаться сразу после загрузки страницы.

Также есть параметр type : он решает, какой рендерер будет использоваться. Вы можете выбрать между canvas, SVG и WebGl. Однако вы не просто вводите имя: вы используете библиотечную константу своего рода: Two.Types.canvas , Two.Types.svg или Two.Types.webgl . Просто для ясности, two.js будет просто использовать SVG по умолчанию; он не обнаруживает какие-либо функции, чтобы увидеть, что будет поддерживать браузер. Вы должны будете сделать это самостоятельно (и я думаю, что это хорошая идея: маленькие инструменты, одна вещь, и все такое).

Итак, когда у нас есть экземпляр Two , что нам с ним делать. Сначала вы захотите добавить его на страницу. У него есть метод appendTo который принимает HTML-элемент в качестве параметра, поэтому давайте appendTo это:

1
2
3
<div id=»main»></div>
<script src=»./two.min.js»></script>
<script src=»./main.js»></script>

Затем в main.js мы начнем с этого:

1
2
3
4
5
6
var el = document.getElementById(«main»),
    two = new Two({
        fullscreen: true
    });
 
two.appendTo(el);

Со всеми этими настройками мы готовы нарисовать несколько фигур.


Мы начнем с основных форм; в то время как мы можем создавать наши собственные сложные формы с помощью new Two.Polygon , самые простые формы можно создать несколькими удобными методами.

Начнем с кругов. Функция makeCircle принимает три параметра:

1
2
3
4
var circle = two.makeCircle(110, 110, 100);
circle.fill = «#881111»;
 
two.update();

Мы рассмотрим снизу вверх: вызов обновлений two.update является областью рисования и фактически отображает содержимое. Возвращаясь к кругу, первые два параметра — это координаты x и y для центра круга. Тогда третий параметр — это радиус круга. Все функции two.make... возвращают объект Two.Polygon . Когда мы пройдем этот урок, вы увидите несколько свойств и методов, которые вы можете использовать для этих фигур. Вот первое: fill . Как вы можете догадаться, он устанавливает цвет заливки: подойдет любой действующий CSS.

Результат должен выглядеть так:

А как насчет прямоугольников? Метод two.makeRectangle принимает четыре параметра. Так же, как круг, первые два параметра отмечают координаты x и y для центра прямоугольника. Тогда третий параметр — это width а четвертый — height прямоугольника.

1
2
3
4
5
6
var rect = two.makeRectangle(115, 90, 150, 100);
rect.fill = «orange»;
rect.opacity = 0.25;
rect.noStroke();
 
two.update();

Опять же, мы используем свойство fill . Мы также используем свойство opacity , которое принимает десятичное значение от 0 до 1; у нас тут четверть непрозрачности. Наконец, мы используем метод noStroke , который удаляет обводку (рамку) из прямоугольника. Вот что мы имеем:

Эллипсы тоже довольно просты: как вы можете догадаться, первые два параметра задают центр эллипса. Тогда у нас есть ширина и высота:

1
2
3
4
5
6
var ellipse = two.makeEllipse(100, 40, 90, 30);
ellipse.stroke = «#112233»;
ellipse.linewidth = 5;
ellipse.noFill();
 
two.update();

Для новых свойств: у нас есть stroke , который устанавливает цвет границы; чтобы установить ширину этой границы, мы используем свойство linewidth . Тогда помните noStroke ? Метод noFill такой же, за исключением того, что удаляет цвет заливки для нашей фигуры (без этого наши фигуры по умолчанию имеют белую заливку).

Конечно, самые простые формы — это линии.

1
2
3
var line = two.makeLine(10, 10, 110, 210);
line.linewidth = 10;
line.stroke = «rgba(255, 0, 0, 0.5)»;

Первые два параметра — это x и y для одного конца строки; второй набор для другого конца.

Вероятно, самая неудобная форма для создания — это кривая. Метод two.makeCurve принимает столько наборов параметров x, y сколько вам нужно — каждая пара является точкой, где линия изгибается. Тогда последний параметр является логическим: сделайте его true если фигура открыта, то есть концы не соединяются. Если вы хотите, чтобы two.js рисовал линию, соединяющую два конца кривых, это должно быть false .

1
2
3
4
5
var curve = two.makeCurve(110, 100, 120, 50, 140, 150, 160, 50, 180, 150, 190, 100, true);
curve.linewidth = 2;
curve.scale = 1.75;
curve.rotation = Math.PI / 2;
curve.noFill();

Вы знаете linewidth , но как насчет scale ? Мы можем использовать это, чтобы уменьшить или расширить нашу форму; здесь мы расширяем форму на 175%. Затем мы можем использовать rotation чтобы вращать нашу форму на несколько радиан; мы делаем 90 градусов, то есть полу-пи радианы.

Наконец, вы можете подумать, что, поскольку мы сделали форму открытой, мы не получим наполнения; но это не правда Незакрытая кривая все равно будет иметь заливку, поэтому мы используем noFill для удаления заливки и в итоге noFill только кривую.

Последний тип фигуры является универсальным: это общий многоугольник. На самом деле, это почти как кривая, за исключением того, что линии идут прямо из точки в точку.

1
2
3
4
5
var poly = two.makePolygon(110, 100, 120, 50, 140, 150, 160, 50, 180, 150, 190, 100);
poly.linewidth = 4;
poly.translation = new Two.Vector(60, 60);
poly.stroke = «#cccccc»;
poly.fill = «#ececec»;

Как и в случае с кривой, у нас есть столько пар координат, сколько мы хотели бы, а затем открытое логическое значение; здесь мы устанавливаем значение false , поэтому форма будет закрыта.

Мы также устанавливаем translation здесь; это позволяет нам перемещать форму влево или вправо и вверх или вниз. Мы устанавливаем свойство translation для экземпляра Two.Vector . Конструктор Two.Vector принимает два параметра: x и y . В итоге они становятся координатами центра фигуры. Вам на самом деле не нужно создавать новый вектор для этого; Вы можете просто назначить каталог значений x и y :

1
2
poly.translation.x = 60;
poly.translation.y = 60;

Вот что мы получаем:


До сих пор мы работали с отдельными объектами формы; однако возможно сгруппировать фигуры и взаимодействовать с ними как одним целым.

Вы можете создать группу с two.makeGroup метода two.makeGroup . Затем мы можем использовать его метод add для добавления фигуры в группу.

01
02
03
04
05
06
07
08
09
10
11
var group = two.makeGroup(),
    rect = two.makeRectangle(0, 0, 100, 100),
    circ = two.makeCircle(50, 50, 50);</p>
 
rect.fill = «red»;
circ.fill = «blue»;
 
group.add(rect);
group.add(circ);
 
two.update();

Если вы запустите это, это довольно просто; точно так же, как вы получите без group битов.

Но тогда мы можем работать с группой, используя любые преобразования, которые мы можем сделать для индивидуальной фигуры. Например, как насчет перевода?

1
2
3
4
group.translation.x = 100;
group.translation.y = 100;
 
two.update();

Как и в случае с обычными формами, группы упорядочены по всей длине по мере их создания. Однако, если вы добавите фигуру в одну группу, а затем в другую группу, она будет удалена из первой группы. Это замечательно, если вам нужно изменить передний-задний порядок фигур при анимации (к этому мы вернемся). Итак, если мы начнем с этого:

01
02
03
04
05
06
07
08
09
10
11
var topGroup = two.makeGroup(),
    bottomGroup = two.makeGroup(),
    rect = two.makeRectangle(100, 100, 100, 100),
    circ = two.makeCircle(150, 150, 50);
rect.fill = «red»;
circ.fill = «blue»;
 
topGroup.add(rect);
topGroup.add(circ);
 
two.update();

У нас так же, как указано выше:

Но если мы добавим rect в bottomGroup . , ,

1
bottomGroup.add(rect);

Теперь наша площадь находится на вершине.

Наконец, давайте поговорим об анимации. Вы уже знаете, что two.js отображает формы, которые вы создали, когда вызываете two.update() . Если вы вместо этого вызываете two.play() , это все равно что неоднократно вызывать update() , используя Request Animation Frame . Каждый раз, когда это происходит, two.js запускает событие «update». Вот как мы можем создавать анимацию: прислушиваться к событию «обновление»; и когда это произойдет, запустите функцию, чтобы установить следующий кадр.

Наши примеры до сих пор были довольно простыми, поэтому давайте сделаем шаг вперед: мы создадим орбитальную планету с собственной орбитальной луной. Помните, мы начинаем с создания двух экземпляров:

1
2
3
4
var el = document.getElementById(«main»),
    two = new Two({
        fullscreen: true
    }).appendTo(el);

Далее нам нужно настроить несколько переменных.

1
2
3
4
5
6
7
8
var earthAngle = 0,
    moonAngle = 0,
    distance = 30,
    radius = 50,
    padding = 100,
    orbit = 200,
    offset = orbit + padding,
    orbits = two.makeGroup();

Мы будем увеличивать earthAngle и moonAngle чтобы наша планета и луна moonAngle вокруг своих орбит. Переменная distance — это то, как далеко наша луна будет от нашей земли. radius — это лучи нашей планеты Земля, а padding — это количество пространства, которое наша планета будет иметь за пределами своей орбиты. Указанная орбита происходит от переменной orbit . Переменная offset — это то, как далеко наша планета будет смещена от края холста. Наконец, группа orbits будет содержать два круга орбит, что позволит нам показать или скрыть их по желанию. Не волнуйтесь, если вы немного смущены; Вы увидите, как они все работают вместе в секунду.

Начнем с линии земной орбиты. Конечно, это просто круг:

1
2
3
4
5
6
7
var earthOrbit = two.makeCircle(offset, offset, orbit);
earthOrbit.noFill();
earthOrbit.linewidth = 4;
earthOrbit.stroke = «#ccc»;
orbits.add(earthOrbit);
 
two.update();

Здесь нет ничего нового. Вот что вы должны увидеть:

Затем нам нужно создать планету и разместить ее на своей орбите. Во-первых, нам нужны средства, чтобы выяснить, где на орбите должна быть размещена планета; и, конечно, это нужно изменить для каждого кадра анимации. Итак, давайте создадим функцию, которая будет возвращать координаты центра x и y для орбиты на основе текущего угла для позиционирования вокруг круга и радиуса орбиты:

1
2
3
4
5
6
function getPositions(angle, orbit) {
    return {
        x: Math.cos(angle * Math.PI / 180) * orbit,
        y: Math.sin(angle * Math.PI / 180) * orbit
    };
}

Да, это немного тригонометрия, но не беспокойтесь слишком сильно: в основном, мы конвертируем угол (который является градусом) в радиан, используя методы синуса и косинуса JavaScript, а затем умножаем его на orbit , Теперь мы можем использовать эту функцию, чтобы добавить землю к изображению:

1
2
3
4
5
6
var pos = getPositions(earthAngle++, orbit),
    earth = two.makeCircle(pos.x + offset, pos.y + offset, radius);
 
earth.stroke = «#123456»;
earth.linewidth = 4;
earth.fill = «#194878»;

Мы начинаем с получения позиции для первого earthAngle (значение 0, помните?); затем мы строим нашу earth на основе этих позиций (плюс смещение) и раскрашиваем ее. Вот что мы получаем в итоге:

Теперь давайте оживим эту планету. Код привязки к событию на самом деле идет прямо из Backbone, поэтому он может выглядеть знакомо:

1
2
3
4
5
6
7
two.bind(«update», function (frameCount) {
    var pos = getPositions(earthAngle++, orbit);
    earth.translation.x = pos.x + offset;
    earth.translation.y = pos.y + offset;
});
 
two.play();

Здесь происходит то, что каждый раз, когда происходит событие update , мы используем функцию getPositions чтобы вычислить позицию для следующего угла на земле. Тогда нам просто нужно установить центр земли на эти новые позиции плюс смещение. Наконец, мы вызываем two.play() для запуска событий обновления. Если вы перезагрузите страницу сейчас, вы увидите, что Земля вращается вокруг орбиты.

Отличная работа, а? А как насчет Луны и ее орбитального пути? это пойдет выше заявления bind .

01
02
03
04
05
06
07
08
09
10
11
var moonOrbit = two.makeCircle(earth.translation.x, earth.translation.y, radius + distance);
moonOrbit.noFill();
moonOrbit.linewidth = 4;
moonOrbit.stroke = «#ccc»;
orbits.add(moonOrbit);
 
var pos = getPositions(moonAngle, radius + distance),
    moon = two.makeCircle(earth.translation.x + pos.x, earth.translation.y + pos.y, radius / 4);
 
moonAngle += 5;
moon.fill = «#474747»;

Это очень похоже на код для планеты: мы центрируем круг орбиты Луны в центре Земли, используя ее свойства translation ; его радиус — это радиус Земли плюс расстояние, на которое Луна должна быть от Земли. Мы снова добавляем moonOrbit в группу orbits .

Затем мы создаем луну, сначала получая ее желаемое положение и создавая круг в этом месте. Для радиуса мы будем использовать четверть радиуса, который мы использовали для земли. Мы будем каждый раз улучшать угол наклона Луны на 5, поэтому он будет двигаться быстрее Земли.

Отключив анимацию (комментируя оператор two.bind ), мы получим следующее:

Последний шаг: оживи луну. Внутри того же оператора two.bind добавьте следующие строки:

1
2
3
4
5
6
7
var moonPos = getPositions(moonAngle, radius + distance);
moon.translation.x = earth.translation.x + moonPos.x;
moon.translation.y = earth.translation.y + moonPos.y;
moonAngle += 5;
 
moonOrbit.translation.x = earth.translation.x;
moonOrbit.translation.y = earth.translation.y;

Как и раньше, мы получаем новое положение Луны и позиционируем его относительно Земли. Затем мы также перемещаем кольцо орбиты Луны так, чтобы оно оставалось центрированным на Земле.

Со всем этим на месте, наш маленький пример завершен: Вот еще снимок действия:

Как я уже сказал, мы также можем скрыть орбиты. Так как они оба находятся в группе orbits , мы можем использовать свойство visible группы:

1
orbits.visible = false;

И сейчас:


Ну, это завершение этого урока. Как вы думаете, вы будете использовать two.js в своих проектах? Или, может быть, у вас есть лучшая альтернатива? Давайте послушаем об этом в комментариях!