DOM абнаўлення на доўгай беглым функцыі

У мяне ёсць кнопка, якая запускае доўгі працуе функцыя, калі яна націснутая. Зараз, у той час як функцыя працуе, я хачу, каб змяніць тэкст кнопкі, але ў мяне ўзнікаюць праблемы ў некаторых браўзэрах, такіх як Firefox, IE.

HTML:

<button id="mybutt" class="buttonEnabled" onclick="longrunningfunction();">do some work</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 (ў хром працуе нармальна)

Так я думаў, каб пакласці яго ў SetTimeout:

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);
}

але гэта не працуе, альбо для светлячок! кнопка атрымлівае інвалідаў, мяняе колер (за кошт прымянення новага CSS), але тэкст не мяняецца.

Я павінен ўсталяваць час 50мс, а не толькі 0ms для таго, каб зрабіць яго працу (змяніць тэкст кнопкі). Цяпер я лічу, гэта па-дурному, па меншай меры. Я магу зразумець, калі гэта будзе працаваць толькі з 0ms спазненнем, але тое, што будзе адбывацца ў больш павольным кампутары? можа быць, светлячок трэба будзе 100мс там у SetTimeout? гэта гучыць даволі недарэчна. Я спрабаваў шмат разоў, 1мс, 10мс, 20мс ... не, гэта не будзе абнаўляць яго. толькі з 50мс.

Так што я рушыў услед радзе ў гэтай тэме:

Прымус DOM абнаўлення ў Internet Explorer пасля JavaScript йота маніпуляцыя

так што я спрабаваў:

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-ня асвяжыцца.

Можа хтосьці прапанаваць надзейнае рашэнне пераважна без Hacky?

загадзя дзякую!

як прапанавана тут я таксама спрабаваў

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

але безвынікова

Force DOM перамалёваць/абнавіць на Chrome/Mac

<�Моцны> TL; DR:

шукае надзейнага метаду крос-браўзэра, каб мець прымусовы DOM абнавіць без выкарыстання SetTimeout (пераважнае рашэнне ў сувязі з рознымі часовымі інтэрваламі, неабходныя ў залежнасці ад тыпу доўгага бегу кода, браўзэр, хуткасць кампутара і SetTimeout патрабуе дзе-небудзь ад 50 да 100 мс у залежнасці па сітуацыі)

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

18
@ Mike'Pomax'Kamermans да я проста абнавіў свой пост з jsfiddle.
дададзена аўтар MIrrorMirror, крыніца
Джош, але ж я не пярэчу, калі jQuery з'яўляецца часткай рашэння
дададзена аўтар MIrrorMirror, крыніца
Вы не хочаце мець цяжкую апрацоўку ў галоўным патоку браўзэра, як яна замярзае старонку. Выкарыстанне Web Workers для выканання гэтай задачы.
дададзена аўтар Šime Vidas, крыніца
Чаму вы не кэшаваць вашыя спасылкі? Вы выконваеце той жа DOM запытвае некалькі разоў.
дададзена аўтар Šime Vidas, крыніца
<�Код> mybutt , ён ён
дададзена аўтар Ronnie, крыніца
Вы можаце ператварыць гэта ў jsfiddle калі ласка?
дададзена аўтар Mike 'Pomax' Kamermans, крыніца
Што тычыцца вашага рэдагавання, гэта азначае jQuery таксама можа быць часткай рашэнні?
дададзена аўтар Josh, крыніца
Праверце гэта: stackoverflow.com/questions/7342084/…
дададзена аўтар Ezequiel Gorbatik, крыніца

11 адказы

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

You have two problems: 1) your button has a in it. Remove that, just set .innerHTML on the button itself. But this isn't the real problem of course. 2) you're running very long operations, and you should think very hard about why, and after answering the why, how:

