Подтвердить что ты не робот

Обновление DOM при длительной работе

У меня есть кнопка, которая запускает долго работающую функцию при нажатии. Теперь, когда функция запущена, я хочу изменить текст кнопки, но у меня возникают проблемы в некоторых браузерах, таких как Firefox, IE.

HTML:

<button id="mybutt" class="buttonEnabled" onclick="longrunningfunction();"><span id="myspan">do some work</span></button>

JavaScript:

function longrunningfunction() {
    document.getElementById("myspan").innerHTML = "doing some work";
    document.getElementById("mybutt").disabled = true;
    document.getElementById("mybutt").className = "buttonDisabled";

    //long running task here

    document.getElementById("myspan").innerHTML = "done";
}

Теперь с Firefox и IE возникают проблемы (в chrome все работает нормально)

Поэтому я подумал о том, чтобы поместить его в установленное время:

function longrunningfunction() {
    document.getElementById("myspan").innerHTML = "doing some work";
    document.getElementById("mybutt").disabled = true;
    document.getElementById("mybutt").className = "buttonDisabled";

    setTimeout(function() {
        //long running task here
        document.getElementById("myspan").innerHTML = "done";
    }, 0);
}

но это не работает и для Firefox! кнопка отключается, меняет цвет (из-за применения нового CSS), но текст не меняется.

Я должен установить время 50 мс, а не просто 0 мс, чтобы заставить его работать (измените текст кнопки). Теперь я нахожу это глупым по крайней мере. Я могу понять, будет ли это работать с задержкой 0 мс, но что произойдет в более медленном компьютере? может быть, Firefox потребуется 100 мс в установленное время? это звучит довольно глупо. Я пробовал много раз, 1 мс, 10 мс, 20 мс... нет, это не обновит. только с 50 мс.

Поэтому я последовал совету в этой теме:

Принудительное обновление DOM в Internet Explorer после манипуляций с javascript dom

поэтому я попытался:

function longrunningfunction() {
    document.getElementById("myspan").innerHTML = "doing some work";
    var a = document.getElementById("mybutt").offsetTop; //force refresh

    //long running task here

    document.getElementById("myspan").innerHTML = "done";
}

но это не работает (FIREFOX 21). Тогда я попробовал:

function longrunningfunction() {
    document.getElementById("myspan").innerHTML = "doing some work";
    document.getElementById("mybutt").disabled = true;
    document.getElementById("mybutt").className = "buttonDisabled";
    var a = document.getElementById("mybutt").offsetTop; //force refresh
    var b = document.getElementById("myspan").offsetTop; //force refresh
    var c = document.getElementById("mybutt").clientHeight; //force refresh
    var d = document.getElementById("myspan").clientHeight; //force refresh

    setTimeout(function() {
        //long running task here
        document.getElementById("myspan").innerHTML = "done";
    }, 0);
}

Я даже попробовал clientHeight вместо offsetTop, но ничего. DOM не обновляется.

Может ли кто-нибудь предложить надежное решение, желательно не взломанное?

заранее спасибо!

как предложено здесь, я также пытался

$('#parentOfElementToBeRedrawn').hide().show();

безрезультатно

Принудительно перерисовывать/обновлять DOM в Chrome/Mac

TL; DR:

ищем НАДЕЖНЫЙ кросс-браузерный метод для принудительного обновления DOM без использования setTimeout (предпочтительное решение из-за различных временных интервалов, необходимых в зависимости от типа долго выполняющегося кода, браузера, скорости компьютера и setTimeout, от 50 до 100 мс в зависимости от по ситуации)

jsfiddle: http://jsfiddle.net/WsmUh/5/

4b9b3361

Ответ 1

Решено !! Нет setTimeout() !!!

Протестировано в Chrome 27.0.1453, Firefox 21.0, Internet 9.0.8112

$("#btn").on("mousedown",function(){
$('#btn').html('working');}).on('mouseup', longFunc);

function longFunc(){
  //Do your long running work here...
   for (i = 1; i<1003332300; i++) {}
  //And on finish....  
   $('#btn').html('done');
}

ДЕМО ЗДЕСЬ!

Ответ 2

