要素が存在するまで関数を待機させる


159

別のキャンバスの上にキャンバスを追加しようとしています。最初のキャンバスが作成されるまで、この関数の開始を待機させるにはどうすればよいですか?

function PaintObject(brush) {

    this.started = false;

    // get handle of the main canvas, as a DOM object, not as a jQuery Object. Context is unfortunately not yet
    // available in jquery canvas wrapper object.
    var mainCanvas = $("#" + brush).get(0);

    // Check if everything is ok
    if (!mainCanvas) {alert("canvas undefined, does not seem to be supported by your browser");}
    if (!mainCanvas.getContext) {alert('Error: canvas.getContext() undefined !');}

    // Get the context for drawing in the canvas
    var mainContext = mainCanvas.getContext('2d');
    if (!mainContext) {alert("could not get the context for the main canvas");}

    this.getMainCanvas = function () {
        return mainCanvas;
    }
    this.getMainContext = function () {
        return mainContext;
    }

    // Prepare a second canvas on top of the previous one, kind of second "layer" that we will use
    // in order to draw elastic objects like a line, a rectangle or an ellipse we adjust using the mouse
    // and that follows mouse movements
    var frontCanvas = document.createElement('canvas');
    frontCanvas.id = 'canvasFront';
    // Add the temporary canvas as a second child of the mainCanvas parent.
    mainCanvas.parentNode.appendChild(frontCanvas);

    if (!frontCanvas) {
        alert("frontCanvas null");
    }
    if (!frontCanvas.getContext) {
        alert('Error: no frontCanvas.getContext!');
    }
    var frontContext = frontCanvas.getContext('2d');
    if (!frontContext) {
        alert("no TempContext null");
    }

    this.getFrontCanvas = function () {
        return frontCanvas;
    }
    this.getFrontContext = function () {
        return frontContext;
    }

4
クリックでキャンバスを作成したら、関数を実行するか、関数を実行するハンドラーを実行するイベントをトリガーします。要素が利用可能になったときに発生する組み込みのクロスブラウザイベントはありません。
Kevin B

回答:


304

キャンバスを作成するコードにアクセスできる場合は、キャンバスが作成された直後に関数を呼び出すだけです。

そのコードにアクセスできない場合(たとえば、それがgoogleマップなどのサードパーティのコードである場合)、実行できることは、間隔内の存在をテストすることです。

var checkExist = setInterval(function() {
   if ($('#the-canvas').length) {
      console.log("Exists!");
      clearInterval(checkExist);
   }
}, 100); // check every 100ms

ただし、サードパーティのコードには、ロードが完了したときにコードを(コールバックまたはイベントのトリガーによって)有効にするオプションが何度もあることに注意してください。それはあなたの関数を置くことができる場所かもしれません。区間解は本当に悪い解であり、他に何も機能しない場合にのみ使用する必要があります。


angularjsの先行入力で使用するのに最適なソリューションです。正しい方向に案内してくれてありがとう!
JuanTrev 2014年

1
Ajaxによって何かが作成されるのを待ってから、他のものをそこに置くのに最適なソリューションです。どうもありがとう。
Countzero、2015年

@iftahセレクタが変数である場合、これをどのように機能させることができますか?また、IDまたはクラスセレクターの場合も変更されます。クラスで選択したときに複数の要素が返されることがあるので、セレクターにインデックスを渡してどの要素を特定するかを見つける必要があります。どうすればいいですか?ありがとう
Kragalon

@Kraglonこれは完全に異なる質問であり、この回答のコメントには適していません。私は...など、問題が何であるか、あなたは新しい質問を勧め、あなたがしようとしたかを説明
Iftah

8
もう一つは、与えられた溶液を用いたとき、あなたはforループ内のコードのその部分を持ち、最大リトライカウンタを設定する必要があります言及することが重要である何かがうまくいかない場合は、無限ループ:)で終わるいけない
BJ