Я б вы бяжыце доўгую вылічальную працу, скараціць яго ў тайм-аўту зваротнага выкліку. Вашы прыклады не паказваюць, што ваш «доўгі праца» на самай справе (пятля спіна не ў рахунак), але ў вас ёсць некалькі варыянтаў у залежнасці ад браўзэраў, якія вы прымаеце, з адным GIANT booknote: ня запускаць доўгія працоўныя месцы ў JavaScript, перыяд. JavaScript з'яўляецца однопоточна асяроддзем па спецыфікацыі, таму любая аперацыя, вы хочаце зрабіць, павінна быць у стане завяршыць у мілісекундах. Калі ён не можа, вы ў літаральным сэнсе рабіць нешта не так.

Калі вам трэба вылічыць складаныя рэчы, выгрузіць яго на сервер з аперацыяй AJAX (універсальнай ва ўсіх браўзэрах, часта даючы вам) хуткую апрацоўку для гэтай аперацыі і б) добрыя 30 секунд часу, што вы можаце асінхронна ня-чакаць для вынік павінен быць вернуты) або выкарыстоўваць WebWorker фонавы паток (вельмі ня універсальны).

Калі разлік займае шмат часу, але не абсурдна так, рэфактарынг кода, так што вы выконваеце часткі, з тайм-аўт перадышкі:

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=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: don't run long computations, but if you have to, make the server do the work for you and just use an asynchronous AJAX call. The server can do the work faster, and your page won't block.

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

<�Моцны> змяніць

also note that your bounty description is a classic case of The XY problem

13
дададзена
я, верагодна, падзяліць функцыю доўга працуе ў невялікіх settimeouts
дададзена аўтар MIrrorMirror, крыніца
няма ніякай магчымасці, каб зрабіць працу на сэрвэры, так як ён не можа тэхнічна быць зроблена на сэрвэры.
дададзена аўтар MIrrorMirror, крыніца
Дарэчы, ці ведаеце вы глядзець у webworkers, таму што гэта гучыць як свайго роду рэчы, якія павінны быць альбо не быць зроблена наогул у JavaScript або ў WebWorker, якая прызначана для вырашэння гэтага абмежавання JS.
дададзена аўтар David Mulder, крыніца
Гэта можа быць мудрым; калі гэта не можа быць зроблена на сэрвэры, а затым рэзкі яго ў ланцугі зваротнага выкліку з тайм-аўты, па меншай меры, разблакаваць браўзэр.
дададзена аўтар Mike 'Pomax' Kamermans, крыніца

SOLVED IT!! No setTimeout()!!!

Выпрабавана ў Chrome 27.0.1453, Firefox 21,0, Інтэрнэт 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');
}

ДЭМА ТУТ!

5
дададзена
@KyleK дзякуй за ваш адказ, я буду старацца, каб убачыць, калі я магу пазбегнуць праблемных выпадкаў, як паказаў Matt. Я не супраць рашэння SetTimeout само па сабе, я ў парадку з SetTimeout раствор, які змяшчае (..., 0) або SetTimeout (..., 1), тое, што я не хачу SetTimeout (..., 50/100), дзе я павінен быў бы змясціць вялікае значэнне, каб клапаціцца для ўсіх браўзэраў
дададзена аўтар MIrrorMirror, крыніца
Ну ........ на жаль, для ОП, ён хоча нешта без SetTimeout() ....... гэта адзінае рашэнне, я паспрабаваў 34 розных ітэрацый, і гэта той, які прайшоў выпрабаванне. Калі ён не гатовы выкарыстоўваць SetTimeout() або betteryet, выкарыстоўвайце WebWorker..which, безумоўна, як бы я гэта зрабіць ....... гэта для яго. Калі перасоўванне мышы па-за кнопкі з'яўляецца праблемай, я мяркую, што ён робіць кнопку вельмі вялікі. :)
дададзена аўтар KyleK, крыніца
Пстрыкніце мышшу на кнопцы, перацягнуць яго з боку кнопкі. Цяпер адпусціце яго. Woop, ёсць праблема. :(
дададзена аўтар traditional, крыніца
Чартоўску разумнае рашэнне, +1, :-) Ці ёсць дакументацыя, якая падтрымлівае яго? Я маю на ўвазе, SetTimeout (..., 0); трук павінен працаваць (і гэта шырока дакументаваны), але не працуе для @MirrorMirror ў FireFox
дададзена аўтар German Latorre, крыніца
але ўнутрана выкарыстоўвае jQuery SetTimeout ва ўсім -.- вы проста карыстаецеся рамкі замест гэтага, я не бачу, што разумны ў гэтым
дададзена аўтар Sebas, крыніца
Гэта парушае стандартныя паводзіны браўзэра. Націсніце падзеі ня не спрацоўваюць, пакуль пасля MouseDown і MouseUp агню для таго ж элемента. Калі які-небудзь адбываецца, калі мыш знаходзіцца па-за элемента, падзея пстрычкі не спрацоўвае. Праблемныя выпадкі выкарыстання: 1) Націсніце кнопку мышы на элеменце, перамесціце мыш звонку, а затым адпусціце кнопку. Анімацыя працуе на нявызначаны тэрмін, але апрацоўка ніколі не пачынаецца. 2) Націсніце на кнопку мышы па-за элемента, перамясціць мыш ўнутры, затым адпусціць кнопку. Апрацоўка праходзіць без бачных прыкмет таго, што ён пачаў.
дададзена аўтар Matt Coughlin, крыніца

