요소가 존재할 때까지 기능 대기


158

다른 캔버스 위에 캔버스를 추가하려고합니다. 첫 번째 캔버스가 만들어 질 때까지이 함수가 시작될 때까지 어떻게 대기 할 수 있습니까?

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

답변:


303

캔버스를 작성하는 코드에 액세스 할 수있는 경우-캔버스를 작성한 후 바로 함수를 호출하십시오.

해당 코드에 액세스 할 수없는 경우 (예 : 구글 맵과 같은 타사 코드 인 경우) 간격으로 존재하는지 테스트하십시오.

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

그러나 타사 코드에는로드가 끝나면 코드를 콜백 또는 이벤트 트리거링하여 활성화하는 옵션이 있습니다. 그것은 당신이 당신의 기능을 넣을 수있는 곳 일 수 있습니다. 인터벌 솔루션은 실제로 나쁜 솔루션이므로 다른 것이 작동하지 않는 경우에만 사용해야합니다.


angularjs typeahead에서 사용하기에 완벽한 솔루션입니다. 올바른 방향으로 안내해 주셔서 감사합니다!
JuanTrev

1
거기에 다른 것을 넣기 전에 Ajax에 의해 만들어지는 것을 기다리는 훌륭한 솔루션. 고마워
Countzero

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

NB 나는이 코드를 직접 테스트하지는 않았지만 이것이 일반적인 아이디어입니다.

변경된 DOM 부분 만 검색하도록 쉽게 확장 할 수 있습니다. 이를 위해서는 mutations인수를 사용하십시오 MutationRecord. 객체 의 배열입니다 .


2
이것을 좋아했습니다. 감사합니다.
insign

1
이 패턴은 많은 경우에 특히 유용합니다. 특히 JS를 페이지로 가져오고 다른 항목이로드되었는지 알 수없는 경우에 특히 그렇습니다.
이름을 위해

1
가장 좋은 답변! 감사!
안토니 해치 킨

1
오래된 브라우저 (ff38)가 붙어있어 저를 구했습니다.
Jung rhew

1
이것은 놀랍다! 나는 이것이 이전에 존재한다는 것을 알기를 바란다.

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
이것은 꽤 깔끔합니다!
Dexter Bengil

무엇입니까 r?
다니엘 몰러

글쎄,하지만 어디에서 왔을 까? 무엇을합니까? 무엇을 보냈 setTimeout습니까?
다니엘 몰러

@ DanielMöller 이 코드를 더 잘 이해하려면 Promises 를 살펴 봐야 할 수도 있습니다 . 기본적으로 코드의 기능은 500ms의 시간 초과를 설정하고 while 루프의 새로운 반복을 시작하기 전에 완료 될 때까지 기다리는 것입니다. 영리한 솔루션!
ClementParis016

8

Jamie Hutber의 답변에 비해 약간의 개선이 있습니다.

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, 모듈 및 약속 :

// 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

나는 새로운 약속 전에 반환을 놓친 것 같아요.
ncubica

1
이것은 모든 정기 타이머 기반 검사보다 훨씬 나은 적절한 솔루션입니다.
András Szepesházi

4
실제로 이것은 현재 형태로는 작동하지 않습니다. $ someElement가 초기에 null 인 경우 (즉, 아직 DOM에 존재하지 않는 경우) CSS 선택기 대신이 null 값을 onElementReady 함수에 전달하면 요소가 해결되지 않습니다. 대신 CSS 선택기를 텍스트로 전달하고 각 패스에서 .querySelector를 통해 요소에 대한 참조를 얻으십시오.
András 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

vo! :)


2

Iftah의 또 다른 변형

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.