48

サポートする必要があるブラウザーに応じて、MutationObserverのオプションがあります。

編集:すべての主要なブラウザーがMutationObserverをサポートするようになりました

これに沿った何かがうまくいくはずです:

// callback executed when canvas was found
function handleCanvas(canvas) { ... }

// set up the mutation observer
var observer = new MutationObserver(function (mutations, me) {
  // `mutations` is an array of mutations that occurred
  // `me` is the MutationObserver instance
  var canvas = document.getElementById('my-canvas');
  if (canvas) {
    handleCanvas(canvas);
    me.disconnect(); // stop observing
    return;
  }
});

// start observing
observer.observe(document, {
  childList: true,
  subtree: true
});

注意:私はこのコードを自分でテストしていませんが、それは一般的な考えです。

これを簡単に拡張して、変更されたDOMの部分のみを検索できます。そのためには、mutations引数を使用します。これはMutationRecordオブジェクトの配列です。


2
これを追加しました。ありがとうございました。
19:31に署名

1
このパターンは、JSをページにプルしていて、他のアイテムがロードされているかどうかわからない場合など、多くの場合に非常に役立ちます。
名前の場合

1
ベストアンサー!ありがとう!
アントニーハッチキンズ

1
私は古いブラウザ(ff38)で立ち往生していて、これで助かりました。
ジョンリュー

1
これは素晴らしいです!これが以前に存在していたことを知っていればいいのに。
Rob

39

これは最新のブラウザーでのみ機能しますが、使用する方が簡単だと思うthenので、まずテストしてください。

コード

function rafAsync() {
    return new Promise(resolve => {
        requestAnimationFrame(resolve); //faster than set time out
    });
}

function checkElement(selector) {
    if (document.querySelector(selector) === null) {
        return rafAsync().then(() => checkElement(selector));
    } else {
        return Promise.resolve(true);
    }
}

またはジェネレータ関数を使用する

async function checkElement(selector) {
    const querySelector = document.querySelector(selector);
    while (querySelector === null) {
        await rafAsync()
    }
    return querySelector;
}  

使用法

checkElement('body') //use whichever selector you want
.then((element) => {
     console.info(element);
     //Do whatever you want now the element is there
});

エラーがあります。発電機の機能を使用する場合は、querySelectorは、すべてのループで更新する必要がありますwhile (document.querySelector(selector) === null) {await rafAsync()}
haofly

31

要素を待つためのより近代的なアプローチ:

while(!document.querySelector(".my-selector")) {
  await new Promise(r => setTimeout(r, 500));
}
// now the element is loaded

このコードは、非同期関数でラップする必要があることに注意してください。


4
これはかなりきれいです!
デクスターベンジル2018年

何がrありますか?
DanielMöller

さて、わかりましたが、それはどこから来たのですか?それは何をするためのものか?何を送っていsetTimeoutますか?
DanielMöller

@DanielMöller は、このコードをよりよく理解するためにPromisesを確認する必要があるかもしれません。基本的にここでコードが行うことは、500msのタイムアウトを設定し、それが完了するのを待ってから、whileループの新しい反復を開始することです。賢い解決策!
ClementParis016

8

これがジェイミー・ハットバーの答えに対する小さな改善です

const checkElement = async selector => {

while ( document.querySelector(selector) === null) {
    await new Promise( resolve =>  requestAnimationFrame(resolve) )
}

return document.querySelector(selector); };

8

でリレーするrequestAnimationFrameよりもsetTimeoutです。これはes6モジュールでの私の解決策であり、を使用していPromisesます。

es6、モジュール、promise:

// onElementReady.js
const onElementReady = $element => (
  new Promise((resolve) => {
    const waitForElement = () => {
      if ($element) {
        resolve($element);
      } else {
        window.requestAnimationFrame(waitForElement);
      }
    };
    waitForElement();
  })
);

export default onElementReady;