Як апісана ў «Script займае занадта доўгія і цяжкія працы» раздзел Падзеі і часу паглыбленыя (цікавае чытанне, дарэчы):

<�Р> [...] <�моцнага> падзяліць працу на частку , якія атрымліваюць запланаваную адзін за адным. [...] Тады ёсць «вольны час» для браўзэра рэагаваць паміж часткамі. Гэта можа зрабіць і рэагаваць на іншыя падзеі. Як наведвальнік і браўзэр шчаслівыя.

Я ўпэўнены, што ёсць шмат разоў, у якіх задача не можа быць падзелена на больш дробныя задачы, або іх фрагменты. Але я ўпэўнены, што там будзе шмат іншых раз у якіх гэта таксама магчыма! :-)

Некаторы рэфактарынг неабходны ў прыведзеным прыкладзе. Вы можаце <�моцны> стварыць функцыю, каб зрабіць частку працы вы павінны зрабіць. Гэта можа пачацца, як гэта:

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

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

Пасля таго, як праца скончаная, функцыя павінна вызначыць, ці з'яўляецца наступная праца частка павінна быць зроблена, або кал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).

3
дададзена
пляма на гэта лёгка перапрацаваны таксама для большасці працяглых задач.
дададзена аўтар twobob, крыніца

Калі вы не хочаце выкарыстоўваць SetTimeout , то вам застаецца WebWorker - гэта запатрабуе, аднак з падтрымкай HTML5 браўзэраў.

Гэта адзін са спосабаў вы можаце выкарыстоўваць іх -

Вызначыце HTML і ўбудаваны скрыпт (вы не павінны выкарыстоўваць ўбудаваны скрыпт, вы можаце дакладна таксама даць 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>

Для убудаванага сцэнара мы адзначаем яго з тыпам JavaScript/працоўны .

У звычайным файле Javascript -

Функцыя, якая пераўтворыць убудаваны скрыпт у 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');
}

Working example here:
http://jsfiddle.net/AbdiasSoftware/Ls4XJ/

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

Калі вы не хочаце ці не можаце выкарыстоўваць WebWorker вы Папрасіць выкарыстоўваць SetTimeout .

Гэта адбываецца таму, што гэта адзіны шлях (побач з setInterval ), што дазволіць вам у чаргу падзея. Як вы заўважылі, што вам трэба будзе даць яму некалькі мілісекунд, так як гэта дасьць ПІ «пакой для breeth» так на выступ. Як JS з'яўляецца однопоточных вы не можаце стаяць у чарзе падзей і іншыя спосабы ( requestAnimationFrame не будзе добра працаваць у дадзеным кантэксьце).

Спадзяюся, што гэта дапамагае.

2
дададзена

Update: I don't think in the long term that you can be sure of avoiding Firefox's aggressive avoidance of DOM updates without using a timeout. If you want to force a redraw/DOM update, there are tricks available, like adjusting the offset of elements, or doing hide() then show(), etc., but there is nothing very pretty available, and after a while when those tricks get abused and slow down user experience, then browsers get updated to ignore those tricks. See this article and the linked articles beside it for some examples: Force DOM redraw/refresh on Chrome/Mac

