Хотя SVG существует уже некоторое время, его принятие в качестве части HTML5 дало ему новую жизнь. Чтобы отпраздновать этот факт, в этой серии пошаговых инструкций было исследовано, что произойдет, если мертвые также получат новую жизнь, создав предсказатель выживания после зомби-апокалипсиса, созданный почти полностью из SVG и JavaScript.
В третьей части этой серии кричащий человек в центре этого предсказателя был окружен опасностью со всех сторон. JavaScript впервые использовался для управления как Core DOM, так и SVG DOM, добавляя десятки новых зомби, а затем переключая их с помощью атрибута «transform».
В этой заключительной части серии снова будет использоваться JavaScript, на этот раз, чтобы бросить человеку привязь надежды в виде торговых центров и быдла. Будет ли этого достаточно? Оставайтесь с нами до конца, когда JavaScript также используется для вычисления шансов выживания человека, а затем для установки индикатора прогноза, анализируя реальные возможности SVG браузера и действуя соответствующим образом.
Примечание. Это пошаговое руководство основано на законченном коде части 3 данной серии, который можно найти в качестве источника http://justinwhitney.com/zombies/zombies_part3.htm .
Добавление дополнительных элементов SVG
В третьей части этой серии было показано, как анимировать один из шести элементов управления приращением / уменьшением холста панели управления: элемент управления «Больше зомби». Это не будет делать вообще. Контроль населения также нуждается в меньшем количестве зомби. Но прежде чем мы это сделаем, нужно добавить другие изображения SVG. Человек нуждается в защите.
Начните с торговых центров. Для простоты предположим, что в этом городе есть максимум четыре торговых центра, которые будут размещены в четырех углах. Поскольку необходимо отслеживать количество торговых центров, добавьте глобальный массив для торговых центров в верхней части раздела <script>:
<script>
var malls = new Array();
…
Код newMall установит координаты x, y для нового элемента SVG на основе количества торговых центров в массиве с учетом 100 × 100 измерений самого изображения торгового центра. Остальная часть кода по сути такая же, как и в коде newZombie, с добавлением, что новый элемент SVG будет помещен в массив для целей отслеживания:
function newMall(){
if (malls.length < 4) {
var svg = document.createElementNS("http://www.w3.org/2000/svg","image");
svg.setAttributeNS('http://www.w3.org/1999/xlink','href','building.svg');
svg.setAttribute('width','471');
svg.setAttribute('height','303');
var scale = .21;
var x = 0;
var y = 0;
var thisMall = malls.length;
if (thisMall == 0 || thisMall == 2) {
x = 20;
} else {
x = 480;
}
if (thisMall == 0 || thisMall == 1) {
y = 10;
} else {
y = 300;
}
malls.push(svg);
svg.setAttribute('transform','translate(' + (x) + ', ' + (y) + ') scale(' + scale + ', ' + scale + ')');
document.getElementById('cityBox').appendChild(svg);
}
}
Наконец, добавьте событие мыши к кнопке приращения торгового центра, которая является элементом <path> с идентификатором «mallMore»:
<path id="mallMore" d="M 300 150 l -50 -25 l 0 50 l 50 -25" stroke="black" stroke-width="1" fill="red" onmouseup="newMall();" />
Результат с максимальными торговыми центрами (и без зомби) должен выглядеть как на рисунке 1.
Рисунок 1. Динамически добавленные элементы SVG Mall <image>
В то время как быки-зомби являются наихудшими (особенно семейства зомби-культов, страдающих от боли), здоровое быдло, вооруженное луком и стрелами, может помочь выжить при натиске ходячих или бегущих мертвецов. Динамическое добавление их в город выглядит как добавление зомби или торговых центров. Но в этом случае быдло будет размещено внутри безопасной зоны. Для дополнительного бонуса они будут перевернуты лицом вверх.
function newRedneck(){
var svg = document.createElementNS("http://www.w3.org/2000/svg","image");
svg.setAttributeNS('http://www.w3.org/1999/xlink','href','redneck.svg');
svg.setAttribute('width','375');
svg.setAttribute('height','950');
scale = .07;
var cityWidth = 600;
var cityHeight = 400;
var safezoneWidth = 200;
var safezoneHeight = 200;
var safezoneX = Math.round((cityWidth - safezoneWidth) / 2, 0);
var safezoneY = Math.round((cityHeight - safezoneHeight) / 2, 0);
var x = Math.floor(Math.random()*(safezoneWidth)) + safezoneX;
var y = Math.floor(Math.random()*(safezoneHeight-100)) + safezoneY;
flip = (x > (cityWidth / 2)) ? 1 : -1; //flip rednecks left of center
svg.setAttribute('transform','translate(' + (x) + ', ' + (y) + ') scale(' + (flip * scale) + ', ' + scale + ')');
document.getElementById('cityBox').appendChild(svg);
}
Добавьте вызов этой функции в элемент <path> с идентификатором «redneckMore»:
<path id="redneckMore" d="M 300 250 l -50 -25 l 0 50 l 50 -25" stroke="black" stroke-width="1" fill="red" onmouseup="newRedneck();" />
При быстром тестировании должен получиться кричащий человек, надежно укрытый в куче быдлов, как показано на рисунке 2.
Рисунок 2. Динамически добавленная команда защиты зомби
Со всеми тремя элементами в игре город (Рисунок 3) теперь ожил или нет.
Рисунок 3. Видение будущего, предоставлено JavaScript и SVG
Завершение панели управления
Все идет нормально. Но чтобы завершить панель управления, нужно сделать три вещи:
- Кнопки декремента должны быть включены
- Текст должен быть обновлен
- Параметры должны иметь верхний и нижний пределы
Лучший способ выполнить все три задачи одновременно — добавить четвертую функцию для обработки всех ответов кнопок и вызовов трех функций создания. Этот скрипт будет называться tickSVG.
Процедура newMall была шагом в правильном направлении с точки зрения контроля популяции SVG, поэтому используйте ее в качестве модели. Сначала добавьте глобальные массивы для зомби и быдла:
<script>
var zombies = new Array();
var malls = new Array();
var rednecks = new Array();
…
Далее, вместо непосредственного добавления элементов в DOM, каждая из функций создания должна возвращать новый элемент SVG. Итак, для каждой из трех функций, завершите:
//document.getElementById('cityBox').appendChild(svg);
return svg;
Также в newMall удалите следующую строку:
malls.push(svg);
Функция tickSVG сама будет управлять популяцией SVG <image> на основе параметров, которые передаются кнопками. Сначала функция обновляет соответствующий элемент <text> новым итогом. Во-вторых, основываясь на максимальных и минимальных пределах и текущем размере массива, tickSVG создает новый элемент <image> или извлекает верхний элемент из стека массива, в зависимости от того, какая кнопка была нажата. Наконец, он либо помещает новый элемент <image> в массив и добавляет его в DOM, как это было первоначально сделано в подпрограмме newMall, либо извлекает его из массива и удаляет его из DOM (обратите внимание на использование removeChild). метод).
function tickSVG(textName, increment, min, max) {
var textElement = document.getElementById(textName);
var currentValue = parseInt(textElement.textContent);
currentValue += increment;
if ( (currentValue <= max) && (currentValue >= min) ) {
textElement.textContent = currentValue;
isMore = (increment == Math.abs(increment));
switch (textName) {
case 'zombieText':
var svgArray = zombies;
var newSVG = (isMore ? newZombie() : svgArray[svgArray.length - 1]);
break;
case 'redneckText':
var svgArray = rednecks;
var newSVG = (isMore ? newRedneck() : svgArray[svgArray.length - 1]);
break;
case 'mallText':
var svgArray = malls;
var newSVG = (isMore ? newMall() : svgArray[svgArray.length - 1]);
break;
}
if (isMore) {
svgArray.push(newSVG);
document.getElementById('cityBox').appendChild(newSVG);
} else {
document.getElementById('cityBox').removeChild(newSVG);
svgArray.pop(svgArray.length - 1);
}
}
}
И наконец, все кнопки нуждаются в новых вызовах onmouseup. Найдите каждый из следующих путей и либо замените существующий вызов onmouseup, либо добавьте новый. Эти вызовы tickSVG включают ссылки на элементы <text>, созданные на предыдущих этапах, а также несколько произвольных значений max и min, за исключением торговых центров, которые были рассчитаны максимум на четыре.
<path id="zombieLess" d="M 50 50 l 50 -25 l 0 50 l -50 -25" stroke="black" stroke-width="1" fill="red" onmouseup="tickSVG('zombieText',-100,0,3000);" />
<path id="zombieMore" d="M 300 50 l -50 -25 l 0 50 l 50 -25" stroke="black" stroke-width="1" fill="red" onmouseup="tickSVG('zombieText',100,0,3000);" />
<path id="mallLess" d="M 50 150 l 50 -25 l 0 50 l -50 -25" stroke="black" stroke-width="1" fill="red" onmouseup="tickSVG('mallText',-1,0,4);" />
<path id="mallMore" d="M 300 150 l -50 -25 l 0 50 l 50 -25" stroke="black" stroke-width="1" fill="red" onmouseup="tickSVG('mallText',1,0,4);" />
<path id="redneckLess" d="M 50 250 l 50 -25 l 0 50 l -50 -25" stroke="black" stroke-width="1" fill="red" onmouseup="tickSVG('redneckText',-1,0,10);" />
<path id="redneckMore" d="M 300 250 l -50 -25 l 0 50 l 50 -25" stroke="black" stroke-width="1" fill="red" onmouseup="tickSVG('redneckText',1,0,10);" />
Полностью пересмотренный код приведен в конце этого пошагового руководства. Если все работает правильно, страница должна выглядеть примерно так, как показано на рисунке 4.
Рисунок 4. Полностью реализованная панель управления, или Апокалипсис уже близок
Каковы шансы?
Предсказателю выживания зомби-апокалипсиса требуется еще одна часть для завершения. Этот калькулятор шансов должен двигаться. По крайней мере, он нуждается в некотором JavaScript-манипулировании DOM, чтобы обновить 0% вверху и положение стрелки. Но для совместимых браузеров JavaScript может добавить дополнительный особый соус к одной из техник анимации, представленной во второй части этой серии, заставляя стрелку двигаться по динамически сгенерированному пути.
Но сначала немного расчетов. Добавьте новую глобальную переменную в верхней части раздела <script> с именем currentOdds и установите для нее значение 0:
<script>
var zombies = new Array();
var malls = new Array();
var rednecks = new Array();
var currentOdds = 0;
…
Затем создайте новую функцию с именем calcOdds. После исчерпывающего исследования с использованием строгой методологии была тщательно разработана следующая формула для прогнозирования выживания во время апокалипсиса зомби:
function calcOdds() {
var zombieCount = parseInt(document.getElementById('zombieText').textContent);
if (zombieCount == 0) {
currentOdds = 100;
} else {
var redneckCount = parseInt(document.getElementById('redneckText').textContent);
var mallCount = parseInt(document.getElementById('mallText').textContent);
var speed = document.getElementById('speedText').textContent;
var threat = Math.round((zombieCount * (speed == 'Slow' ? 2 : 18)) / 180);
var protection = Math.round(((mallCount * 10) + (redneckCount * 5)) / 60 * 100);
currentOdds = Math.round((100 + protection - threat) / 2);
}
if (currentOdds > 100) currentOdds = 100;
if (currentOdds < 0) currentOdds = 0;
}
Обратите внимание, что, как и в случае с элементом <text> в начале этого пошагового руководства, текстовое значение элемента может быть достигнуто путем доступа к его свойству textContent. Помимо этого различия, DOM выглядит так же, как и для HTML. Чтобы проверить расчет, найдите элемент группы (<g>) с идентификатором «oddsButton». Как объяснено в части 1 , кнопка состоит из двух отдельных элементов: элемента <rect> и элемента <text>. К счастью, сгруппировав их, родительский элемент <g> позволяет связать стили и события мыши с кнопкой в целом. А пока временно добавьте событие onmouseup для проверки calcOdds:
<g id="oddsButton" style="cursor: pointer;" onmouseup="calcOdds(); alert(currentOdds);">
Если это работает, пришло время для следующего шага: перемещение указателя. Создайте новую функцию с именем movePointer:
function movePointer() {
calcOdds();
document.getElementById('oddsText').textContent = currentOdds + '%';
var newY = -2 * currentOdds; //relative Y coordinate
newY += 300; //actual Y coordinate
document.getElementById('oddsPointer').setAttribute('points',
"150," + newY + " 100," + (newY - 25) + " 100," + (newY + 25));
}
Да, расчет newY можно выполнить за один шаг. Это было разделено по причине, которая скоро будет раскрыта. Заслуживает внимания последняя строка — функция setAttribute. Помните, что oddsPointer был создан как элемент <polygon>, который работает путем определения атрибута «points» с использованием абсолютных координат. Как и большинство других атрибутов, точки могут быть доступны через DOM, захватывая элемент и используя setAttribute, а затем указывая, какой атрибут («точки») и новое значение. В этом случае новое значение представляет собой текстовую строку, состоящую из трех пар координат. Указатель жестко закодирован в этом примере, чтобы быть направленным вправо треугольником в пространстве 50 × 50. Координата Y его центра может варьироваться от 300 в нижней части датчика до 100 в верхней части, что соответствует коэффициентам от 0 до 100 соответственно. Формула «newY = 300 — (2 * currentOdds)», разбитая здесь, должна располагать правую вершину треугольника точно по шкале, отмеченной метками, созданными во второй части этой серии.
Чтобы увидеть его в действии, замените событие onmouseup oddsButton вызовом новой функции:
<g id="oddsButton" style="cursor: pointer;" onmouseup="movePointer();">
Населите город несколькими зомби, торговыми центрами и жлобами, возможно, даже переключитесь на быстрых зомби, а затем нажмите «Рассчитать шансы». Указатель должен перейти к чрезвычайно точному прогнозу выживания во время апокалипсиса, как показано на рисунке 5.
Рисунок 5. Готовый продукт: инструмент для спасения жизни
Бонусная функция: динамическая анимация пути
Поскольку это пошаговое руководство сфокусировано на JavaScript и DOM, вот еще один приятный трюк для добавления анимации к движению указателя. Как описано в части 2 , SVG можно анимировать (в совместимых браузерах), добавив элемент <animateMotion> к элементу SVG и затем ссылаясь на путь.
Сделав еще один шаг вперед, JavaScript может динамически изменять координаты этого пути, позволяя перемещаться в любое время и в любом месте для любого элемента на экране.
Чтобы привести слова в действие, найдите многоугольник oddsPointer. Два изменения должны быть сделаны. Сначала определите простой путь с идентификатором «oddsPath». Затем ссылайтесь на этот путь внутри элемента <animateMotion>, который сам вложен в oddsPointer. Обратите внимание, что элемент <polygon> переходит от самозакрывающегося к подробному, поэтому обязательно перепроверьте синтаксис.
<g id="oddsMeter" stroke="#000000" stroke-width="2">
<path d="M100 100 h75 m-75 100 h75 m-75 100 h75" stroke-width="3" />
<path d="M100 150 h50 m-50 100 h50" />
<path id="oddsPath" d="M 0 0 L 0 -200" />
<polygon id="oddsPointer" points="150,300 100,275 100,325" fill="#FF0000" stroke-width="1">
<animateMotion dur="2s" begin="oddsButton.click">
<mpath xlink:href="#oddsPath" />
</animateMotion>
</polygon>
<rect x="90" y="75" rx="15" ry="10" width="20" height="250" fill="#0000FF" />
</g>
Здесь есть ошибка с элементом <path>. Помните, что путь анимации определяет не фактические координаты, а относительное движение анимации. Если бы этот <path> был нарисован на экране, он начинался бы в левом верхнем углу холста и шел прямо вверх. Но то, что он указывает указателю, это начать с его текущего местоположения (M 0 0) и подняться на 200px оттуда (L 0 -200).
Обратите внимание, что анимация запускается при нажатии кнопки oddsButton, как установлено в атрибуте «begin». Но если бы кнопка была нажата прямо сейчас, указатель просто перепрыгнул бы в правильное положение и затем всплыл вверх. В отличие от предыдущих примеров анимации, здесь необходимо различать браузеры, которые распознают и не распознают SVG-анимацию. В общем, лучше всего использовать document.implementation.hasFeature для проверки реализации функции в браузере. В этом случае, функция, которую нужно искать — это анимация в спецификации SVG 1.1. Добавьте функцию, которая поддерживает SupportAnimation ():
function supportsAnimation() {
return document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Animation", "1.1");
}
Это одна из нескольких доступных строк функций, которые можно найти по адресу http://www.w3.org/TR/SVG11/feature .
Затем добавьте еще одну глобальную переменную, чтобы отслеживать значение «L» пути, чтобы указатель всегда начинался с того места, где он ранее остановился, а не сбрасывался в исходное положение.
<script>
var zombies = new Array();
var malls = new Array();
var rednecks = new Array();
var currentOdds = 0;
var previousL = 0;
Это может немного сбивать с толку, учитывая предыдущее описание того, как работает путь анимации. Но имейте в виду, что JavaScript будет изменять атрибут <path>, а не <polygon>. И движение также не меняет атрибуты точек <многоугольника>, поэтому эти точки останутся постоянными при использовании этого метода. Следовательно, если <path> всегда начинается с M 0 0, то <polygon> всегда будет возвращаться к этой координате перед перемещением, вызывая неприглядный скачок перед анимацией. По этой причине значение «М» пути необходимо каждый раз менять, чтобы движение оставалось относительным.
Вернитесь к функции movePointer и добавьте сниффер. Теперь загадка грязного «нового» становится понятной. Исходя из возможностей браузера, либо атрибут «d» пути анимации будет изменен во время начала анимации, либо будет установлен атрибут points многоугольника:
function movePointer() {
calcOdds();
document.getElementById('oddsText').textContent = currentOdds + '%';
var newY = -2 * currentOdds; //relative Y coordinate
if (supportsAnimation()) {
document.getElementById('oddsPath').setAttribute('d','M 0 ' + previousL + ' L 0 ' + newY);
previousL = newY;
} else {
newY += 300; //actual Y coordinate
document.getElementById('oddsPointer').setAttribute('points',
"150," + newY + " 100," + (newY - 25) + " 100," + (newY + 25));
}
}
С этим, этот Предсказатель Выживания Апокалипсиса Зомби теперь завершен! Попробуйте это сейчас на http://justinwhitney.com/zombies/zombies_part4.htm или посмотрите исходный код страницы для окончательной версии этого пошагового руководства. Все ресурсы, используемые на этой странице (любезно предоставлены http://openclipart.org ), можно найти по адресу http://justinwhitney.com/zombies/zombiesAssets.zip .
Завершение
Хотя шансы на выживание в зомби-апокалипсисе могут варьироваться, шансы быть пойманным в одном из них довольно низки. Но, как известно CDC ( http://www.cdc.gov/phpr/zombies.htm ), компании, которые готовятся к зомби, также готовы ко многим другим вещам. Я надеюсь, что эта серия пошаговых руководств проиллюстрировала, как использовать SVG для визуально динамических, масштабируемых веб-приложений.
Полученные здесь навыки могут быть использованы для многих других приложений. Как подготовка к астероидам.
Потому что астероидный апокалипсис обязательно случится.
Эта статья является частью технической серии HTML5 от команды Internet Explorer. Испытайте концепции этой статьи с тремя месяцами бесплатного кросс-браузерного тестирования BrowserStack @ http://modern.IE