javascript
UserJS для воспроизведения H.264 в «Опере»
Существует плагин «DivX web player», который отлично подменяет тег «VIDEO», в случае, если должен быть использован кодек H.264, но браузер его не поддерживает.
К сожалению, в «Опере» он
Самая засада — это тег «VIDEO» без размеров. Мне для поддержки этого приходтся запускать проигрывание и когда становятся доступны медиаданные, останавливать воспроизведение и переписывать размеры видео.
// ==UserScript==
// @name XVid patch
// @author Evgeny Stepanischev aka Bolk
// @version 1.00
// @namespace /files/opera-xvid-patch.js
// @modified 2012-04-14
// @include http://*
// ==/UserScript==
!function () {
var htmlif = function (attr, attrname) {
return attr ? attrname + '="' + attr + '" ' : '';
}
var patchtag = function (o, src) {
var html = '<embed type="video/divx" '+
'custommode="none" ';
if (o.preload == 'auto') html += 'bufferingMode="full" '; else
if (o.preload == 'none') html += 'bufferingMode="null" ';
html += htmlif(o.poster, 'previewImage')+
htmlif(o.id, 'id')+
htmlif(o.name, 'name')+
htmlif(src || o.src, 'src')+
htmlif(o.controls ? 'full' : 'null', 'mode')+
htmlif(o.width, 'width')+
htmlif(o.height, 'height');
if (!o.height || !o.width) {
html += 'onloadedmetadata="this.width=this.videoWidth;this.height=this.videoHeight" ';
html += htmlif(!o.autoplay && 'if (!this._sized) {this.pause(); this.currentTime=0; this._sized=1}', 'onplay');
html += 'autoPlay=true ';
} else {
html += htmlif(o.autoplay ? 'true' : 'false', 'autoPlay');
}
html += 'pluginspage="http://go.divx.com/plugin/download/"></embed>';
o.outerHTML = html;
};
var patch = function () {
window.opera.addEventListener('BeforeScript', function() {
with (window.HTMLVideoElement)
if (prototype._canPlayType === undefined) {
prototype._canPlayType = prototype.canPlayType;
prototype.canPlayType = function (type) {
if (type.substr(0, 9) == 'video/mp4') {
return 'probably';
}
return this._canPlayType(type);
}
}
!function() {
var videos = document.getElementsByTagName('VIDEO');
for (var i = 0, vl = videos.length; i<vl; i++) {
if (!videos[i]) continue;
if (videos[i].type && videos[i].type.substr(0, 9) == 'video/mp4') {
} else {
var sources = videos[i].getElementsByTagName('source');
var foundSupported = false, foundMP4 = false;
for (var j = 0, sl = sources.length; j < sl; j++) {
if (videos[i]._canPlayType(sources[j].type) !== '') {
foundSupported = true;
break;
}
if (sources[j].type.substr(0, 9) == 'video/mp4') {
foundMP4 = sources[j].src;
}
}
if (!foundSupported && foundMP4) {
patchtag(videos[i], foundMP4);
}
}
}
}();
});
};
for (var i = 0, l = navigator.plugins.length; i < l; i++) {
if (navigator.plugins[i].filename == 'DivXBrowserPlugin.plugin') {
return patch();
}
}
}();Добавлено: в комментариях справдливо указали на недостаток — перехватывать надо не только BeforeScript. Исправлять не буду до первого реального случая, когда
Работа с камерой и распознавание чисел в «Опере»

