renovations
Before Width: | Height: | Size: 6.7 KiB |
|
@ -1,2 +0,0 @@
|
|||
# Анимация
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
# Кривые Безье
|
||||
|
||||
Кривые Безье используются в компьютерной графике для рисования плавных изгибов, в [CSS-анимации](#css-animation) для описания процесса анимации и много где ещё.
|
||||
Кривые Безье используются в компьютерной графике для рисования плавных изгибов, в CSS-анимации и много где ещё.
|
||||
|
||||
Тему эту стоит изучить, чтобы в дальнейшем с комфортом пользоваться этим замечательным инструментом.
|
||||
Несмотря на "умное" название -- это очень простая штука.
|
||||
|
||||
В принципе, можно создавать анимацию и без знания кривых Безье, но стоит один раз прочитать, что это такое, так как в векторной графике и продвинутых анимаций без них никак. Это образовательный минимум.
|
||||
|
||||
Тему эту стоит изучить один раз, чтобы в дальнейшем с комфортом пользоваться этим замечательным инструментом.
|
||||
[cut]
|
||||
<script>
|
||||
if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1")) {
|
||||
document.write('<h2 style="color:red">Ваш браузер не поддерживает SVG. Живые примеры без него не работают :(</h2>');
|
||||
}
|
||||
</script>
|
||||
|
||||
## Виды кривых Безье
|
||||
|
||||
|
@ -16,29 +15,24 @@ if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Basi
|
|||
|
||||
Их может быть две, три, четыре или больше. Например:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>По двум точкам</th>
|
||||
<th>По трём точкам</th>
|
||||
<th>По четырём точкам</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
По двум точкам:
|
||||
|
||||
<img src="bezier2.png">
|
||||
</td>
|
||||
<td>
|
||||
|
||||
По трём точкам:
|
||||
|
||||
<img src="bezier3.png">
|
||||
</td>
|
||||
<td>
|
||||
|
||||
По четырём точкам:
|
||||
|
||||
<img src="bezier4.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Если вы посмотрите внимательно на эти кривые, то "на глазок" заметите:
|
||||
<ol>
|
||||
<li>**Точки не всегда на кривой.** Это совершенно нормально, как именно строится кривая мы рассмотрим чуть позже.</li>
|
||||
<li>**Степень кривой равна числу точек минус один**.
|
||||
На рисунках выше, соответственно, получаются для двух точек -- линейная кривая (прямая), для трёх точек -- квадратическая кривая (парабола), для четырёх -- кубическая.</li>
|
||||
Для двух точек -- это линейная кривая (т.е. прямая), для трёх точек -- квадратическая кривая (парабола), для четырёх -- кубическая.</li>
|
||||
<li>**Кривая всегда находится внутри [выпуклой оболочки](http://ru.wikipedia.org/wiki/%D0%92%D1%8B%D0%BF%D1%83%D0%BA%D0%BB%D0%B0%D1%8F_%D0%BE%D0%B1%D0%BE%D0%BB%D0%BE%D1%87%D0%BA%D0%B0), образованной опорными точками:**
|
||||
|
||||
<img src="bezier4-e.png"> <img src="bezier3-e.png">
|
||||
|
@ -47,13 +41,13 @@ if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Basi
|
|||
</li>
|
||||
</ol>
|
||||
|
||||
Основная ценность кривых Безье -- в том, что **кривую можно менять, двигая точки**. При этом **кривая меняется интуитивно понятным образом**.
|
||||
Основная ценность кривых Безье -- в том, что, двигая точки, кривую можно менять, причём кривая при этом меняется интуитивно понятным образом.
|
||||
|
||||
Попробуйте двигать точки мышью в примере ниже:
|
||||
|
||||
[iframe src="demo.svg?nocpath=1&p=0,0,0.5,0,0.5,1,1,1" height=370]
|
||||
|
||||
Как можно заметить, **кривая натянута по касательным 1 -> 2 и 3 -> 4.**
|
||||
**Как можно заметить, кривая натянута по касательным 1 -> 2 и 3 -> 4.**
|
||||
|
||||
После небольшой практики становится понятно, как расположить точки, чтобы получить нужную форму. А, соединяя несколько кривых, можно получить практически что угодно.
|
||||
|
||||
|
@ -63,7 +57,9 @@ if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Basi
|
|||
|
||||
## Математика
|
||||
|
||||
У кривых Безье есть математическая формула. Как мы увидим далее, в ней нет особенной необходимости, но для полноты картины -- вот она.
|
||||
У кривых Безье есть математическая формула.
|
||||
|
||||
Как мы увидим далее, для пользования кривыми Безье знать её нет особенной необходимости, но для полноты картины -- вот она.
|
||||
|
||||
**Координаты кривой описываются в зависимости от параметра `t⋲[0,1]`**
|
||||
|
||||
|
@ -88,28 +84,39 @@ if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Basi
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
Эти уравнения -- векторные, т.е. вместо <code>P<sub>i</sub></code> нужно подставить координаты i-й опорной точки <code>(x<sub>i</sub>, y<sub>i</sub>)</code>.
|
||||
Вместо <code>P<sub>i</sub></code> нужно подставить координаты i-й опорной точки <code>(x<sub>i</sub>, y<sub>i</sub>)</code>.
|
||||
|
||||
Формула даёт возможность строить кривые, но не очень понятно, почему они именно такие, и как зависят от опорных точек. С этим нам поможет разобраться другой алгоритм.
|
||||
Эти уравнения векторные, то есть на для каждой из координат:
|
||||
<ul>
|
||||
<li><code>x = (1−t)<sup>2</sup>x<sub>1</sub> + 2(1−t)tx<sub>2</sub> + t<sup>2</sup>x<sub>3</sub></code></li>
|
||||
<li><code>y = (1−t)<sup>2</sup>y<sub>1</sub> + 2(1−t)ty<sub>2</sub> + t<sup>2</sup>y<sub>3</sub></code></li>
|
||||
</ul>
|
||||
|
||||
Вместо <code>x<sub>1</sub>, y<sub>1</sub>, x<sub>2</sub>, y<sub>2</sub>, x<sub>3</sub>, y<sub>3</sub></code> подставляются координаты трёх опорных точек, и в то время как `t` пробегает множество от `0` до `1`, соответствующие значения `(x, y)` как раз и образуют кривую.
|
||||
|
||||
Впрочем, это чересчур наукообразно, не очень понятно, почему кривые именно такие, и как зависят от опорных точек. С этим нам поможет разобраться другой, более наглядный алгоритм.
|
||||
|
||||
## Рисование "де Кастельжо"
|
||||
|
||||
[Метод де Кастельжо](http://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%B4%D0%B5_%D0%9A%D0%B0%D1%81%D1%82%D0%B5%D0%BB%D1%8C%D0%B6%D0%BE) идентичен математическому определению кривой и наглядно показывает, как она строится.
|
||||
|
||||
Посмотрим его на примере трех точек (точки можно двигать). Нажатие на кнопку "edit" запустит демонстрацию.
|
||||
Посмотрим его на примере трёх точек (точки можно двигать). Нажатие на кнопку "play" запустит демонстрацию.
|
||||
|
||||
[iframe src="demo.svg?p=0,0,0.5,1,1,0&animate=1" height=370]
|
||||
|
||||
**Алгоритм построения "де Кастельжо":**
|
||||
**Алгоритм построения кривой по "методу де Кастельжо":**
|
||||
|
||||
<ol>
|
||||
|
||||
<li>Строятся отрезки между опорными точками 1-2-3. На рисунке выше они **чёрные**.</li>
|
||||
<li>Рисуем опорные точки. В примере выше это `1`, `2`, `3`.</li>
|
||||
<li>Строятся отрезки между опорными точками 1 -> 2 -> 3. На рисунке выше они **чёрные**.</li>
|
||||
<li>Параметр `t` пробегает значения от `0` до `1`. В примере выше использован шаг `0.05`, т.е. в цикле `0, 0.05, 0.1, 0.15, ... 0.95, 1`.
|
||||
|
||||
Для каждого значения `t`:
|
||||
Значение `t` пробегает интервал от 0 до 1, для каждого `t`:
|
||||
<ol>
|
||||
<li>На каждом из этих отрезков берётся точка, находящаяся от начала на расстоянии от 0 до `t` пропорционально длине. То есть, при `t=0` -- точка будет в начале, при `t=0.25` -- на расстоянии в 25% от начала отрезка, при `t=0.5` -- 50%(на середине), при `t=1` -- в конце. Так как **чёрных** отрезков -- два, то и точек выходит две штуки.</li>
|
||||
<li>На каждом из этих отрезков берётся точка, находящаяся от начала на расстоянии от 0 до `t` пропорционально длине. Так как чёрных отрезков -- два, то и точек выходит две штуки.
|
||||
|
||||
То есть, при `t=0` -- точки будут в начале, при `t=0.25` -- на расстоянии в 25% от начала отрезка, при `t=0.5` -- 50%(на середине), при `t=1` -- в конце отрезка.
|
||||
</li>
|
||||
<li>Эти точки соединяются. На рисунке ниже соединяющий их отрезок изображён <span style="color:blue">синим</span>.
|
||||
|
||||
<table>
|
||||
|
@ -121,7 +128,7 @@ if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Basi
|
|||
</table>
|
||||
|
||||
</li>
|
||||
<li>На <span style="color:blue">получившемся</span> отрезке берётся точка на расстоянии, соответствующем `t`. То есть, для `t=0.25` получаем точку в конце первой четверти отрезка, для `t=0.5` -- в середине отрезка. На рисунке выше эта точка отмечена <span style="color:red">красным</span>.
|
||||
<li>На <span style="color:blue">получившемся</span> отрезке берётся точка на расстоянии, соответствующем `t`. То есть, для `t=0.25` (первый рисунок) получаем точку в конце первой четверти отрезка, для `t=0.5` (второй рисунок) -- в середине отрезка. На рисунках выше эта точка отмечена <span style="color:red">красным</span>.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
|
@ -138,14 +145,14 @@ if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Basi
|
|||
|
||||
Алгоритм:
|
||||
<ul>
|
||||
<li>Точки по порядку соединяются отрезками: `1-2`, `2-3`, `3-4`. Получается три чёрных отрезка.</li>
|
||||
<li>Точки по порядку соединяются отрезками: 1 -> 2, 2 -> 3, 3 -> 4. Получается три чёрных отрезка.</li>
|
||||
<li>На отрезках берутся точки, соответствующие текущему `t`, соединяются. Получается два <span style="color:#0A0">зелёных отрезка</span>.</li>
|
||||
<li>На этих отрезках берутся точки, соответствующие текущему `t`, соединяются. Получается один <span style="color:blue">синий отрезок</span>.</li>
|
||||
<li>На синем отрезке берётся точка, соответствующая текущему `t`. При запуске примера выше она <span style="color:red">красная</span>.</li>
|
||||
<li>Эти точки описывают кривую.</li>
|
||||
</ul>
|
||||
|
||||
Нажмите на кнопку "edit" в примере выше, чтобы увидеть это в действии.
|
||||
Нажмите на кнопку "play" в примере выше, чтобы увидеть это в действии.
|
||||
|
||||
Ещё примеры кривых:
|
||||
|
||||
|
@ -165,12 +172,12 @@ if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Basi
|
|||
|
||||
Аналогичным образом могут быть построены кривые Безье и более высокого порядка: по пяти точкам, шести и так далее. Но обычно используются 2-3 точки, а для сложных линий несколько кривых соединяются. Это гораздо проще с точки зрения поддержки и расчётов.
|
||||
|
||||
[smart header="Как провести кривую через нужные точки?"]
|
||||
Этот вопрос не связан с кривыми Безье, но он иногда возникает в смежных задачах.
|
||||
[smart header="Как провести кривую Безье через нужные точки?"]
|
||||
Кривые Безье обычно проводятся через "опорные" точки, которые, как можно видеть из примеров выше, редко лежат на кривой.
|
||||
|
||||
Такая задача называется [интерполяцией](http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BF%D0%BE%D0%BB%D1%8F%D1%86%D0%B8%D1%8F). Существуют математические формулы, которые подбирают коэффициенты кривой по точкам, исходя из требований, например [многочлен Лагранжа](http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BF%D0%BE%D0%BB%D1%8F%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE%D1%87%D0%BB%D0%B5%D0%BD_%D0%9B%D0%B0%D0%B3%D1%80%D0%B0%D0%BD%D0%B6%D0%B0).
|
||||
Если нужно провести кривую именно через нужные точки, то это уже другая задача. Она называется [интерполяцией](http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BF%D0%BE%D0%BB%D1%8F%D1%86%D0%B8%D1%8F). Существуют математические формулы, которые подбирают коэффициенты кривой по точкам, исходя из требований, например [многочлен Лагранжа](http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BF%D0%BE%D0%BB%D1%8F%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE%D1%87%D0%BB%D0%B5%D0%BD_%D0%9B%D0%B0%D0%B3%D1%80%D0%B0%D0%BD%D0%B6%D0%B0).
|
||||
|
||||
Как правило, в компьютерной графике для интерполяции используют кубические кривые, соединённых гладким образом. Вместе они выглядят как одна кривая. Это называется [интерполяция сплайнами](http://ru.wikipedia.org/wiki/%D0%9A%D1%83%D0%B1%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D1%81%D0%BF%D0%BB%D0%B0%D0%B9%D0%BD).
|
||||
Как правило, в компьютерной графике для интерполяции используют кубические кривые, соединённых гладким образом. Вместе они выглядят как одна кривая. Это называется [интерполяция сплайнами](http://ru.wikipedia.org/wiki/%D0%9A%D1%83%D0%B1%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D1%81%D0%BF%D0%BB%D0%B0%D0%B9%D0%BD).
|
||||
[/smart]
|
||||
## Итого
|
||||
|
||||
|
@ -180,12 +187,16 @@ if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Basi
|
|||
<li>Через процесс построения де Кастельжо.</li>
|
||||
</ol>
|
||||
|
||||
С их помощью можно описать почти любую линию, особенно если соединить несколько.
|
||||
Их удобство в том, что:
|
||||
<ul>
|
||||
<li>Можно легко нарисовать плавные линии вручную, передвигая точки мышкой.</li>
|
||||
<li>Более сложные изгибы и линии можно составить, если соединить несколько кривых Безье.</li>
|
||||
</ul>
|
||||
|
||||
Применение:
|
||||
|
||||
<ul>
|
||||
<li>В компьютерной графике, моделировании, в графических редакторах. Шрифты описываются с помощью кривых Безье.</li>
|
||||
<li>В веб-разработке -- для графики на Canvas или в формате SVG. Кстати, все живые примеры выше написаны на SVG. Фактически, это один SVG-документ, к которому точки передаются параметрами. Вы можете открыть его в отдельном окне и посмотреть исходник: <a href="/files/tutorial/browser/animation/bezier/demo.svg?p=0,0,1,0.5,0,0.5,1,1&animate=1">demo.svg</a>.</li>
|
||||
<li>В CSS-анимации, для задания временной функции.</li>
|
||||
<li>В CSS-анимации, для задания траектории и скорости передвижения.</li>
|
||||
</ul>
|
0
3-more/2-animation/2-bezier/bezier2.png → 3-more/3-animation/1-bezier/bezier2.png
Executable file → Normal file
Before Width: | Height: | Size: 596 B After Width: | Height: | Size: 596 B |
0
3-more/2-animation/2-bezier/bezier3-draw1.png → 3-more/3-animation/1-bezier/bezier3-draw1.png
Executable file → Normal file
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
0
3-more/2-animation/2-bezier/bezier3-draw2.png → 3-more/3-animation/1-bezier/bezier3-draw2.png
Executable file → Normal file
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
0
3-more/2-animation/2-bezier/bezier3-e.png → 3-more/3-animation/1-bezier/bezier3-e.png
Executable file → Normal file
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
0
3-more/2-animation/2-bezier/bezier3.png → 3-more/3-animation/1-bezier/bezier3.png
Executable file → Normal file
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
0
3-more/2-animation/2-bezier/bezier4-e.png → 3-more/3-animation/1-bezier/bezier4-e.png
Executable file → Normal file
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
0
3-more/2-animation/2-bezier/bezier4.png → 3-more/3-animation/1-bezier/bezier4.png
Executable file → Normal file
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
0
3-more/2-animation/2-bezier/car.jpg → 3-more/3-animation/1-bezier/car.jpg
Executable file → Normal file
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
1
3-more/3-animation/1-bezier/demo.svg
Normal file
After Width: | Height: | Size: 7.5 KiB |
0
3-more/2-animation/2-bezier/letter_m.png → 3-more/3-animation/1-bezier/letter_m.png
Executable file → Normal file
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
BIN
3-more/3-animation/1-bezier/play.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
0
3-more/2-animation/2-bezier/vase.png → 3-more/3-animation/1-bezier/vase.png
Executable file → Normal file
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
@ -1,11 +1,10 @@
|
|||
# CSS-анимация
|
||||
# CSS-transitions
|
||||
|
||||
Все современные браузеры, кроме IE9- поддерживают <a href="http://www.w3.org/TR/css3-transitions/">CSS transitions</a>, которые позволяют реализовать анимацию средствами CSS, без привлечения JavaScript.
|
||||
|
||||
Большинство примеров из этой статьи не будут работать в IE9-.
|
||||
[cut]
|
||||
|
||||
## Анимация свойства [#css-animation]
|
||||
## Анимация свойства [#css-transition]
|
||||
|
||||
Идея проста. Вы указываете, что некоторое свойство будет анимироваться при помощи специальных CSS-правил. Далее, при изменении этого свойства, браузер сам обработает анимацию.
|
||||
|
||||
|
@ -18,7 +17,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
Любое изменение фонового цвета будет анимироваться в течение 2-х секунд.
|
||||
Теперь любое изменение фонового цвета будет анимироваться в течение 2-х секунд.
|
||||
|
||||
У свойства `"transition"` есть и короткая запись:
|
||||
|
||||
|
@ -28,46 +27,7 @@
|
|||
}
|
||||
```
|
||||
|
||||
Так как [стандарт CSS Transitions](http://www.w3.org/TR/css3-transitions/) находится в стадии разработки, то `transition` нужно снабжать браузерными префиксами.
|
||||
|
||||
### Пример
|
||||
Этот пример работает во всех современных браузерах, не работает в IE9-.
|
||||
|
||||
```html
|
||||
<style>
|
||||
.animated {
|
||||
-webkit-transition: background-color 2s;
|
||||
-ms-transition: background-color 2s;
|
||||
-o-transition: background-color 2s;
|
||||
-moz-transition: background-color 2s;
|
||||
transition: background-color 2s; /* без префикса - на будущее */
|
||||
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
<div class="animated" onclick="this.style.backgroundColor='red'">
|
||||
<span style="font-size:150%">Кликни меня</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
<style>
|
||||
.animated {
|
||||
transition: background-color 2s;
|
||||
-webkit-transition: background-color 2s;
|
||||
-o-transition: background-color 2s;
|
||||
-ms-transition: background-color 2s;
|
||||
-moz-transition: background-color 2s;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
<div class="animated" onclick="this.style.backgroundColor='red'">
|
||||
<span style="font-size:150%">Кликни меня</span>
|
||||
</div>
|
||||
|
||||
CSS-анимации особенно рекомендуются на мобильных устройствах. Они отрисовываются плавнее, чем JavaScript, и меньше нагружают процессор, так как используют графическую акселерацию.
|
||||
|
||||
|
||||
## Полный синтаксис CSS
|
||||
## Полный синтаксис
|
||||
|
||||
Свойства для CSS-анимаций:
|
||||
<dl>
|
||||
|
@ -76,41 +36,27 @@ CSS-анимации особенно рекомендуются на мобил
|
|||
<dt>`transition-duration`</dt>
|
||||
<dd>Продолжительность анимации. Если указано одно значение -- оно применится ко всем свойствам, можно указать несколько значений для разных `transition-property`.</dd>
|
||||
<dt>`transition-timing-function`</dt>
|
||||
<dd>[Кривая Безье](/bezier) по 4-м точкам, используемая в качестве временной функциии.</dd>
|
||||
<dd>Кривая Безье по 4-м точкам, используемая в качестве временной функциии. Их мы изучим [чуть позже](/bezier)</dd>
|
||||
<dt>`transition-delay`</dt>
|
||||
<dd>Указывает задержку от изменения свойства до начала CSS-анимации.</dd>
|
||||
</dl>
|
||||
|
||||
Свойство **`transition`** может содержать их все, в порядке: `property duration timing-function delay, ...`.
|
||||
|
||||
### Пример
|
||||
## Пример
|
||||
|
||||
Анимируем одновременно цвет и размер шрифта:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<style>
|
||||
.growing {
|
||||
-webkit-transition: font-size 3s, color 2s;
|
||||
-ms-transition: font-size 3s, color 2s;
|
||||
-o-transition: font-size 3s, color 2s;
|
||||
-moz-transition: font-size 3s, color 2s;
|
||||
transition: font-size 3s, color 2s;
|
||||
}
|
||||
</style>
|
||||
<button class="growing" onclick="this.style.fontSize='36px';this.style.color='red'">Кликни меня</button>
|
||||
```
|
||||
|
||||
<style>
|
||||
.growing {
|
||||
-webkit-transition: font-size 3s, color 2s;
|
||||
-ms-transition: font-size 3s, color 2s;
|
||||
-o-transition: font-size 3s, color 2s;
|
||||
-moz-transition: font-size 3s, color 2s;
|
||||
transition: font-size 3s, color 2s;
|
||||
}
|
||||
</style>
|
||||
<button class="growing" onclick="this.style.fontSize='36px';this.style.color='red'">Кликни меня</button>
|
||||
|
||||
## Временнáя функция
|
||||
|
||||
В качестве временной функции можно выбрать любую [кривую Безье](/bezier), удовлетворяющую условиям:
|
0
3-more/2-animation/3-css-animation/boat.view/index.html → 3-more/3-animation/2-css-transitions/boat.view/index.html
Executable file → Normal file
0
3-more/2-animation/3-css-animation/ease-in-out.png → 3-more/3-animation/2-css-transitions/ease-in-out.png
Executable file → Normal file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
0
3-more/2-animation/3-css-animation/ease-in.png → 3-more/3-animation/2-css-transitions/ease-in.png
Executable file → Normal file
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
0
3-more/2-animation/3-css-animation/ease-out.png → 3-more/3-animation/2-css-transitions/ease-out.png
Executable file → Normal file
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
0
3-more/2-animation/3-css-animation/ease.png → 3-more/3-animation/2-css-transitions/ease.png
Executable file → Normal file
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
0
3-more/2-animation/3-css-animation/train-curve.png → 3-more/3-animation/2-css-transitions/train-curve.png
Executable file → Normal file
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
0
3-more/2-animation/1-js-animation/move.view/index.html → 3-more/3-animation/3-js-animation/move.view/index.html
Executable file → Normal file
0
3-more/2-animation/1-js-animation/move100.view/index.html → 3-more/3-animation/3-js-animation/move100.view/index.html
Executable file → Normal file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
3-more/2-animation/1-js-animation/width.view/index.html → 3-more/3-animation/3-js-animation/width.view/index.html
Executable file → Normal file
3
3-more/3-animation/index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Анимация [todo]
|
||||
|
||||
CSS анимации. Контроль над ними из JavaScript. Анимации на чистом JavaScript.
|
|
@ -13,53 +13,15 @@
|
|||
|
||||
## Коллекция утечек в IE
|
||||
|
||||
### Утечка DOM ↔ JS в IE7-
|
||||
### Утечка DOM ↔ JS в IE8-
|
||||
|
||||
IE до версии 8 не умел очищать циклические ссылки, появляющиеся между DOM-объектами и объектами JavaScript. В результате и DOM и JS оставались в памяти навсегда.
|
||||
|
||||
[warn header="Пропустите эту секцию, если IE8- не нужен"]
|
||||
Проблема была особенно серьезна в IE6 до SP3 (или до обновления июня 2007 года), там память не освобождалась даже при переходе на другую страницу.
|
||||
В браузере IE8 была проведена серьёзная работа над ошибками, но утечка в IE8- появляется, если круговая ссылка возникает "через объект".
|
||||
|
||||
Сейчас она существует в IE6, 7, а также, в облегчённом варианте, в IE8 (см. далее). Если вы не поддерживаете IE8-, то можете пропустить эту секцию.
|
||||
[/warn]
|
||||
Чтобы было понятнее, о чём речь, посмотрите на следующий код. Он вызывает утечку памяти в IE8-:
|
||||
|
||||
Функция `setHandler` в примере ниже ведёт к утечке памяти в IE6,7:
|
||||
|
||||
```js
|
||||
function setHandler() {
|
||||
var elem = document.getElementById('id');
|
||||
elem.onclick = function() {
|
||||
/* может быть пустая функция, не важно */
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Элемент `elem` здесь ссылается на JavaScript-функцию через ссылку `onclick` напрямую, через свойство, а функция ссылается на `elem` через замыкание.
|
||||
|
||||
<img src="ie1.png">
|
||||
|
||||
Здесь вместо DOM-элемента в IE может быть `XMLHttpRequest`, `ActiveX`, любой другой COM-объект. Круговая ссылка гарантирует утечку.
|
||||
|
||||
|
||||
**Обойти утечки памяти в IE можно, разорвав циклические ссылки.**
|
||||
|
||||
Например, можно удалить ссылку на `elem` из замыкания, присвоив `elem = null`. Таким образом обработчик больше не ссылается на DOM-элемент. Циклическая ссылка разорвана:
|
||||
|
||||
<img src="ie2.png">
|
||||
|
||||
|
||||
Больше информации об этой утечке вы можете почерпнуть из статей: <a href="http://msdn.microsoft.com/en-us/library/ms976398.aspx">Understanding and Solving Internet Explorer Leak Patterns</a> и <a href="http://msdn.microsoft.com/en-us/library/dd361842%28v=vs.85%29.aspx">Circular Memory Leak Mitigation</a>.
|
||||
|
||||
|
||||
### Утечка DOM ↔ JS, вариант для IE8-
|
||||
|
||||
В браузере IE8 была проведена серьёзная работа над ошибками, и пример, описанный выше, больше не приводит к утечке.
|
||||
|
||||
**Но ситуация исправлена не до конца. Утечка в IE8- появляется, если круговая ссылка возникает "через объект".**
|
||||
|
||||
Чтобы было понятнее, о чём речь, посмотрите на следующий код. Он вызывает утечку памяти:
|
||||
|
||||
```js
|
||||
function leak() {
|
||||
// Создаём новый DIV, добавляем к BODY
|
||||
var elem = document.createElement('div');
|
||||
|
@ -80,11 +42,12 @@ function leak() {
|
|||
}
|
||||
```
|
||||
|
||||
<a href="/files/tutorial/leak/leak_ie8.html" target="_blank">Открыть в новом окне (в IE)</a>
|
||||
Полный пример (только для IE8-, а также IE9 в режиме совместимости с IE8):
|
||||
|
||||
Этот пример, течёт в IE6,7,8, а также в IE9 в режиме совместимости с IE8. Проблема -- в круговой ссылке `elem.__expando__elem = elem`.
|
||||
[codetabs src="leak-ie8"]
|
||||
|
||||
**Утечка может возникать и неявным образом, через замыкание:**
|
||||
|
||||
Круговая ссылка и, как следствие, утечка может возникать и неявным образом, через замыкание:
|
||||
|
||||
```js
|
||||
function leak() {
|
||||
|
@ -103,32 +66,17 @@ function leak() {
|
|||
}
|
||||
```
|
||||
|
||||
<a href="/files/tutorial/leak/leak_ie8_2.html" target="_blank">Открыть в новом окне (в IE)</a>
|
||||
Полный пример (IE8-, IE9 в режиме совместимости с IE8):
|
||||
|
||||
**Без метода `method` здесь утечки не возникнет.**
|
||||
[codetabs src="leak-ie8-2"]
|
||||
|
||||
Без привязки метода `method` к элементу здесь утечки не возникнет.
|
||||
|
||||
Бывает ли такая ситуация в реальной жизни? Или это -- целиком синтетический пример, для заумных программистов?
|
||||
|
||||
Да, конечно бывает. Например, при разработке графических компонент.
|
||||
|
||||
Во-первых, сам объект компонента хранит ссылку на его DOM-элемент, чтобы работать с ним. Например:
|
||||
|
||||
```js
|
||||
function Menu(elem) {
|
||||
elem.onclick = function() {
|
||||
// ссылка на elem осталась в замыкании
|
||||
};
|
||||
}
|
||||
|
||||
var menu = new Menu(elem);
|
||||
```
|
||||
|
||||
То есть, компонент всегда знает свой элемент.
|
||||
|
||||
Но бывают ситуации, когда нужно пойти в обратном направлении, а именно -- по элементу определить, какой на нём компонент. Например, при делегировании, чтобы передать обработку события на элементе соответствующему компоненту. Или при Drag'n'Drop, чтобы получить компонент, соответствующий элементу, на который произведён перенос.
|
||||
|
||||
Сама задача не является чем-то из ряда вон выходящим. Вполне естественно, что JS-компонент привязан к элементу, а элемент знает о компоненте на нём. Но в IE8- прямая привязка ведёт к утечке памяти!
|
||||
Да, конечно бывает. Например, при разработке графических компонент -- бывает удобно присвоить DOM-элементу ссылку на JavaScript-объект, который представляет собой компонент. Это упрощает делегирование и, в общем-то, логично, что DOM-элемент знает о компоненте на себе. Но в IE8- прямая привязка ведёт к утечке памяти!
|
||||
|
||||
Примерно так:
|
||||
```js
|
||||
function Menu(elem) {
|
||||
elem.onclick = function() {};
|
||||
|
@ -136,12 +84,13 @@ function Menu(elem) {
|
|||
|
||||
var menu = new Menu(elem); // Menu содержит ссылку на elem
|
||||
*!*
|
||||
elem.menu = menu; // вот такая привязка или что-то подобное ведёт к утечке
|
||||
elem.menu = menu; // такая привязка или что-то подобное ведёт к утечке в IE8
|
||||
*/!*
|
||||
```
|
||||
|
||||
<a href="/files/tutorial/leak/leak_ie8_widget.html" target="_blank">Открыть в новом окне (в IE)</a>
|
||||
Полный пример (IE8-, IE9 в режиме совместимости с IE8):
|
||||
|
||||
Такая привязка удобна, т.к. мы по DOM-элементу можем получить JS-компонент, который к нему привязан. Но, как видим, ведёт к утечке в IE8-.
|
||||
[codetabs src="leak-ie8-widget"]
|
||||
|
||||
### Утечка IE8 при обращении к коллекциям таблицы
|
||||
|
||||
|
@ -168,7 +117,9 @@ function leak() {
|
|||
}
|
||||
```
|
||||
|
||||
<a href="/files/tutorial/leak/leak_ie8_table.html" target="_blank">Открыть в новом окне (в IE)</a>
|
||||
Полный пример (IE8):
|
||||
|
||||
[codetabs src="leak-ie8-table"]
|
||||
|
||||
Особенности:
|
||||
<ul>
|
||||
|
@ -217,10 +168,12 @@ function leak() {
|
|||
|
||||
Когда запускается асинхронный запрос `xhr`, браузер создаёт специальную внутреннюю ссылку (internal reference) на этот объект. находится в процессе коммуникации. Именно поэтому объект `xhr` будет жив после окончания работы функции.
|
||||
|
||||
**Когда запрос завершен, браузер удаляет внутреннюю ссылку, `xhr` становится недостижимым и память очищается... Везде, кроме IE8-.**
|
||||
Когда запрос завершен, браузер удаляет внутреннюю ссылку, `xhr` становится недостижимым и память очищается... Везде, кроме IE8-.
|
||||
|
||||
|
||||
<a href="/files/tutorial/leak/leak_ie8_xhr.html" target="_blank">Открыть в новом окне (в IE8-)</a> (откройте страницу и пусть поработает минут 20 - съест всю память, включая виртуальную).
|
||||
Полный пример (IE8):
|
||||
|
||||
[codetabs src="leak-ie8-xhr"]
|
||||
|
||||
Чтобы это исправить, нам нужно разорвать круговую ссылку `XMLHttpRequest ↔ JS`. Например, можно удалить `xhr` из замыкания:
|
||||
|
||||
|
@ -248,9 +201,7 @@ function leak() {
|
|||
|
||||
<img src="xhr2.png">
|
||||
|
||||
Теперь циклической ссылки нет -- мы устранили утечку.
|
||||
|
||||
<a href="/files/tutorial/leak/leak_ie8_xhr_fixed.html" target="_blank">Посмотреть исправленный пример для IE в отдельном окне</a>.
|
||||
Теперь циклической ссылки нет -- и не будет утечки.
|
||||
|
||||
|
||||
## Объемы утечек памяти
|
||||
|
@ -273,13 +224,11 @@ function f() {
|
|||
}
|
||||
```
|
||||
|
||||
**Пока функция `inner` остается в памяти, `LexicalEnvironment` с переменной большого объема внутри висит в памяти.**
|
||||
Пока функция `inner` остается в памяти, `LexicalEnvironment` с переменной большого объема внутри висит в памяти.
|
||||
|
||||
Висит до тех пор, пока функция `inner` жива.
|
||||
|
||||
**Как правило, JavaScript не знает, какие из переменных функции `inner` будут использованы, поэтому оставляет их все.**
|
||||
|
||||
Исключение -- виртуальная машина V8 (Chrome, Opera, Node.JS), она обычно видит, что переменная не используется во внутренних функциях, и очистит память.
|
||||
Как правило, JavaScript не знает, какие из переменных функции `inner` будут использованы, поэтому оставляет их все. Исключение -- виртуальная машина V8 (Chrome, Opera, Node.JS), она часто (не всегда) видит, что переменная не используется во внутренних функциях, и очистит память.
|
||||
|
||||
В других же интерпретаторах, даже если код спроектирован так, что никакой утечки нет, по вполне разумной причине может создаваться множество функций, а память будет расти потому, что функция тянет за собой своё замыкание.
|
||||
|
||||
|
@ -303,113 +252,6 @@ function f() {
|
|||
}
|
||||
```
|
||||
|
||||
## jQuery: утечки и борьба с утечками
|
||||
|
||||
[warn header="Изменения в jQuery 2"]
|
||||
В jQuery 2 объект с данными элемента, о котором идёт речь далее, более недоступен извне. Он стал локальной переменной внутри jQuery с именем `data_priv`, явный доступ к внутренним данным более невозможен (или deprecated).
|
||||
|
||||
Но в остальном всё работает точно так, как описано, и с теми же последствиями.
|
||||
[/warn]
|
||||
|
||||
**В jQuery для борьбы с утечками памяти в IE6-7 используется <a href="http://api.jquery.com/jQuery.data/">$.data</a> API. Однако, это может стать причиной новых(!) утечек, характерных для jQuery.**
|
||||
|
||||
Основной принцип `$.data` — это для любого JavaScript объекта сохранить/получить значение для элемента с помощью jQuery вызова:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
$(document.body).data('prop', { anything: "любой объект" }) // set
|
||||
alert( $(document.body).data('prop') ) // get
|
||||
```
|
||||
|
||||
jQuery `elem.data(prop, val)` делает следующее:
|
||||
<ol>
|
||||
<li>Элемент получает уникальный идентификатор, если у него такого еще нет:
|
||||
|
||||
```js
|
||||
elem[ jQuery.expando ] = id = ++jQuery.uuid; // средствами jQuery
|
||||
```
|
||||
|
||||
`jQuery.expando` -- это случайная строка, сгенерированная jQuery один раз при входе на страницу. Уникальное свойство, чтобы ничего важного не перезаписать.</li>
|
||||
<li>...А сами данные сохраняются в специальном объекте `jQuery.cache`:
|
||||
|
||||
```js
|
||||
jQuery.cache[id]['prop'] = { anything: "любой объект" };
|
||||
```
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
Когда данные считываются из элемента:
|
||||
|
||||
<ol>
|
||||
<li>Уникальный идентификатор элемента извлекается из `id = elem[ jQuery.expando]`.
|
||||
<li>Данные считываются из `jQuery.cache[id]`.</li>
|
||||
</ol>
|
||||
|
||||
Смысл этого API в том, что DOM-элемент никогда не ссылается на JavaScript объект напрямую. Задействуется идентификатор, а сами данные хранятся в `jQuery.cache`. Утечек в IE не будет.
|
||||
|
||||
К тому же все данные известны библиотеке, так что можно клонировать с ними и т.п.
|
||||
|
||||
**Как побочный эффект -- возникает утечка памяти, если элемент удален из DOM без дополнительной очистки.**
|
||||
|
||||
### Примеры утечек в jQuery
|
||||
|
||||
Следующий код создает jQuery-утечку во всех браузерах:
|
||||
|
||||
```js
|
||||
$('<div/>')
|
||||
.html(new Array(1000).join('text')) // div с текстом, возможна AJAX-загрузка
|
||||
.click(function() { })
|
||||
.appendTo('#data')
|
||||
|
||||
document.getElementById('data').innerHTML = ''; // (*)
|
||||
```
|
||||
|
||||
<a href="/files/tutorial/leak/jquery2.html" target="_blank">Показать в отдельном окне</a>
|
||||
|
||||
Утечка происходит потому, что обработчик события в jQuery хранится в данных элемента. В строке `(*)` элемент удален очисткой родительского `innerHTML`, но в `jQuery.cache` данные остались.
|
||||
|
||||
Более того, система обработки событий в jQuery устроена так, что вместе с обработчиком в данных хранится и ссылка на элемент, так что в итоге оба -- и обработчик и элемент -- остаются в памяти вместе со всем замыканием!
|
||||
|
||||
**Ещё более простой пример утечки:**
|
||||
|
||||
Этот код создает утечку:
|
||||
|
||||
```js
|
||||
function go() {
|
||||
$('<div/>')
|
||||
.html(new Array(1000).join('text'))
|
||||
.click(function() { })
|
||||
}
|
||||
```
|
||||
|
||||
<a href="/files/tutorial/leak/jquery1.html" target="_blank">Показать в отдельном окне</a>
|
||||
|
||||
Причина здесь в том, что элемент `<div>` создан, но нигде не размещен :). После выполнения функции ссылка на него теряется. Но обработчик события `click` уже сохранил данные в `jQuery.cache`, которые застревают там навсегда.
|
||||
|
||||
### Используем jQuery без утечек
|
||||
|
||||
Чтобы избежать утечек, описанных выше, для удаления элементов используйте функции jQuery API, а не чистый JavaScript.
|
||||
|
||||
Методы <a href="http://api.jquery.com/remove/">remove()</a>, <a href="http://api.jquery.com/empty">empty()</a> и <a href="http://api.jquery.com/html">html()</a> проверяют дочерние элементы на наличие данных и очищают их. Это несколько замедляет процедуру удаления, но зато освобождается память.
|
||||
|
||||
|
||||
**К счастью обнаружить такие утечки легко. Проверьте размер `$.cache.`** Если он большой и растет, то изучите кэш, посмотрите, какие записи остаются и почему.
|
||||
|
||||
### Улучшение производительности jQuery
|
||||
|
||||
У способа борьбы с утечками IE, применённого в jQuery, есть побочный эффект.
|
||||
|
||||
**Функции, удаляющие элементы, бегают по всему дереву DOM и очищают подэлементы.**
|
||||
|
||||
Представьте себе, что вы получили с сервера большую таблицу (в виде текста), вставили её в документ и хотите обновить. Вызов `$('table').remove()` будет бегать по всем ячейкам и искать в них данные. Но мы-то знаем, что обработчики назначены через делегирование, и тратить на это время ни к чему!
|
||||
|
||||
Чтобы "грязно" удалить элемент, без чистки, можно воспользоваться методом <a href="http://api.jquery.com/detach">detach()</a>. Его официальное назначение -- в том, чтобы убрать элемент из DOM, но сохранить возможность для вставки (и, соответственно, оставить на нём все данные). А неофициальное -- быстро убрать элемент из DOM. Если на нём нет данных и обработчиков, то всё хорошо.
|
||||
|
||||
В принципе, если хочется всё сделать чисто, но быстро -- никто не мешает сделать `elem.detach()` и поместить вызов `elem.remove()` в `setTimeout`. В результате очистка будет происходить ассинхронно и незаметно.
|
||||
|
||||
Итак, будем надеяться, что эта тема для вас теперь прозрачна и ясна и в следующих разделах мы не будем больше говорить об утечках в jQuery.
|
||||
|
||||
## Поиск и устранение утечек памяти
|
||||
|
||||
### Проверка на утечки
|
||||
|
@ -472,9 +314,4 @@ firefox --profilemanager
|
|||
|
||||
Утечки памяти штука довольно сложная. В борьбе с ними вам определенно понадобится одна вещь: *Удача!*
|
||||
|
||||
<div style="text-align:center">
|
||||
<img src="goodluck.png">
|
||||
</div>
|
||||
|
||||
<p style="text-align:right;font-style:italic">Перевести с английского варианта<br> учебника помог Марат Шагиев</p>
|
||||
|
||||
|
|
32
3-more/4-optimize/1-memory-leaks/leak-ie8-2.view/index.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<script>
|
||||
// Утечка в IE8 standards mode, а также в IE9 в режиме IE8
|
||||
// См. http://blog.j15r.com/2009/07/memory-leaks-in-ie8.html
|
||||
function leak() {
|
||||
// Создаём новый DIV, добавляем к BODY
|
||||
var elem = document.createElement('div');
|
||||
document.body.appendChild(elem);
|
||||
|
||||
elem.__expando = {
|
||||
bigAss: new Array(1000000).join('lalala'),
|
||||
method: function() {} // создаётся круговая ссылка через замыкание
|
||||
};
|
||||
|
||||
// Удалить элемент из DOM. Браузер должен очистить память.
|
||||
elem.parentElement.removeChild(elem);
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>Нажимайте на кнопку и наблюдайте, как увеличивается количество занимаемой браузером памяти.</p>
|
||||
<button onclick="leak()">Создать утечку!</button>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
|
||||
var elem = document.createElement('div'); // любой элемент
|
||||
|
||||
// Течёт в настоящем IE8, Standards Mode
|
||||
// Не течёт в других IE. Не течёт в IE9 в режиме совместимости с IE8
|
||||
function leak() {
|
||||
|
||||
for (var i = 0; i < 2000; i++) {
|
||||
|
||||
elem.innerHTML = '<table><tr><td>1</td></tr></table>';
|
||||
|
||||
elem.firstChild.rows[0]; // просто доступ через rows[] приводит к утечке
|
||||
// при том, что мы даже без сохраненяем значение в переменную
|
||||
|
||||
elem.removeChild(elem.firstChild); // удалить таблицу
|
||||
// elem.innerHTML = '' очистил бы память, он работает по-другому, см. главу "управление памятью"
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<p>Нажимайте на кнопку и наблюдайте, как увеличивается количество занимаемой браузером памяти.</p>
|
||||
<button onclick="leak()">Создать утечку!</button>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<script>
|
||||
// Утечка в IE8 standards mode, а также в IE9 в режиме IE8
|
||||
// См. http://blog.j15r.com/2009/07/memory-leaks-in-ie8.html
|
||||
function leak() {
|
||||
// Создаём новый DIV, добавляем к BODY
|
||||
var elem = document.createElement('div');
|
||||
document.body.appendChild(elem);
|
||||
|
||||
// Записываем в свойство ссылку на объект
|
||||
var menu = new Menu(elem);
|
||||
elem.menu = menu;
|
||||
|
||||
// Удалить элемент из DOM. Браузер должен очистить память.
|
||||
elem.parentElement.removeChild(elem);
|
||||
}
|
||||
|
||||
function Menu(elem) {
|
||||
elem.onclick = function() {};
|
||||
this.bigAss = new Array(1000000).join('lalala');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<p>Нажимайте на кнопку и наблюдайте, как увеличивается количество занимаемой браузером памяти.</p>
|
||||
<button onclick="leak()">Создать утечку!</button>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Страница создаёт объект <code>XMLHttpRequest</code> каждые 50мс.</p>
|
||||
|
||||
<p>Смотрите на память, она течёт в IE<9.</p>
|
||||
|
||||
<script>
|
||||
function leak() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('GET', 'something.js?'+Math.random(), true);
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if(xhr.readyState == 4 && xhr.status == 200) {
|
||||
document.getElementById('test').innerHTML++
|
||||
}
|
||||
}
|
||||
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
onload = function() {
|
||||
setInterval(leak, 50);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>Количество запросов: <span id="test">0</span></div>
|
||||
|
||||
</body>
|
||||
</html>
|
36
3-more/4-optimize/1-memory-leaks/leak-ie8.view/index.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<script>
|
||||
// Утечка в IE8 standards mode, а также в IE9 в режиме IE8
|
||||
// См. http://blog.j15r.com/2009/07/memory-leaks-in-ie8.html
|
||||
function leak() {
|
||||
// Создаём новый DIV, добавляем к BODY
|
||||
var elem = document.createElement('div');
|
||||
document.body.appendChild(elem);
|
||||
|
||||
// Записываем в свойство жирный объект
|
||||
elem.__expando = {
|
||||
bigAss: new Array(1000000).join('lalala')
|
||||
};
|
||||
|
||||
// Создаём круговую ссылку. Без этой строки утечки не будет.
|
||||
elem.__expando.__elem = elem;
|
||||
|
||||
// Удалить элемент из DOM. Браузер должен очистить память.
|
||||
elem.parentElement.removeChild(elem);
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>Нажимайте на кнопку и наблюдайте, как увеличивается количество занимаемой браузером памяти.</p>
|
||||
<button onclick="leak()">Создать утечку!</button>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
125
3-more/4-optimize/2-memory-leaks-jquery/article.md
Normal file
|
@ -0,0 +1,125 @@
|
|||
|
||||
# Утечки памяти при использовании jQuery
|
||||
|
||||
В jQuery для хранения обработчиков событий и других вспомогательных данных, связанных с DOM-элементами, используется внутренний объект, который в jQuery 1 доступен через <a href="http://api.jquery.com/jQuery.data/">$.data</a>.
|
||||
|
||||
В jQuery 2 доступ к нему закрыт через замыкание, он стал локальной переменной внутри jQuery с именем `data_priv`, но в остальном всё работает точно так, как описано, и с теми же последствиями.
|
||||
|
||||
## $.data
|
||||
|
||||
Встроенная функция `$.data` позволяет хранить привязывать произвольные значения к DOM-узлам.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
// присвоить
|
||||
$(document).data('prop', { anything: "любой объект" })
|
||||
|
||||
// прочитать
|
||||
alert( $(document).data('prop').anything ) // любой объект
|
||||
```
|
||||
|
||||
Реализована она хитрым образом. Данные не хранятся в самом элементе, а во внутреннем объекте jQuery.
|
||||
|
||||
jQuery-вызов `elem.data(prop, val)` делает следующее:
|
||||
|
||||
<ol>
|
||||
<li>Элемент получает уникальный идентификатор, если у него такого еще нет:
|
||||
|
||||
```js
|
||||
elem[ jQuery.expando ] = id = ++jQuery.uuid; // средствами jQuery
|
||||
```
|
||||
|
||||
`jQuery.expando` -- это случайная строка, сгенерированная jQuery один раз при входе на страницу. Уникальное свойство, чтобы ничего важного не перезаписать.</li>
|
||||
<li>...А сами данные сохраняются в специальном объекте `jQuery.cache`:
|
||||
|
||||
```js
|
||||
jQuery.cache[id]['prop'] = { anything: "любой объект" };
|
||||
```
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
Когда данные считываются из элемента:
|
||||
|
||||
<ol>
|
||||
<li>Уникальный идентификатор элемента извлекается из `id = elem[ jQuery.expando]`.
|
||||
<li>Данные считываются из `jQuery.cache[id]`.</li>
|
||||
</ol>
|
||||
|
||||
Смысл этого API в том, что DOM-элемент никогда не ссылается на JavaScript объект напрямую. Задействуется идентификатор, а сами данные хранятся в `jQuery.cache`. Утечек в IE не будет.
|
||||
|
||||
К тому же все данные известны библиотеке, так что можно клонировать с ними и т.п.
|
||||
|
||||
Как побочный эффект -- возникает утечка памяти, если элемент удален из DOM без дополнительной очистки.
|
||||
|
||||
## Примеры утечек в jQuery
|
||||
|
||||
Следующий код создает jQuery-утечку во всех браузерах:
|
||||
|
||||
```js
|
||||
$('<div/>')
|
||||
.html(new Array(1000).join('text')) // div с текстом, возможна AJAX-загрузка
|
||||
.click(function() { })
|
||||
.appendTo('#data')
|
||||
|
||||
document.getElementById('data').innerHTML = ''; // (*)
|
||||
```
|
||||
|
||||
Полный пример:
|
||||
|
||||
[codetabs src="jquery-leak"]
|
||||
|
||||
Утечка происходит потому, что обработчик события в jQuery хранится в данных элемента. В строке `(*)` элемент удален очисткой родительского `innerHTML`, но в `jQuery.cache` данные остались.
|
||||
|
||||
Более того, система обработки событий в jQuery устроена так, что вместе с обработчиком в данных хранится и ссылка на элемент, так что в итоге оба -- и обработчик и элемент -- остаются в памяти вместе со всем замыканием!
|
||||
|
||||
Ещё более простой пример утечки:
|
||||
|
||||
Этот код также создает утечку:
|
||||
|
||||
```js
|
||||
function go() {
|
||||
$('<div/>')
|
||||
.click(function() { })
|
||||
}
|
||||
```
|
||||
|
||||
...То есть, мы создаём элемент, вешаем на него обработчик... И всё.
|
||||
|
||||
Такой код ведёт к утечке памяти как раз потому, что элемент `<div>` создан, но нигде не размещен :). После выполнения функции ссылка на него теряется. Но обработчик события `click` уже сохранил данные в `jQuery.cache`, которые застревают там навсегда.
|
||||
|
||||
## Используем jQuery без утечек
|
||||
|
||||
Чтобы избежать утечек, описанных выше, для удаления элементов используйте функции jQuery API, а не чистый JavaScript.
|
||||
|
||||
Методы <a href="http://api.jquery.com/remove/">remove()</a>, <a href="http://api.jquery.com/empty">empty()</a> и <a href="http://api.jquery.com/html">html()</a> проверяют дочерние элементы на наличие данных и очищают их. Это несколько замедляет процедуру удаления, но зато освобождается память.
|
||||
|
||||
К счастью обнаружить такие утечки легко. Проверьте размер `$.cache`. Если он большой и растет, то изучите кэш, посмотрите, какие записи остаются и почему.
|
||||
|
||||
## Улучшение производительности jQuery
|
||||
|
||||
У способа организации внутренних данных, применённого в jQuery, есть важный побочный эффект.
|
||||
|
||||
Функции, удаляющие элементы, также должны удалить и связанные с ними внутренние данные. Для этого нужно для каждого удаляемого элемента проверить -- а нет ли чего во внутреннем хранилище? И, если есть -- удалить.
|
||||
|
||||
Представим, что у нас есть большая таблица `<table>`, и мы хотим обновить её содержимое на новое. Вызов `$('table').html(новые данные)` перед вставкой новых данных аккуратно удалит старые: пробежит по всем ячейкам и проверит внутреннее хранилище.
|
||||
|
||||
Если это большая таблица, то обработчики, скорее всего, стоят не на ячейках, а на самом элементе `<table>`, то есть используется делегирование. А, значит, тратить время на проверку всех подэлементов ни к чему.
|
||||
|
||||
Но jQuery-то об этом не знает!
|
||||
|
||||
Чтобы "грязно" удалить элемент, без чистки, мы можем сделать это через "обычные" DOM-вызовы или воспользоваться методом <a href="http://api.jquery.com/detach">detach()</a>. Его официальное назначение -- в том, чтобы убрать элемент из DOM, но сохранить возможность для вставки (и, соответственно, оставить на нём все данные). А неофициальное -- быстро убрать элемент из DOM, без чистки.
|
||||
|
||||
Возможен и промежуточный вариант: никто не мешает сделать `elem.detach()` и поместить вызов `elem.remove()` в `setTimeout`. В результате очистка будет происходить асинхронно и незаметно.
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Утечки памяти при использовании jQuery возможны, если через DOM-методы удалять элементы, к которым привязаны данные или обработчики.</li>
|
||||
<li>Чтобы утечки не было, достаточно убедиться, что элемент удаляется при помощи методов jQuery.</li>
|
||||
<li>Побочный эффект -- при удалении элементов jQuery должна проверить наличие данных для них. Это сильно замедляет процесс удаления большого поддерева DOM.</li>
|
||||
<li>Если мы значем, что обработчиков и данных нет -- гораздо быстрее удалять элементы при помощи вызова `detach` или обычного DOM.</li>
|
||||
</ul>
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script src="http://code.jquery.com/jquery.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="data"></div>
|
||||
|
||||
<script>
|
||||
|
||||
function go() {
|
||||
|
||||
$('<div/>')
|
||||
.html(new Array(1000).join('text'))
|
||||
.click(function() { })
|
||||
.appendTo('#data');
|
||||
|
||||
document.getElementById('data').innerHTML = '';
|
||||
|
||||
}
|
||||
|
||||
var interval = setInterval(go, 10)
|
||||
</script>
|
||||
|
||||
Утечка идёт...
|
||||
|
||||
<input type="button" onclick="clearInterval(interval)" value="stop"/>
|
||||
|
||||
</body>
|
||||
</html>
|
0
3-more/5-compress/1-minification/my.svg → 3-more/4-optimize/3-minification/my.svg
Executable file → Normal file
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
@ -132,20 +132,27 @@ window.isIE = window.isIE || getBrowserVersion();
|
|||
|
||||
## Убираем вызовы console.*
|
||||
|
||||
Минификатор имеет в своём распоряжении дерево кода и может удалить ненужные вызовы. Хотя по умолчанию в UglifyJS и GCC такого флага нет, код минификатора можно легко расширить, добавив эту возможность.
|
||||
Минификатор имеет в своём распоряжении дерево кода и может удалить ненужные вызовы.
|
||||
|
||||
Для UglifyJS это делают опции компилятора:
|
||||
<ul>
|
||||
<li>Для UglifyJS функция обхода дерева:
|
||||
<li>`drop_debugger` -- убирает вызовы `debugger`.</li>
|
||||
<li>`drop_console` -- убирает вызовы `console.*`.</li>
|
||||
</ul>
|
||||
|
||||
Можно написать и дополнительную функцию преобразования, которая убирает другие вызовы, например для `log.*`:
|
||||
|
||||
```js
|
||||
//+ hide="Раскрыть функцию"
|
||||
var uglify = require('uglify-js');
|
||||
var pro = uglify.uglify;
|
||||
|
||||
function ast_squeeze_console(ast) {
|
||||
var w = pro.ast_walker(),
|
||||
walk = w.walk,
|
||||
scope;
|
||||
return w.with_walkers({
|
||||
"stat": function(stmt) {
|
||||
if(stmt[0] === "call" && stmt[1][0] == "dot" && stmt[1][1] instanceof Array && stmt[1][1][0] == 'name' && stmt[1][1][1] == "console") {
|
||||
if(stmt[0] === "call" && stmt[1][0] == "dot" && stmt[1][1] instanceof Array && stmt[1][1][0] == 'name' && stmt[1][1][1] == "log") {
|
||||
return ["block"];
|
||||
}
|
||||
return ["stat", walk(stmt)];
|
||||
|
@ -161,22 +168,5 @@ function ast_squeeze_console(ast) {
|
|||
};
|
||||
```
|
||||
|
||||
Полный код, использующий эту функцию и модуль uglify-js для сжатия с убиранием вызова: [myuglify.js](/files/tutorial/compress/myuglify.js).
|
||||
|
||||
Вы можете добавить свои любимые опции и использовать его вместо поставляемого "из коробки".
|
||||
</li>
|
||||
<li>В GCC соответствующие опции называются `stripNameSuffixes` и `stripTypePrefixes`, но они скрыты при запуске минификатора из командной строки или через веб-сервис.
|
||||
|
||||
Чтобы их использовать, нужно либо поставить утилиту [plovr](http://plovr.com/), что эквивалентно стрельбе из пушки по воробьям, либо написать свой Java-код для вызова компилятора, который будет ставить опции.
|
||||
|
||||
Выглядеть этот код может [примерно так](https://groups.google.com/d/msg/closure-compiler-discuss/qqdnzikzpWA/8cPHKIR1svgJ).
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Аналогичным образом можно убрать и любой другой код, например вызовы вашего собственного логгера.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Эту функцию следует вызвать на результате `parse`, и она пройдётся по дереву и удалит все вызовы `log.*`.
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
# Оптимизация
|
||||
|
||||
Утечки памяти, увеличение скорости выполнения и загрузки скриптов.
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
# Сжатие JavaScript
|
||||
|