Веб-страницы обновляются на основе однопоточного контроллера, и половина браузеров не обновляет DOM или стиль до тех пор, пока не прекратится выполнение JS, предоставляя вычислительный контроль браузеру. Это означает, что если вы установите некоторое значение element.style.[...] = ..., оно не будет активировано до тех пор, пока ваш код не завершит работу (либо полностью, либо потому, что браузер видит, что вы делаете что-то, что позволяет ему перехватывать обработку в течение нескольких мс).

У вас есть две проблемы: 1) в вашей кнопке есть <span>. Удалите это, просто установите .innerHTML на самой кнопке. Но это не настоящая проблема, конечно. 2) вы выполняете очень длинные операции, и вам нужно очень тщательно продумать, почему, и после ответа на вопрос почему, как:

Если вы выполняете длинную вычислительную работу, разделите ее на обратные вызовы тайм-аута (или, в 2019 году, await/async - см. примечание в конце этого ансера). Ваши примеры не показывают, какова ваша "длинная работа" на самом деле (спин-цикл не учитывается), но у вас есть несколько вариантов в зависимости от того, какие браузеры вы используете, с одной GIANT-закладкой: не выполняйте длинные задания в JavaScript, точка. По спецификации JavaScript является однопоточным окружением, поэтому любая операция, которую вы хотите выполнить, должна выполняться за миллисекунды. Если это невозможно, вы буквально делаете что-то не так.

Если вам нужно вычислить сложные вещи, перенесите их на сервер с помощью операции AJAX (универсально для всех браузеров, часто давая вам a) более быструю обработку для этой операции и b) хорошие 30 секунд времени, которые вы можете асинхронно не ожидать результат должен быть возвращен) или использовать фоновый поток веб-работника (очень НЕ универсальный).

Если ваши вычисления занимают много времени, но это не абсурдно, рефакторинг вашего кода, чтобы вы выполняли части с задержкой времени ожидания:

function doLongCalculation(callbackFunction) {
  var partialResult = {};
  // part of the work, filling partialResult
  setTimeout(function(){ doSecondBit(partialResult, callbackFunction); }, 10);
}

function doSecondBit(partialResult, callbackFunction) {
  // more 'part of the work', filling partialResult
  setTimeout(function(){ finishUp(partialResult, callbackFunction); }, 10);
}

function finishUp(partialResult, callbackFunction) {
  var result;
  // do last bits, forming final result
  callbackFunction(result);
}

Длинный расчет почти всегда можно реорганизовать в несколько этапов, либо потому, что вы выполняете несколько шагов, либо потому, что вы выполняете одно и то же вычисление миллион раз и можете разбить его на партии. Если вы (преувеличивали) это:

var resuls = [];
for(var i=0; i<1000000; i++) {
  // computation is performed here
  if(...) results.push(...);
}

тогда вы можете просто обрезать это до функции, ослабленной по таймауту, с помощью обратного вызова

function runBatch(start, end, terminal, results, callback) {
  var i;
  for(var i=start; i<end; i++) {
    // computation is performed here
    if(...) results.push(...);      } 
  if(i>=terminal) {
    callback(results);
  } else {
    var inc = end-start;
    setTimeout(function() {
      runBatch(start+inc, end+inc, terminal, results, callback);
    },10);
  }
}

function dealWithResults(results) {
  ...
}

function doLongComputation() {
  runBatch(0,1000,1000000,[],dealWithResults);
}

TL; DR: не выполняйте длинных вычислений, но если вам нужно, заставьте сервер выполнять всю работу за вас и просто используйте асинхронный вызов AJAX. Сервер может выполнять работу быстрее, и ваша страница не будет блокироваться.

Примеры JS о том, как работать с длинными вычислениями в JS на клиенте, приведены здесь только для того, чтобы объяснить, как вы можете решить эту проблему, если у вас нет возможности выполнять вызовы AJAX, что в 99,99% случаев не будет случай.

редактировать

также обратите внимание, что описание вашей награды является классическим случаем проблемы XY

2019 редактировать

В современном JS концепция await/async значительно улучшает обратные вызовы тайм-аута, поэтому используйте их вместо этого. Любой await сообщает браузеру, что он может безопасно запускать запланированные обновления, поэтому вы пишете свой код "структурированным, как если бы он был синхронным" способом, но вы помечаете свои функции как async, а затем вы await выводите их. их всякий раз, когда вы звоните им:

async doLongCalculation() {
  let firstbit = await doFirstBit();
  let secondbit = await doSecondBit(firstbit);
  let result = await finishUp(secondbit);
  return result;
}