Я вчера заморочился на отличненько. Мне показалось скучным и грустным вводить цифры счёта за квартиру в окошко «Госуслуг» и решил сделать автоматическую распознавалку чисел, попутно изучив работу с камерой из браузера.
Доступ к камере — эксперментальный стандарт, поэтому мало кто из браузеров может им похвастаться, я использовал специальный билд «
АПИ оказалось несложным. Всё, что нужно сделать — соединить специальным образом тег VIDEO с источником стримового видео; потом я 10 раз в секунд забираю кадр с видео и кладу в CANVAS (на скриншоте он цветной), оттуда вырезаю небольшую область, перевожу в градации серого, беру за порог 75% от усреднённого цвета и перевожу всё в ч/б.
Больше всего из трёх часов, которые у меня ушли на это, я делал распознование цифр. Алгоритмы, которые я нашёл в интернете, были очень монструозными, да и переписывать готовое было скучно, поэтом я перепробовал несколько вариантов, которые пришли мне в голову.
В итоге, лучше всех распознаёт следующий алгоритм.
Сначала я очевидным образом разбиваю изображение на цифры, потом условно разделяю каждую цифру вертикальной линией по середине. Слева и справа от линий считаю количество переходов между чёрным и белым, двигаясь сверху вниз. Получаются комбинации из пар чисел. Я их прореживаю — одинаковые последовательно идущие заменяю на одно значение.
Получаются цепочки, которые уже достаточно хорошо характеризуют цифру, правда некоторые цифры этот паттерн не в состоянии распознать. Поэтому я считаю количетсво ч/б переходов ещё и по моей умозрительной вертикальной линии.
На скриншоте в большом текстовом поле записаны получившиеся комбинации. Первое — номер по порядку, далее (через двоеточие) — количество переходов по вертикали и потом описанная последовательность. Например, «ноль» записывается как «2:11» (два перехода по вертикали, потом одинаковые переходы по разу слева и справа разделительной линии).
Алгоритм «шумит»,
Каррирование и частичное применение
Я, оказывается, путал раньше каррирование и частичное применение. Даже не то чтобы путал, просто не пытался разобраться в чём разница между терминами, применяя то и другое по мере надобности. Недавно на «Хабре» в комментариях мне на моё незнание указали и я уяснил, наконец, что конкретно стоит за каждым термином.
Каррирование — преобразование функции от двух аргументов в функцию от первого аргумента, возвращающую функцию, результат вызова которой со вторым агрументом эквивалентен вызову первоначальной функции с упомянутыми аргументами. Немного запутанно, с примерами станет всё яснее. Я буду объяснять на примере Джаваскрипта, его больше народу знает, а на ПХП очень уж многословно получается. Кстати, в соответствующую статью в «Википедии» я добавил пример на «Гугл Гоу».
// функция, суммирующая два числа
function sum(x, y) {
return x+y;
}
alert(sum(2, 8)); // вернёт 10
function sum(x) {
return function (y) {
return x+y;
}
}
alert(sum(2)(8)); // тоже вернёт 10При вызове второй функции sum создаётся функция, в которую
Частичное применение функции (Partial function) — создание некой функции на базе указанной, где некоторые агрументы заменены конкретными значениями. Например:
function partial_one_arg(f, a) {
return function(b) {
return f(a, b);
}
}
min0 = partial_one_arg(Math.min, 0);
min1 = partial_one_arg(Math.min, 1);
alert([min0(-5), min0(5)]); // вернёт [-5, 0]
alert([min1(-5), min1(5)]); // вернёт [-5, 1]В примере я заменил один из агрументов фунции нахождения минимального значения на конкретное значение, теперь при вызове функции «min0» сравнение будет идти с нулём, при вызове «min1» — с единицей.
Очень простые штуки, часто встречающиеся в функциональном программировании.
%u → UTF-8
Столкнулся сегодня с задачей — надо сконвертировать пришедшую из JavaScript строку (в ней символы Юникода в виде «%uXXXX»)
echo json_decode(str_replace('%u', '\\u', json_encode($str_from_javascript)));С Новым годом, что ли?
Я тут, чтобы мозг совсем не расслаблялся на праздниках, убил около часа и вручную сделал на Яваскрипте небольшое поздравление с Новым годом:
[][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})
[-~[]]+([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+
!![]]+([]+!![])[+![]]+([]+!![])[+!![]]+
([]+!![])[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+
!![])]+([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])
[+!![]]][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})
[-~[]]+([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+
([]+!![])[+![]]+([]+!![])[+!![]]+([]+!![])
[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+
([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])[+!![]]]
(([]+!![])[+!![]]+([]+{})[-~[]<<[-~[]+-~[]]]+
([]+!![])[-[]]+([]+[][{}])[-[]]+([]+!![])[+!![]]+
([]+[][{}])[-~[]]+([]+{})[~[]+(+!![]<<!![]+!![]+
!![])]+([]+![])[!![]+!![]+!![]]+([]+![])[!![]<<!![]+
!![]]+([]+![])[!![]+!![]]+([]+![])[-[]])()
[([{}-{}]+[])[-~[]]+([]+![])[!![]+!![]]+([]+![])
[!![]<<!![]+!![]]+([]+!![])[-~[]]+([]+!![])[+[]]](
[][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})
[-~[]]+([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+
!![]]+([]+!![])[+![]]+([]+!![])[+!![]]+
([]+!![])[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+
([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])[+!![]]]
[([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+
([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+
([]+!![])[+![]]+([]+!![])[+!![]]+([]+!![])
[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+
([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])[+!![]]]
(([]+!![])[+!![]]+([]+{})[-~[]<<[-~[]+-~[]]]+
([]+!![])[-[]]+([]+[][{}])[-[]]+([]+!![])
[+!![]]+([]+[][{}])[-~[]]+([]+{})[~[]+(+!![]<<!![]+
!![]+!![])]+([]+![])[!![]+!![]+!![]]+([]+![])
[!![]<<!![]+!![]]+([]+![])[!![]+!![]]+([]+![])
[-[]])()[([]+![])[-~[]]+([]+!![])[+![]]+([]+{})
[-~[]]+([]+{})[!![]+!![]]](([]+![])[-~[]]+
([][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+
([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])
[+![]]+([]+!![])[+!![]]+([]+!![])[-[~[]+~[]]]+
([]+{})[-~[]+(!![]<<!![]+!![])]+([]+!![])[+![]]+
([]+{})[-~[]]+ ([]+!![])[+!![]]]+[])[(-~[]+[+![]])-!![]])+
([]+![])[-~[]]+
(/!/[([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+([]+[][{}])
[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])[+![]]+([]+!![])[+!![]]+([]+!![])
[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+([]+!![])[+![]]+([]+{})
[-~[]]+([]+!![])[+!![]]]+[])[+!![]+[!![]<<!![]-~[]]]+
(/!/[([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+([]+[][{}])
[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])[+![]]+([]+!![])[+!![]]+([]+!![])
[-[~[]+~[]]]+([]+{})[-~[]+(!![]<<!![]+!![])]+([]+!![])[+![]]+([]+{})
[-~[]]+([]+!![])[+!![]]]+[])[+!![]+[!![]<<!![]-~[]]]+
([]+[~[]/[]])[[[!![]+!![]]*[!![]+!![]]]*[!![]+!![]]]+
([]+{})[~[]+(+!![]<<!![]+!![]+!![])]+
([]+[][{}])[-~[]]+
([]+{})[-~[]<<[-~[]+-~[]]]+
([][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})
[-~[]]+([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])
[+![]]+([]+!![])[+!![]]+([]+!![])[-[~[]+~[]]]+([]+{})[-~[]+
(!![]<<!![]+!![])]+([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])
[+!![]]][([]+{})[-~[]+(!![]<<!![]+!![])]+([]+{})[-~[]]+
([]+[][{}])[-~[]]+([]+!{})[!![]+!![]+!![]]+([]+!![])[+![]]+
([]+!![])[+!![]]+([]+!![])[-[~[]+~[]]]+([]+{})[-~[]+
(!![]<<!![]+!![])]+([]+!![])[+![]]+([]+{})[-~[]]+([]+!![])
[+!![]]](([]+!![])[+!![]]+([]+{})[-~[]<<[-~[]+-~[]]]+
([]+!![])[-[]]+([]+[][{}])[-[]]+([]+!![])[+!![]]+([]+[][{}])
[-~[]]+([]+{})[~[]+(+!![]<<!![]+!![]+!![])]+([]+![])
[!![]+!![]+!![]]+([]+![])[!![]<<!![]+!![]]+([]+![])
[!![]+!![]]+([]+![])[-[]])()+[])[([]+![])[!![]+!![]+
!![]]+([]+![])[!![]+!![]]+([]+[][{}])[!![]+(!![]<<!![]+!![])]+
([]+{})[!![]+(!![]<<!![]+!![])]+([]+!![])[!![]+!![]+!![]]](~[]+~[])[-[]]+
([]+{})[~[]+(+!![]<<!![]+!![]+!![])]+
(~[]/[]+[])[-~[]<<-(~[]+~[]+~[])]+
([~[]<[]]+[])[-[~[]+~[]+~[]]]+
([{}-{}]+[])[-~[]]+
([]+!![])[+!![]]+
(/!/+[])[-~[]])Запускать лучше всего в консоли браузера (в Firebug, Dragonfly и так далее). Цели сократить код у меня не было, более того,
Если
Почему не следует расширять встроенные типы JavaScript
На Хабре хорошая статья — «
Поддержка MJPEG в «Опере»
Как я уже писал, «Опера» иначе показывает MJPEG, не как остальные браузеры. Если открыть
А мне хочется смотреть
Вдохновившись библиотекой добавляющей нативную поддержку APNG в браузер, я подумал, что я ничем не хуже и смогу добавить поддержку MJPEG в «Оперу». Получился небольшой userjs, который я, естественно, выложил на «Гитхаб».
Идея такая — находим картинку во всех тегах IMG, у которой расширение «mjpg». Через XHR получаем с сервера видеопоток, копим кадр, подменяем найденную картинку и так далее.
Работает начиная с 12 версии браузера, которая ещё не вышла (кстати, в позавчерашней сборке сломали userjs напрочь) — в предыдущих версиях XHR слабоват по возможностям для этого.
Больше всего я бился с декодированием бинарных данных. Последняя версия «Оперы» поддерживает тип ответа arraybuffer (а вот blob не поддерживается), но если его выбрать, то данные в цикле
Кстати, быстрее всего у меня получилось декодировать не циклом, а регулярными выражениями, дальше тормозит уже не мой код, а способ получения отдельного кадра — каждый раз я соединяюсь с сервером заново. Можно бы накапливать кадры пачкой и потом их сбрасывать, но у меня нет задачи смотреть трансляции футбола, у меня дом, который не бегает и полученной скорости мне вполне хватает
В общем, получился довольно небольшой код — 94 строки. Если вы не в курсе как устанавливаются скрипты userjs, то это очень просто. В интернете есть много инструкций на эту тему. Только напоминаю, что «Опера» нужна двенадцатая, причём в сборке за седьмое октября userjs сломаны.Посмотреть код.
// ==UserScript==
// @name MJPEG+Opera - Add to Opera MJPEG support
// @author Evgeny Stepanischev aka Bolk
// @version 1.00
// @namespace /files/opera-mjpeg.js
// @modified 2011-10-09
// @include *
// ==/UserScript==
opera.addEventListener('BeforeEvent.DOMContentLoaded', function() {
if (opera.version() < 12) return;
var BolkMJpeg = function (url, img) {
var req = new XMLHttpRequest();
img.src = 'about:blank'; // Сброс картинки, чтобы прекратить передачу
// Асинхронно забираем кадр при помощи XHR, нам придёт видеопоток
var start = function (url) {
req.open("GET", url, true);
// Свежепридуманный хитрый хак, позволяет получить данные в UCS-2
req.overrideMimeType('image/jpeg');
req.send(null);
}
// Хитрыми путями восстанавливаем порядок байт в reponse, там сейчас UCS-2,
// получаем просто последовательность байт
var reqbytes = function (n, l) {
return escape(req.response.substr(n, l)).
replace(/([^%]|%[^u].|%u....)/g, '<$1>').
replace(/<%u(..)(..)>/g, '%$2%$1').
replace(/<%(..)>/g, '%$1%00').
replace(/<(.)>/g, '$1%00');
}
// заменяем картинку на бинарные данные
var draw = function (bin) {
img.src = 'data:image/jpeg,' + bin;
}
// На изменение состояние входных данных собираем по частям JPEG
req.onreadystatechange = function () {
// состояние «3» — данные пошли, но ещё не кончились
if (req.readyState === 3) {
var hnd = setInterval(function () {
// по заголовку получаем размер данных
var header = unescape(reqbytes(0, 300)).toLowerCase();
var idx = header.indexOf('content-length:');
if (idx > -1) {
// 15 — длина заголовка
var len = parseInt(header.substr(idx + 15), 10);
var headend = header.indexOf("\r\n\r\n");
// дожидаемся конца заголовка
if (headend > -1) {
var resp = reqbytes(0, len);
// умножаем всё на три, так как reqbytes даёт esc-последовательность
// вида %XX%XX…
len *= 3;
headend = (headend + 4) * 3;
// если собрали весь JPEG, обрываем соединение
// и начинаем новый цикл. Мы могли бы собирать кадры и дальше, но
// это съест всю память со временем
if (resp.length >= len + headend) {
req.abort();
resp = resp.substr(headend, len);
draw(resp);
clearInterval(hnd);
start(url);
}
}
}
}, 100);
}
}
start(url);
};
// ищем картинку, у которой расширение mjpg
var imgs = document.getElementsByTagName('IMG');
for (var i = 0, len = imgs.length; i<len; i++) {
if (/\.mjpg$/.test(imgs[i].src)) {
BolkMJpeg(imgs[i].src, imgs[i]);
break;
}
}
}, true);
IEWebGL
Микрософт пока не собирается реализовывать WebGL в своём браузере, так как считает, что технология не устоялась (и даже называла её небезопасной). Для тех, кто
Плагин показывает отличную производительность (до 100 fps под IE9 и 10PP, до 60 — под IE8), работает начиная с IE6, не требует OpenGL и менять в HTML или JavaScript сайта так же ничего не требуется.
Компания «Нетскейп» была первой в деле серверного Джаваскрипта
Чувствую себя археологом. После утренних изысканий за тарелкой пельменей, мне удалось установить точную дату релиза (уже добавил в «Википедию») Netscape Enterprise Server 2.0 — 5 марта 1996 года. Именно в этом сервере впервые появился серверный Джаваскрипт.
Вторая (по всей видимости) реализация была у Микрософт — в «Википедии» написано, что версия JScript для IIS стала доступна в январе 1997 года.
Кстати, публичная бета ASP появилась много раньше — в октябре 1996, как апгрейд для IIS 2.0. Но сайты можно было разрабатывать только на VBScript.
Так что в этом году исполнилось 15 лет серверному Джаваскрипту.
Хочу развеять миф про серверный Джаваскрипт
С появлением фреймворка Node.js сервервый Джаваскрипт стал широко рекламироваться, но даже ребята, слышавшие об ASP,
Это не так. Например, в микрософтском ASP можно было использовать два языка — JScript и VBScript, в моей практике была
Об ASP у меня остались самые худшие воспоминания, например, в тамошнем API не было ничего, что помогло бы программисту обработать заливаемые файлы, нужно было пользоваться сторонним ActiveX (они работают и на серверной стороне). Наверняка потом там много изменилось в плане удобства, но я уже не застал.
А ещё раньше я читал о сервере от «Нетскейп», который тоже содержал Джаваскрипт в качестве языка серверного программирования. Сравнивая давность своих воспоминаний, я прихожу к выкоду, что «Нетскейп» был первым на этот поприще. К сожалению, я не смог навскидку найти информации об этом сервере, но когда я
JavaScript GeoAPI и timeout
Если вы пользовались
Коллега из «Яндекса» провёл исследование — попросил всех знакомых зайти на специальную страницу, где использовалось Geolocation API. Выводы любопытные:
Среднее время работы геолокации 8 секунд. Максимальное 24 сек. Но в 15 секунд уложились все устройства, кроме одного.
C первой попытки с тестом справилось 18 из 21 устройства.
Всегда стоит использовать геоопредление высокой точности, так как геоопределение низкой точности не работает на Андроидах без включенной геолокации по wifi сетям. Щупанье чужих телефонов показало что как правило люди ее не включают.
Добавление поддержки APNG в браузеры, которые его не поддерживают
В ЖЖ нашлась библиотека добавляющая поддержку APNG в браузеры, которые его не поддерживают — то есть в «Хром», «Сафари» и «ИЕ». Принцип простой (но повозиться автору пришлось много) — файл вытягивается через AJAX, парсится на кадры, кадры последовательно отображаются в CANVAS.
Проверка типа соединения в «Андроидах»
Оказывается в «Андроидах», начиная с версии 2.2 есть специальное свойство у navigator.connection, которое позволяет узнать тип соединения: «navigator.connection.type». Объект «navigator.connection» содержит так же несколько констант, с самодокументируемыми названиями, которые содержат все возможные значения типа соединения: UNKNOWN, ETHERNET, WIFI, CELL_2G, CELL_3G.
with (navigator.connection)
if (type == CELL_2G || type == CELL_3G) {
// медленное и/или платное
}На последней, на данный момент прошивке iOS на iPhone 4, увы, это свойство не поддерживается.
location.reload() ≠ location.href=location.href
Увидел у Никиты Васильева во внутренних бложиках, что вызов «location.reload()» не эквивалентен присваиванию location.href в себя же. Оказывается, последний перезагружает страницу из кеша во всех браузерах, кроме «Оперы».
Рабочая задача: карта + объекты
Ничего экстраординарного, просто задача по работе попалась.
Дана карта местности (картинка), на неё нанесено большое количество объектов, несколько тысяч. В каждый объект можно тыцнуть мышкой, карта большая, её надо позволять «таскать» мышкой.
Текущее решение тормозит, а надо сделать так, чтобы не тормозило.
У меня была гипотеза, что если нарисовать всё это дело на большущем CANVAS, выходящем за пределы экрана и позволить его таскать мышкой, то тормозит не будет.

При клике происходит поиск объекта по списку (безо всякой оптимизации пока), если объект найден, то выводит информацию по цвету, если зажать кнопку мыши, срабатывает флаг «пользователь желает тащить», если пошевелить в этот момент мышью, координаты CANVAS будут меняться вслед за мышью.
Вроде не тормозит, хороший способ делать карты. Делюсь, вдруг пригодится кому.
JS CSS в одном файле у меня на сайте
Я
Особых трудностей не встретил, только убрал все комментарии из файлов с JavaScript руками и добавил удаление BOM из файлов, браузеры
Схема,
Вот ещё тонкость, обязательно нужно добавить новый
AddType */* .cssjsОказалось, что последний «Хром» ни в какую не хочет рендерить CSS с таким типом, пришлось заменить на «text/css,*/*» на «text/css», предыдущий вариант не работал в IE9.
Надо посмотреть как отнесётся к этому второй FF, а пока, если у вас «Хром» или IE9 и показывает неправильно, обновите, пожалуйста, страницу, чтобы она не из кеша взялась, всё должно нормально отрендериться.
Посмотрим «в поле» насколько это стабильно работает.
Черновики у меня в комментариях
Мне тут хочется пару проектов на PHP доделать, а я
В том числе сделал то, что давно просили — черновики в комментариях. Работает только в современных браузерах — IE 8.0+, FF 3.5+, Safari 4.0+, Chrome 4.0+, Opera 10.5+, а так же iOS 2.0+ и Андроид 2.0+. Только такой список, так как сохранение происходит в localStorage.
localStorage — это клиентская база данных «ключ и значение», которые есть сейчас уже во всех распространённых браузерах. Преимущество хранения черновиков именно в ней — черновик сохраняется вне зависимости от того рухнул сервер или нет, ведь localStorage сохраняет данные на диске клиента.
Кстати, я делал библиотеку эмуляции localStorage для IE 6 и 7 (используется behavior userData), но у себя использовать не буду, ни к чему поощрять некрофилию.
Работает всё следующим образом (детально можно посмотреть в исходниках любой страницы с комментариями): когда пользователь делает паузу в наборе комментария, мой JS вызывает localStorage.setItem, где ключём является URL страницы плюс имя поля (у меня их три — имя пользователя, сам комментарий и кому этот комментарий был адресова).
После отправки комментария в теле полученной страницы приходят вызовы localStorage.removeItem, которые удаляют сохранённые значения.
Наконец, если страница была загружена не в результате отправки комментария, вызываются localStorage.getItem, чтобы получить сохранённые значения и подставить их в соответствующие поля.
Код, с использованием JQuery, получился вот такой:
// globalStorage — это для FF 2.0, но я не уверен, что это правда работает
var storage = window.localStorage || window.globalStorage && window.globalStorage[location.hostname] || null;
if (storage) {
var savedraft = function s() {
if (s.hnd) {
clearTimeout(s.hnd);
}
var o = $(this);
s.hnd = setTimeout(function() {
try { // если не хватит места, будет исключение
var prefix = location.pathname + '#';
storage.setItem(prefix + o.attr('name'), o.val());
storage.setItem(prefix + '?', +new Date()); // сохраняю время, возможно в будущем буду подчищать старые черновики
} catch (o) {}
}, 500);
}
// каждый элемент, который надо сохранять, имеет класс draft
$.each($('.draft'), function(_, value) {
var o = $(value),
key = location.pathname + '#' + o.attr('name'),
v = storage.getItem(key);
if (savedsuccessfully) { // внешний флаг, что черновик больше не нужен — всё сохранено на сервере
storage.removeItem(key);
} else if (v !== null) {
o.val(v);
}
o.keypress(savedraft).change(savedraft);
})
}localStorage не резиновое, у разных браузеров значения различаются, но в целом, там около 5МБ на домен, да и диск пользователя забивать жалко. Поэтому я сохраняю время записи, возможно
Tanks JS1K
Кстати о JavaScript. Может быть
Не так давно мне
Мне очень хотелось сократить названия методов канваса, очень уж они длинные — putImageData, createImageData и так далее. Для игры, занимающей (сейчас) 713 байт это расточительно. На эту тему в голову идея и пришла.
Идея заключается в том, чтобы пройтись по списку методов и присвоить объекту их же, но под другими, короткими именами. Самый простой способ, до которого я додумался — это взять пятую букву названия метода (правда, кажется a[i] для строк не везде будет работать, вариант с charAt занимает на 7 байт больше).
Получился такой код:
for(i in a)a[i[V=4]]=a[i]Переменная «V» со значением «четыре» используется дальше как счётчик оставшихся жизней, в переменной «a» ссылка на объект контекста канваса. В итоге, если мне надо вызывать метод «a.putImageData», я могу вызвать его как «a.m», вместо «a.createImageData» можно вызывать «a.t» и так далее (отчёт с нуля начинается).
Чем больше таких вызовов, тем больше экономия. Единственное, свойства (например «fillStyle») так сократить не получится.
DOOM на JavaScript
Ребята из Mozilla портировали DOOM на JavaScript. Скомпилировав DOOM
Запускается не во всех браузерах (последний «Хром» просто завис), но в «Опере» и «Файрфоксе» у меня запустилось. Для запуска необходима поддержка Canvas. На скриншоте видно, что FPS приличный, на моём «Маке» в FireFox4 держится на уровне 20—35.

CSS и JS в одном файле
Раз уж я затронул тему снижения числа коннектов к серверу при загрузке сайта, то было бы уместо вспомнить незаслуженно забытую технику — объединение JS и CSS в одном файле.
Насколько я знаю, первым эту технику исследовал некий ShivaP в статье «Combine CSS with JS and make it into a single download!» ещё четыре года назад.
Идея такова: в файле специальными ухищрениями помещается и код JavaScript, и код CSS, файл подключается два раза — один раз через тег SCRIPT, второй раз через тег LINK (как CSS), браузер грузит файл один раз, а второй раз берёт его уже из кеша.
Автор проверил свою идею на двух браузерах — IE и FireFox, на том и остановился.
Я проверил эту идею на гораздо бо́льшем числе браузеров (обнаружил проблемы на некоторых «Хромах» и исправил), а так же немного её видоизменил, разрешив использовать многострочный CSS без особых неудобств. Последнее нужно персонально мне, так как у меня нет возможности прогонять свой CSS через оптимизаторы и вытягивать таким образом код CSS в одну строку, а я хочу использовать эту технику у себя на сайте.
У техники есть ограничение, которое не портит много крови, но о нём необходимо знать — в тексте CSS и JS не должна встречаться конструкция «*/». Если в CSS достаточно просто выкусить все комментарии («*/» там, можно считать, нигде больше встретиться не может), то в JS за этим придётся следить, так как такая комбинация вполне может встретиться, в частности, в регулярных выражениях. Например: var regexp = /smth*/g;
В таких случаях звёздочку необходимо прятать (например, заменять её в регулярных выражениях на \x2A или разбивать строку: «smth*» + «/»).
Как ShivaP, первый исследователь этой техники, решил проблему размещения JS и CSS в одном файле можно почитать в оринальной статье, я же поступил несколько иначе, но сходным образом. Конструкции размещаются так, чтобы JS оказывался в комментариях CSS и наоборот. Я похожими вещами уже занимался неоднократно.
Мой код выглядит следующим образом (я поставил рядом две картинки, чтобы можно было сравнить как один и тот же код воспринимается интерпретатором JavaScript и парсером CSS):

Как видите, принцип довольно несложен.
Теперь несколько слов о том как это всё подключается:
<link type="text/css" rel="stylesheet" href="core.jscss" />
<script type="text/javascript" src="core.jscss#2"></script>Обратите внимание на «#2» после core.jscss, этот хак позволяет обойти странное поведение некоторых версий «Хрома» — без него браузер не делал попыток подгрузить файл ещё раз и JavaScript не выполнялся.
Немаловажно так же, что заголовок
У себя на сайте я буду использовать скрипт наподобие нижеприведённого, чтобы разом разрешить все проблемы и выставить правильные заголовки. Формат вызова простой: скрипту через запятую перечисляются файлы, которые нужно объединить и подключить (файлы берутся из папки «media», см. переменную «$dir»): /util/js+css.php?core.js,core.css,another.css,another.jsЧитать дальше
<?
ob_start();
$dir = 'media/';
$expires = 10 * 365 * 24 * 60 * 60; // 10 years
$files = explode(',', str_replace('..', '', $_SERVER['QUERY_STRING']));
$css = $js = array();
foreach ($files as $file) {
switch (pathinfo($file, PATHINFO_EXTENSION)) {
case 'css':
$css[] = $dir . $file;
break;
case 'js':
$js[] = $dir . $file;
break;
}
}
$css = @array_map('file_get_contents', array_unique($css));
$js = @array_map('file_get_contents', array_unique($js));
$css = implode($css);
$js = implode($js, ';');
// removes /* comments */ from css
$css = preg_replace('!/\*.*?\*/!s', '', $css);
// removes only first /* comment */ from js
$js = preg_replace('!^/\*.*?\*/!s', '', $js);
echo <<<JSCSS
// /*
$js
"*/{"/*"}
$css
*/
JSCSS;
$len = ob_get_length();
$txt = ob_get_clean();
$etag = '"' . sha1($etag) . '"';
header("Content-type: */*; charset=utf-8");
header("Expires: " . gmdate('D, d M Y H:i:s \G\M\T', $_SERVER['REQUEST_TIME'] + $expires));
header("Cache-control: max-age=$expires, public");
header("Content-length: " . $len);
header('ETag: ' . $etag);
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
list ($hash) = explode(';', $_SERVER['HTTP_IF_NONE_MATCH']);
if ($hash == $etag) {
header('HTTP/1.1 304 Not modified, thanx for yr question');
exit;
}
}
echo $txt;