// in your app
import onElementReady from './onElementReady';

const $someElement = document.querySelector('.some-className');
onElementReady($someElement)
  .then(() => {
    // your element is ready
  }

plain js and promises

var onElementReady = function($element) {
  return new Promise((resolve) => {
    var waitForElement = function() {
      if ($element) {
        resolve($element);
      } else {
        window.requestAnimationFrame(waitForElement);
      }
    };
    waitForElement();
  })
};

var $someElement = document.querySelector('.some-className');
onElementReady($someElement)
  .then(() => {
    // your element is ready
  });

Uncaught TypeError: Cannot read property 'then' of undefined
Jeff Puckett 2017年

新しいプロミスの前に、私はリターンを逃したと思います...
ncubica

1
これは適切なソリューションであり、すべての定期的なタイマーベースのチェックよりもはるかに優れています。
アンドラーシュSzepesházi

4
実際、これは現在の形式では機能しません。$ someElementが最初はnullの場合(つまり、DOMにまだ存在しない場合)、(CSSセレクターの代わりに)このnull値をonElementReady関数に渡すと、要素は解決されません。代わりに、CSSセレクターをテキストとして渡し、各パスで.querySelectorを介して要素への参照を取得してください。
アンドラーシュSzepesházi

1
これは私のユースケースに最適で、適切な解決策のようです。ありがとう
rdhaese

5

これは、オブザーバブルを使用したソリューションです。

waitForElementToAppear(elementId) {                                          

    return Observable.create(function(observer) {                            
            var el_ref;                                                      
            var f = () => {                                                  
                el_ref = document.getElementById(elementId);                 
                if (el_ref) {                                                
                    observer.next(el_ref);                                   
                    observer.complete();                                     
                    return;                                                  
                }                                                            
                window.requestAnimationFrame(f);                             
            };                                                               
            f();                                                             
        });                                                                  
}                                                                            

今あなたは書くことができます

waitForElementToAppear(elementId).subscribe(el_ref => doSomethingWith(el_ref);

4

domにレンダリングされるまでタイムアウトを設定することで、domが既に存在するかどうかを確認できます。

var panelMainWrapper = document.getElementById('panelMainWrapper');
setTimeout(function waitPanelMainWrapper() {
    if (document.body.contains(panelMainWrapper)) {
        $("#panelMainWrapper").html(data).fadeIn("fast");
    } else {
        setTimeout(waitPanelMainWrapper, 10);
    }
}, 10);

3

MutationObserverを使用した一般的なソリューションが必要な場合は、この関数を使用できます

// MIT Licensed
// Author: jwilson8767

/**
 * Waits for an element satisfying selector to exist, then resolves promise with the element.
 * Useful for resolving race conditions.
 *
 * @param selector
 * @returns {Promise}
 */
export function elementReady(selector) {
  return new Promise((resolve, reject) => {
    const el = document.querySelector(selector);
    if (el) {resolve(el);}
    new MutationObserver((mutationRecords, observer) => {
      // Query for elements matching the specified selector
      Array.from(document.querySelectorAll(selector)).forEach((element) => {
        resolve(element);
        //Once we have resolved we don't need the observer anymore.
        observer.disconnect();
      });
    })
      .observe(document.documentElement, {
        childList: true,
        subtree: true
      });
  });
}

ソース:https : //gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
使用方法の例

elementReady('#someWidget').then((someWidget)=>{someWidget.remove();});

注:MutationObserverは優れたブラウザサポートを備えています。https://caniuse.com/#feat=mutationobserver

エボラ!:)


2

イフタの別のバリエーション

var counter = 10;
var checkExist = setInterval(function() {
  console.log(counter);
  counter--
  if ($('#the-canvas').length || counter === 0) {
    console.log("by bye!");
    clearInterval(checkExist);
  }
}, 200);

要素が表示されない場合に備えて、無限のチェックは行いません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.