使函数等到元素存在


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
在单击时创建画布时,运行该函数或触发一个事件,该事件运行一个运行该函数的处理程序。当元素变为可用时,不会发生内置的跨浏览器事件。
凯文B

Answers:


303

如果您有权访问创建画布的代码,则只需在创建画布后立即在其中调用该函数即可。

如果您无权访问该代码(例如,如果它是第三方代码,例如google maps),那么您可以做的就是在一定间隔内测试其存在:

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

但请注意-很多时候,第三方代码都可以选择在加载完成后激活代码(通过回调或事件触发)。那可能是放置函数的地方。间隔解决方案确实是一个糟糕的解决方案,只有在其他方法无效的情况下才应使用。


在angularjs预输入中使用的完美解决方案。感谢您引导我朝正确的方向前进!
JuanTrev 2014年

1
优秀的解决方案,可以等待Ajax正在创建的东西再放入其他东西。非常感谢。
Countzero

@iftah如果选择器是变量,我将如何使其工作?同样,如果它是一个ID或类选择器,也会随之更改。有时,当我选择一个类时,会返回多个元素,并且我需要找到一种将索引传递给选择器的方法,以找出哪个元素。我该怎么做?谢谢
Kragalon '16

@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
喜欢这个。谢谢。
签署

1
这种模式在很多情况下确实很有用,尤其是当您将JS拉入页面并且不知道是否加载了其他项目时。
的名字

1
最好的答案!谢谢!
安东尼·哈奇金斯

1
我使用的是旧版浏览器(ff38),这救了我。
jung rhew 19-10-17

1
这真太了不起了!我希望我早知道这已经存在。
罗布

39

这仅适用于现代浏览器,但我发现仅使用a更容易,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 '18

是什么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
杰夫·普基特

我想我在新的承诺之前错过了回报。
ncubica

1
这是正确的解决方案,比所有基于计时器的定期检查要好得多。
安德拉斯·塞佩沙奇

4
实际上,这不适用于当前形式。如果$ someElement最初为空(即DOM中尚不存在),则将该空值(而不是CSS选择器)传递给onElementReady函数,该元素将永远无法解析。相反,将CSS选择器作为文本传递,并尝试在每次传递时通过.querySelector获得对该元素的引用。
安德拉斯·塞佩沙基

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

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

以防万一该元素从未显示,因此我们不会进行无限检查。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.