237

我正在尝试在另一个画布上添加一个画布——我怎样才能让这个功能等到第一个画布被创建后才开始?

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

14 回答 14

405

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

如果您无法访问该代码(例如,如果它是第三方代码,例如谷歌地图),那么您可以做的是测试是否存在间隔:

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

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

于 2013-04-22T14:30:51.163 回答
91

根据您需要支持的浏览器,可以选择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

于 2016-02-04T20:45:51.837 回答
56

这仅适用于现代浏览器,但我发现使用 a 更容易,then所以请先测试,但是:

ES5

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

ES6

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

用法

checkElement('body') //use whichever selector you want
.then((element) => {
     console.info(element);
     //Do whatever you want now the element is there
});
于 2017-12-12T15:42:53.333 回答
53

一种更现代的等待元素的方法:

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

请注意,此代码需要包装在异步函数中。

于 2018-02-03T22:48:42.440 回答
37

这是对 Jamie Hutber 的回答的一个小改进

const checkElement = async selector => {
  while ( document.querySelector(selector) === null) {
    await new Promise( resolve =>  requestAnimationFrame(resolve) )
  }
  return document.querySelector(selector); 
};

要使用:

checkElement('.myElement').then((selector) => {
  console.log(selector);
});
于 2018-11-12T20:57:41.060 回答
16

如果您想要使用 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

等等瞧!:)

于 2020-05-12T08:38:03.410 回答
10

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
  });
于 2017-07-07T20:19:10.247 回答
6

这是使用 observables 的解决方案。

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);
于 2019-10-11T11:08:49.993 回答
5

您可以通过设置超时来检查 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);
于 2014-09-26T07:10:52.513 回答
5

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

以防万一元素从未显示,所以我们不会无限检查。

于 2020-02-27T00:23:26.683 回答
2

一种纯基于 Promise 的 JavaScript 方法,您可以告诉等待数毫秒。

    const waitElementFor = function(query, ms = 3000) { // 3000 === 3 seconds
        return new Promise((resolve) => {
            var waited = 0;
            var el = null;
            var wi = setInterval(function() {
                el = document.querySelector(query);
                if (waited >= ms || el) {
                    clearInterval(wi);
                    if(el) {
                        resolve(el);
                    } else {
                        resolve(null);
                    }
                }
                waited += 10;
            }, 10);  
        });
    }

要使用该函数,只需在异步函数中使用以下代码。

var element = await waitElementFor('#elementID');

片段:

const waitElementFor = function(query, ms = 3000) { // 3000 === 3 seconds
    return new Promise((resolve) => {
        var waited = 0;
        var el = null;
        var wi = setInterval(function() {
            el = document.querySelector(query);
            if (waited >= ms || el) {
                clearInterval(wi);
                if(el) {
                    resolve(el);
                } else {
                    resolve(null);
                }
            }
            waited += 10;
        }, 10);  
    });
}

async function snippetTestAyncFunction(){
    var element = await waitElementFor('#elementID');
    console.log(element);
}

snippetTestAyncFunction();

于 2022-01-29T14:20:49.707 回答
1

也许我有点晚了:),但这是chrisjhoughton提供的一个不错且简短的解决方案,它允许在等待结束时执行回调函数。

https://gist.github.com/chrisjhoughton/7890303

var waitForEl = function(selector, callback) {
  if (jQuery(selector).length) {
    callback();
  } else {
    setTimeout(function() {
      waitForEl(selector, callback);
    }, 100);
  }
};

waitForEl(selector, function() {
  // work the magic
});

如果需要将参数传递给回调函数,可以这样使用:

waitForEl("#" + elDomId, () => callbackFunction(param1, param2));

不过要小心!默认情况下,此解决方案可能会陷入无限循环的陷阱。

The GitHub thread 中还提供了对 topicstarter 建议的一些改进。

享受!

于 2021-01-10T07:09:20.093 回答
1

这适用于那些在 Chrome 控制台中运行代码而不仅仅是硬编码到 html 中的人。

上面的 user993683 提供了可以在您的控制台代码中运行的代码。他/她的代码如下:

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

他/她补充说它“需要在异步函数中”。如果你在 Chrome 的控制台中使用代码,那么实际上你不需要将它包装在一个函数中。它会像写的那样工作。您只需要在尝试访问元素之前将其放置在代码中的位置以确保它存在。

唯一需要注意的是,它不适用于仅在其他情况下有时才出现的元素。否则,如果元素从不下载,它将无限循环,您必须关闭浏览器才能停止等待。仅将其用于您确定会出现的元素。

我公司的表格页面有十几个或更多字段要为每个案例编号填写。而且我每天在脚本数组中都有数百个案例编号。更改 iFrame SRC 时元素不会同时加载,并且“onload”在 Chrome 控制台脚本中不起作用。因此,这种方法对我来说是天赐良机,它每天至少为我节省 45 分钟,而不是由于加载时间波动而在这里等待 10 秒或那里等待 30 秒的旧通用异步等待。

我所做的唯一更改是“getElementById”而不是一般的“querySelector”,因为我需要的所有元素都有 ID。

while(!document.getElementById("myFrame").contentWindow.document.getElementById('someDocID')) {
      await new Promise(r => setTimeout(r, 500));
    }
// After completing the wait above it is now safe to access the element
document.getElementById("myFrame").contentWindow.document.getElementById('someDocID'
).innerText = "Smith, John R";
// and now click the submit button then change the SRC to a fresh form, and use
//*emphasized text* the code again to wait for it to fully load

我向监视器道歉,但我添加了这个作为答案,因为在对控制台脚本进行了几个月的研究并等待元素加载之后,user993683 关于函数的评论终于让我意识到控制台脚本不需要此代码的函数。我的目标只是为其他控制台脚本用户保存与我经历的相同的学习曲线。

于 2022-01-05T16:39:52.753 回答
0

只需使用setTimeOut递归:

waitUntilElementIsPresent(callback: () => void): void {
    if (!this.methodToCheckIfElementIsPresent()) {
        setTimeout(() => this.waitUntilElementIsPresent(callback), 500);
        return;
    }
    callback();
}

用法

this.waitUntilElementIsPresent(() => console.log('Element is present!'));

您可以限制尝试次数,因此当限制后元素不存在时将引发错误:

waitUntilElementIsPresent(callback: () => void, attempt: number = 0): void {
    const maxAttempts = 10;
    if (!this.methodToCheckIfElementIsPresent()) {
        attempt++;
        setTimeout(() => this.waitUntilElementIsPresent(callback, attempt), 500);
        return;
    } else if (attempt >= maxAttempts) {
        return;
    }
    callback();
}
于 2020-10-22T06:40:53.273 回答