Іншыя адказы выглядаюць як яны маюць асноўныя элементы, неабходныя, але я думаў, што гэта варта было б адзначыць, што мая практыка, каб абгарнуць ўсе інтэрактыўныя DOM змяняюць функцый у функцыі «адпраўка», якая апрацоўвае неабходныя паўзы, неабходных, каб абыйсці той факт, што Firefox надзвычай агрэсіўна пазбегнуць абнаўленняў DOM, каб добра набраць на тэстах (і быць спагаднымі для карыстальнікаў падчас працы ў Інтэрнэце).

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

$("#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++) {
            //
        }

}

Заўвага: функцыя дыспетчарская таксама блакуе выклікі ад адбываецца ў той жа самы час, таму што гэта можа сур'ёзна заблытаць карыстальнікаў, калі абнаўлення статусу з некалькіх клікаў адбываюцца разам.

1
дададзена

паспрабуйце гэта

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

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

    setTimeout(function() {
        longRunningTask();
    }, 1);    
}
0
дададзена
Я паспрабаваў тое, што вы прапанавалі. Гэта не спрацавала. праблема сапраўды захоўваецца
дададзена аўтар MIrrorMirror, крыніца
Неа, ня important..the same..doesn't працы
дададзена аўтар MIrrorMirror, крыніца
чаму? гэта праблема?
дададзена аўтар MIrrorMirror, крыніца
ды поўны код кнопкі HTML з'яўляецца: <�кнопка ID = клас "mybutt" = "buttonEnabled" OnClick = "longrunningfunction ();"> працаваць доўга працуе функцыя </button> . таксама абноўлены ў пачатковым пасце
дададзена аўтар MIrrorMirror, крыніца
не. не працуе
дададзена аўтар MIrrorMirror, крыніца
Калі ласка, вы можаце падзяліцца HTML код кнопкі, а?
дададзена аўтар Jay Patel, крыніца
як я не маю доступ да поўнага коду і канкрэтна ў асяроддзі вашай ўстаноўка мае, я не магу даць вам больш прапаноў.
дададзена аўтар Jay Patel, крыніца
Як вы абнаўляеце значэнне myspan замест MyButton , DOM не можа бачыць абнаўлення. Вам неабходна абнавіць значэнне MyButton і пазбавіцца ад дыяпазону.
дададзена аўтар Jay Patel, крыніца
Гэта так здаецца. Паспрабуйце выдаліць яго, як гэта не служыць ніякай мэты.
дададзена аўтар Jay Patel, крыніца
Чаму пралёт ўнутры кнопкі?
дададзена аўтар Jay Patel, крыніца
Я думаў, што праблема была ў выкарыстанні innerHTML , таму я рэкамендаваў выкарыстоўваць значэнне
дададзена аўтар Jay Patel, крыніца

Злёгку мадыфікаваны код на 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);
}

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

0
дададзена

DOM буфера таксама існуе ў браўзэры па змаўчанні на андроіда, доўга працуе яваскрипт толькі флэш-буфер DOM адзін раз, выкарыстоўваць SetTimeout (..., 50), каб вырашыць гэтую праблему.

0
дададзена

Некаторыя браўзэры не падтрымліваюць атрыбут добры OnClick HTML. Лепш выкарыстоўваць гэтую падзею на JS аб'екта. Як гэта:

<button id="mybutt" class="buttonEnabled">
    do some work
</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>

I made a fiddle with working example http://jsfiddle.net/BZWbH/2/

0
дададзена
на жаль, няма, не працуе. ваш абноўлены jsfiddle: jsfiddle.net/BZWbH/3
дададзена аўтар MIrrorMirror, крыніца

Вы спрабавалі дадаць слухач «OnMouseDown», каб змяніць тэкст кнопкі і для LongRunning функцыі націсніце падзея.

0
дададзена

Сімуляваць 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";
    }
});}
0
дададзена