#javascript #jquery
#javascript #jquery
Вопрос:
У меня есть небольшая анимация в jQuery, которая должна быстро показывать слова и не слова в течение определенного промежутка времени в миллисекундах при нажатии.
var arr = ['GOAT', 'BEAVER', 'TIGER', 'ELEPHANT', 'FOX', 'BEAR', 'BEE', 'CAT', 'DOG', 'MOUSE', 'LION', 'FISH', 'SHRIMP', 'HEN', 'GOOSE', 'COW', 'CROCODILE', 'DEER', 'MOOSE', 'HIPPOPOTAMUS', 'WOLF', 'RACCOON', 'HARE', 'OTTER', 'DOLPHIN', 'WHALE', 'CHICK'];
var narr = ['REQXARDE', 'YORSTDAJ', 'AWQPQQQR', 'FJSJAJAA', 'QQWPEEET', 'ALALOIYE', 'BOUILAARW', 'NVOSAQEWW', 'WARTYDIOS', 'SUPARWLISS', 'WQQQAPXXX', 'OOOSAAOEA', 'SSIUDHFWW', 'AWWWEIPP', 'AAZXDOUP', 'SURPAAARJ', 'AALDJWWA', 'WEEJSYSJ', 'REQXARDE', 'YORSTDAJ', 'AWQPQQQR', 'FJSJAJAA', 'QQWPEEET', 'ALALOIYE', 'BOUILAARW'];
var key = ['jQuery', 'Javascript', 'css3', 'stackoverflow', 'html5', 'animation'];
/* This selects a random value from each array */
function narr_val() { return narr[Math.floor(Math.random() * narr.length)]; }
function arr_val() { return arr[Math.floor(Math.random() * arr.length)]; }
function key_val() { return key[Math.floor(Math.random() * key.length)]; }
$( "#foo" ).bind("click tap", function(){
$("#foo").unbind( "click" );
//Block 0
$('#foo').fadeIn(1).delay(500).html('Attention!').fadeOut(1,function(){
$('#foo').fadeIn(1).delay(500).html('In 3...').fadeOut(1,function(){
$('#foo').fadeIn(1).delay(500).html('2...').fadeOut(1,function(){
$('#foo').fadeIn(1).delay(500).html('1...').fadeOut(1,function(){
$('#foo').fadeIn(1).delay(500).html('Go!').fadeOut(1,function(){
//Block 1
$('#foo').fadeIn(1).delay(175).html(narr_val()).fadeOut(1,function(){
$('#foo').fadeIn(1).delay(40).html(key_val()).fadeOut(1,function(){
$('#foo').fadeIn(1).delay(175).html(narr_val()).fadeOut(1,function(){
$('#foo').fadeIn(1).delay(320).html(arr_val()).fadeOut(1,function(){
$('#foo').fadeIn(1).delay(40).html(narr_val()).fadeOut(1,function(){
//Block 2
$('#foo').fadeIn(1).delay(175).html(narr_val()).fadeOut(1,function(){
$('#foo').fadeIn(1).delay(40).html(key_val()).fadeOut(1,function(){
$('#foo').fadeIn(1).delay(175).html(narr_val()).fadeOut(1,function(){
$('#foo').fadeIn(1).delay(320).html(arr_val()).fadeOut(1,function(){
$('#foo').fadeIn(1).delay(40).html(narr_val()).fadeOut(1,function(){
и т.д. … есть версия с 27 блоками, а другая с 40. Рабочий пример здесь
}); }); }); }); }); }); }); }); }); }); }); }); }); }); }); });
Мои вопросы:
1) Любой другой способ сделать это вместо использования встроенных функций обратного вызова?
2) Любой способ написать этот синтаксис с помощью цикла for или чего-то, что позволило бы избежать записи 27 блоков функций обратного вызова?
Комментарии:
1. Да, вероятно, есть. Вы смотрели на систему очередей? Я не думаю, что вам нужны какие-либо из этих обратных вызовов.
2. Не публикую это как ответ, поскольку я уверен, что есть лучший способ, но здесь это может вам помочь: jsfiddle.net/hnkhZ/1
3. Downvoter, потрудитесь объяснить?
Ответ №1:
Другим подходом было бы использовать встроенную возможность постановки в очередь путем создания вызова .html, который можно поставить в очередь (см. jQuery .queue для получения дополнительной информации):
function queuedHtml(html) {
return function () {
$(this).html(html);
$(this).dequeue();
};
}
Это можно настроить для использования именованной очереди, а не очереди по умолчанию.
Как только вы это сделаете, вы можете настроить свой код примерно так:
$("#foo").bind("click tap", function () {
var $foo = $("#foo");
$foo.unbind("click");
$foo.fadeIn(1).delay(500).queue(queuedHtml('Attention!')).fadeOut(1)
.fadeIn(1).delay(500).queue(queuedHtml('In 3...')).fadeOut(1)
.fadeIn(1).delay(500).queue(queuedHtml('2...')).fadeOut(1)
.fadeIn(1).delay(500).queue(queuedHtml('1...')).fadeOut(1)
.fadeIn(1).delay(500).queue(queuedHtml('Go!')).fadeOut(1);
for (var i = 0; i < 27; i) {
$foo.fadeIn(1).delay(175).queue(queuedHtml(narr_val())).fadeOut(1)
.fadeIn(1).delay(40).queue(queuedHtml(key_val())).fadeOut(1)
.fadeIn(1).delay(175).queue(queuedHtml(narr_val())).fadeOut(1)
.fadeIn(1).delay(320).queue(queuedHtml(arr_val())).fadeOut(1)
.fadeIn(1).delay(40).queue(queuedHtml(narr_val())).fadeOut(1);
}
$foo.fadeIn(1).queue(queuedHtml('Terminated'));
});
Вы можете еще немного сократить код, проведя рефакторинг некоторой повторяющейся логики — например:
function addToQueue($el, html, delay) {
$el.fadeIn(1).delay(delay).queue(queuedHtml(html)).fadeOut(1);
}
$("#foo").bind("click tap", function () {
var $foo = $("#foo");
$foo.unbind("click");
addToQueue($foo, 'Attention!', 500);
addToQueue($foo, 'In 3...', 500);
addToQueue($foo, '2...', 500);
addToQueue($foo, '1...', 500);
addToQueue($foo, 'Go!', 500);
for (var i = 0; i < 27; i) {
addToQueue($foo, narr_val(), 175);
addToQueue($foo, key_val(), 40);
addToQueue($foo, narr_val(), 175);
addToQueue($foo, arr_val(), 320);
addToQueue($foo, narr_val(), 40);
}
$foo.fadeIn(1).queue(queuedHtml('Terminated'));
});
или уменьшите его еще больше, проведя немного больший рефакторинг:
$("#foo").bind("click tap", function () {
var $foo = $("#foo");
$foo.unbind("click");
$.each(['Attention', 'In 3...', '2...', '1...', 'Go!'], function(idx, val) {
addToQueue($foo, val, 500);
});
var delays = [175, 40, 175, 320, 40];
for (var i = 0; i < 27; i) {
$.each([narr_val(), key_val(), narr_val(), arr_val(), narr_val()], function(idx, val) {
addToQueue($foo, val, delays[idx]);
});
}
$foo.fadeIn(1).queue(queuedHtml('Terminated'));
});
Комментарии:
1. @dc5: Это потрясающее сокращение длины кода! Также спасибо за пошаговую демонстрацию. Однако остается одна проблема, и я не понимаю почему, заключается в том, что сроки, похоже, не соблюдаются. Самая длинная задержка, 320 мс, возникает для слова animal. Это означает, что это слово должно восприниматься на экране. Скорее, более длительная задержка возникает для не-слова. Похоже, что задержка не применяется к правильному значению массива.
2. В настоящее время логика добавляет задержку перед установкой html, поэтому задержка 320 происходит непосредственно перед установкой animal word. Слово animal отображается в течение 40 мс (последнее значение задержки). Если это не то, что вы хотите, вы можете переключить вызов в очередь html с задержкой:
$el.fadeIn(1).queue(queuedHtml(html)).delay(delay).fadeOut(1);
3. хорошо, понял, спасибо! Просто измените переменную delays на: var delays = [40, 175, 40, 175, 320]; делает его в точности похожим на то, что было у меня.
4. Если подумать об этом подробнее, вы можете получить лучший эффект от этой цепочки: fadeOut -> update html -> fadeIn -> delay с более длинными значениями fadeIn / Out, подобными этому:
$el.fadeOut(100).queue(queuedHtml(html)).fadeIn(100).delay(delay);
В нынешнем виде значения fadeIn / Out на 1 мс, вероятно, не нужны — вы также можете удалить их, если хотите резкого перехода.5. @Fred — Я вижу, где я ошибся — в вашей версии обратного
.html
вызова вызов произошел сразу после вызова.delay
, поскольку эта версия не находится в очереди. Я не учел это при создании версии в очереди.
Ответ №2:
При работе с асинхронными материалами вам нужно использовать рекурсивные функции вместо циклов for для выполнения итерации. Без использования каких-либо библиотек одним из возможных подходов было бы что-то вроде
var aims = [
{delay:500, html:'Attention!'},
{delay:500, html:'In 3...'},
...
];
function loop(i){
if(i >= anims.length){
//ANIMATION DONE
}else{
var a = anims[i];
$('#foo').fadeIn(1).delay(a.delay).html(a.html).fadeOut(1,function(){
loop(i 1);
});
}
}
loop(0);
Самый прямой способ избежать вложенности обратного вызова в Javascript — использовать именованные обратные вызовы вместо анонимных. Например:
function f1(){ $('#foo').fadeIn(1).delay(500).html('Attention!').fadeOut(1, f2); }
function f2(){ $('#foo').fadeIn(1).delay(500).html('In 3...').fadeOut(1,f2); }
function f3(){ ... }
Конечно, наличие набора жестко закодированных имен f1, f2, f3, fn … подвержено ошибкам и вообще не поддерживается. Что вы можете сделать, так это предоставить каждой пошаговой функции ее продолжение в качестве параметра
var steps = [
function (next){ $('#foo').fadeIn(1).delay(500).html('Attention!').fadeOut(1, next); },
function (next){ $('#foo').fadeIn(1).delay(500).html('In 3...').fadeOut(1, next); },
...
];
А затем используйте функцию цепочки для последовательного вызова каждого шага, передавая следующий шаг в качестве параметра:
function chain(steps, onDone){
function loop(i){
if(i >= steps.length){
onDone();
)else{
steps[i](function(){
loop(i 1);
});
}
}
loop(0);
}
chain(steps, function(){ console.log("all done") });
Существует множество библиотек (либо на основе обратного вызова, либо на основе обещаний), которые предлагают эти функции цепочки, если вы не хотите реализовывать их самостоятельно.