async doFirstBit() {
  //...
}

async doSecondBit...

...

Ответ 3

Как описано в разделе < Script с чересчур длинными и тяжелыми работами События и сроки в глубину (интересное чтение, кстати):

[...] разделить задание на части, которые планируются друг за другом. [...] Затем есть "свободное время", чтобы браузер отвечал между частями. Он может отображать и реагировать на другие события. И посетитель, и браузер счастливы.

Я уверен, что существует много раз, когда задача не может быть разделена на более мелкие задачи или фрагменты. Но я уверен, что будет много других случаев, когда это возможно!: -)

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

function doHeavyWork(start) {
    var total = 1000000000;
    var fragment = 1000000;
    var end = start + fragment;

    // Do heavy work
    for (var i = start; i < end; i++) {
        //
    }

Как только работа будет завершена, функция должна определить, должна ли выполняться следующая деталь, или если выполнение завершено:

    if (end == total) {
        // If we reached the end, stop and change status
        document.getElementById("btn").innerHTML = "done!";
    } else {
        // Otherwise, process next fragment
        setTimeout(function() {
            doHeavyWork(end);
        }, 0);
    }            
}

Ваша основная функция dowork() будет выглядеть следующим образом:

function dowork() {
    // Set "working" status
    document.getElementById("btn").innerHTML = "working";

    // Start heavy process
    doHeavyWork(0);
}

Полный рабочий код http://jsfiddle.net/WsmUh/19/ (кажется, что он нежно относится к Firefox).

Ответ 4

Если вы не хотите использовать setTimeout, тогда вы останетесь с WebWorker - для этого потребуется браузер с поддержкой HTML5.

Это один из способов их использования -

Определите свой HTML и встроенный script (вам не нужно использовать inline script, вы также можете указать URL-адрес существующего отдельного JS файла):

<input id="start" type="button" value="Start" />
<div id="status">Preparing worker...</div>

<script type="javascript/worker">
    postMessage('Worker is ready...');

    onmessage = function(e) {
        if (e.data === 'start') {
            //simulate heavy work..
            var max = 500000000;
            for (var i = 0; i < max; i++) {
                if ((i % 100000) === 0) postMessage('Progress: ' + (i / max * 100).toFixed(0) + '%');
            }
            postMessage('Done!');
        }
    };
</script>

Для встроенного script мы отмечаем его с типом javascript/worker.

В обычном файле Javascript -

Функция, которая преобразует встроенный script в Blob-url, который может быть передан WebWorker. Обратите внимание, что это может означать работу в IE, и вам придется использовать обычный файл:

function getInlineJS() {
    var js = document.querySelector('[type="javascript/worker"]').textContent;
    var blob = new Blob([js], {
        "type": "text\/plain"
    });
    return URL.createObjectURL(blob);
}

Работник установки:

var ww = new Worker(getInlineJS());

Получить сообщения (или команды) из WebWorker:

ww.onmessage = function (e) {
    var msg = e.data;

    document.getElementById('status').innerHTML = msg;

    if (msg === 'Done!') {
        alert('Next');    
    }
};

Мы запускаем нажатие кнопки в этой демонстрации:

document.getElementById('start').addEventListener('click', start, false);

function start() {
    ww.postMessage('start');
}

Рабочий пример здесь:
http://jsfiddle.net/AbdiasSoftware/Ls4XJ/

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

Если вы не хотите или не можете использовать WebWorker, вы должны использовать setTimeout.

Это потому, что это единственный способ (помимо setInterval), который позволяет вам помещать в очередь событие. Как вы заметили, вам нужно будет дать ему несколько миллисекунд, так как это даст игровому движку "номер для записи" так сказать. Поскольку JS является однопоточным, вы не можете ставить в очередь события другими способами (requestAnimationFrame не будет работать хорошо в этом контексте).

Надеюсь, что это поможет.

Ответ 5

Обновление. В долгосрочной перспективе я не думаю, что вы можете быть уверены, что избегаете агрессивного избегания Firefox обновлений DOM без использования тайм-аута. Если вы хотите принудительно обновить/изменить DOM, есть доступные трюки, такие как настройка смещения элементов или выполнение hide(), затем show() и т.д., Но нет ничего очень доступного, и через некоторое время, когда эти трюки злоупотребляют и замедляют работу пользователей, затем браузеры обновляются, чтобы игнорировать эти трюки. См. Эту статью и связанные статьи рядом с ней для некоторых примеров: Применить DOM перерисовать/обновить Chrome/Mac

Другие ответы выглядят так, как будто у них есть основные элементы, но я подумал, что стоит упомянуть, что моя практика заключается в том, чтобы обернуть все интерактивные функции изменения DOM в функцию "отправки", которая обрабатывает необходимые паузы, необходимые для получения вокруг того факта, что Firefox чрезвычайно агрессивен, избегая обновлений DOM, чтобы хорошо оценивать результаты тестов (и реагировать на пользователей во время просмотра Интернета).

Я посмотрел на ваш JSFiddle и настроил функцию отправки, на которую полагаются многие мои программы. Я думаю, что это самоочевидно, и вы можете просто вставить его в свой существующий скрипт JS, чтобы увидеть, как он работает:

$("#btn").on("click", function() { dispatch(this, dowork, 'working', 'done!'); });

function dispatch(me, work, working, done) {
    /* work function, working message HTML string, done message HTML string */
    /* only designed for a <button></button> element */
    var pause = 50, old;
    if (!me || me.tagName.toLowerCase() != 'button' || me.innerHTML == working) return;
    old = me.innerHTML;
    me.innerHTML = working;
    setTimeout(function() {
        work();
        me.innerHTML = done;
        setTimeout(function() { me.innerHTML = old; }, 1500);
    }, pause);
}

function dowork() {
        for (var i = 1; i<1000000000; i++) {
            //
        }

}

Примечание. Функция отправки также блокирует одновременные вызовы, так как это может серьезно запутать пользователей, если обновления состояния из нескольких кликов происходят вместе.

Ответ 6

С 2019 года используется двойной requesAnimationFrame для пропуска кадра вместо создания условия гонки с использованием setTimeout.

function doRun() {
    document.getElementById('app').innerHTML = 'Processing JS...';
    requestAnimationFrame(() =>
    requestAnimationFrame(function(){
         //blocks render
         confirm('Heavy load done') 
         document.getElementById('app').innerHTML = 'Processing JS... done';
    }))
}
doRun()
<div id="app"></div>

Ответ 7

Попробуйте это

function longRunningTask(){
    // Do the task here
    document.getElementById("mybutt").value = "done";
}

function longrunningfunction() {
    document.getElementById("mybutt").value = "doing some work";

    setTimeout(function() {
        longRunningTask();
    }, 1);    
}

Ответ 8

Некоторые браузеры не обрабатывают атрибут onclick html. Лучше использовать это событие на объекте js. Вот так:

<button id="mybutt" class="buttonEnabled">
    <span id="myspan">do some work</span>
</button>

<script type="text/javascript">

window.onload = function(){ 

    butt = document.getElementById("mybutt");
    span = document.getElementById("myspan");   

    butt.onclick = function () {
        span.innerHTML = "doing some work";
        butt.disabled = true;
        butt.className = "buttonDisabled";

        //long running task here

        span.innerHTML = "done";
    };

};

</script>

Я сделал скрипку с рабочим примером http://jsfiddle.net/BZWbH/2/

Ответ 9

Вы пытались добавить слушателя к "onmousedown", чтобы изменить текст кнопки и событие click для функции longrunning.

Ответ 10

Слегка изменил ваш код в jsfiddle и:

$("#btn").on("click", dowork);

function dowork() {
    document.getElementById("btn").innerHTML = "working";
    setTimeout(function() {
        for (var i = 1; i<1000000000; i++) {
            //
        }
        document.getElementById("btn").innerHTML = "done!";
    }, 100);
}

Тайм-аут, установленный для более разумного значения 100ms, помогло. Попробуй. Попробуйте отрегулировать задержку, чтобы найти наилучшее значение.

Ответ 11

Буфер DOM также существует в браузере по умолчанию на Android, длинный запуск javascript только сброс буфера DOM один раз, используйте setTimeout (..., 50), чтобы решить эту проблему.

Ответ 12

Подделка запроса ajax

function longrunningfunction() {
document.getElementById("myspan").innerHTML = "doing some work";
document.getElementById("mybutt").disabled = true;
document.getElementById("mybutt").className = "buttonDisabled";
$.ajax({
    url: "/",
    complete: function () {
        //long running task here
        document.getElementById("myspan").innerHTML = "done";
    }
});}