我正在开发chrome扩展程序。我想在点击popup.html
中的按钮后将一些API连接到当前标签。我在popup.js
:
$('button').click(function() {
chrome.tabs.executeScript({
file: 'js/ymaps.js'
}, function() {});
});
在ymaps.js
中,我使用以下代码将API连接到当前标签:
var script = document.createElement('script');
script.src = "http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU";
document.getElementsByTagName('head')[0].appendChild(script);
使用Yandex Maps需要此API。因此,在该代码之后,我创建<div>
,其中应放置地图:
$('body').append('<div id="ymapsbox"></div>');
这个简单的代码只会将地图加载到创建的<div>
:
ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
myMap = new ymaps.Map("ymapsbox", {
center: [55.76, 37.64],
zoom: 7
});
}
我认为,一切都很清楚,如果你还在阅读,我会解释问题是什么。
当我点击popup.html
中的按钮时,我会进入Chrome控制台Uncaught ReferenceError: ymaps is not defined
。好像api库没有连接。但!当我手动输入控制台ymaps
时 - 我获得了可用方法的列表,因此库已连接。那么为什么当我调用ymaps
- 来自已执行的.js
- 文件的对象时,我会收到这样的错误?
UPD:我还试图在ymaps.ready(init)
函数中包含$(document).ready()
:
$(document).ready(function() {
ymaps.ready(init);
})
但错误仍在出现。 下面的人说api库可能尚未加载。但是这段代码也会产生错误。
setTimeout(function() {
ymaps.ready(init);
}, 1500);
我甚至试图这样做......
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
if (changeInfo.status == "complete") {
chrome.tabs.executeScript({
file: 'js/gmm/yandexmaps.js'
});
}
});
答案 0 :(得分:4)
ymaps
未定义,因为您尝试在内容脚本中使用它,而库是在页面上下文中加载的(通过<script>
标记)。
通常,您可以通过将库加载为内容脚本来解决问题,例如
chrome.tabs.executeScript({
file: 'library.js'
}, function() {
chrome.tabs.executeScript({
file: 'yourscript.js'
});
});
但是,这不会解决您的问题,因为您的库会在<script>
标记中加载更多外部脚本。因此,只有网页中的脚本才能看到部分库(而不是内容脚本,因为单独的script execution environments)。
<script>
标记并将其作为内容脚本运行。从https://github.com/Rob--W/chrome-api/tree/master/scriptTagContext获取scriptTagContext.js
,然后在其他内容脚本之前加载它。此模块通过将<script>
(在内容脚本中创建)的执行环境更改为内容脚本来解决您的问题。
chrome.tabs.executeScript({
file: 'scriptTagContext.js'
}, function() {
chrome.tabs.executeScript({
file: 'js/ymaps.js'
});
});
有关文档,请参阅Rob--W/chrome-api/scriptTagContext/README.md
有关解决方案背后概念的解释,请参阅first revision of this answer。
如果您 - 某些方式 - 不想使用以前的解决方案,那么还有另一个选项可以让代码运行。我强烈建议反对这种方法,因为它可能(并且会)在其他页面中引起冲突。然而,为了完整性:
通过页面中的<script>
标记插入内容脚本(或者至少是使用外部库的扩展部分),在页面上下文中运行所有代码。这仅在您不使用任何Chrome扩展程序API时才有效,因为您的脚本将有效地使用网页的有限权限运行。
例如,您问题中的代码将按如下方式重构:
var script = document.createElement('script');
script.src = "http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU";
script.onload = function() {
var script = document.createElement('script');
script.textContent = '(' + function() {
// Runs in the context of your page
ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
myMap = new ymaps.Map("ymapsbox", {
center: [55.76, 37.64],
zoom: 7
});
}
} + ')()';
document.head.appendChild(script);
};
document.head.appendChild(script);
这只是将脚本的执行上下文切换到页面的众多方法之一。阅读Building a Chrome Extension - Inject code in a page using a Content script以了解有关其他可能选项的更多信息。
答案 1 :(得分:2)
这不是时间问题,而是与“执行环境”相关的问题。
将脚本注入Web页面的JS上下文(将脚本标记插入head
),但尝试从内容脚本的JS上下文中调用ymaps
。然而,内容脚本在一个孤立的世界中“活着”,无法访问网页的JS上下文(请看 the docs )。
编辑(感谢Rob的评论)
通常,您可以捆绑该库的副本并将其作为内容脚本注入。在您的特定情况下,这也无济于事,因为库本身会将脚本标记插入到加载依赖项中。
根据您的具体要求,您可以:
您可以在弹出窗口或新标签中显示(并让用户与之交互),而不是将地图插入网页。您将在包含该库的新窗口/选项卡中提供要加载的HTML文件(引用该文件的捆绑副本或在 relaxing the default Content Security Policy 之后使用CDN - 前者是推荐的方式)。
修改外部库(即删除脚本标记的插入)。我建议反对它,因为这种方法引入了额外的维护“成本”(例如,每次更新库时都需要重复该过程)。
将所有代码注入网页的上下文中 可能的陷阱:弄乱网页JS,例如覆盖已定义的变量/函数。 此外,如果您需要与chrome。* API(Web页面的JS上下文无法使用,因此您需要设置专有的消息传递机制,例如使用自定义事件),此方法将变得越来越复杂。
然而,如果你只需要执行一些简单的初始化代码,这是一个可行的替代方案:
E.g:
<强> ymaps.js:强>
function initMap() {
ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
myMap = new ymaps.Map("ymapsbox", {
center: [55.76, 37.64],
zoom: 7
});
}
}
$('body').append('<div id="ymapsbox"></div>');
var script1 = document.createElement('script');
script1.src = 'http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU';
script1.addEventListener('load', function() {
var script2 = document.createElement('script');
var script2.textContent = '(' + initMap + ')()';
document.head.appendChild(script2);
});
document.head.appendChild(script1);
罗已经指出了这个主题的重要资源:答案 2 :(得分:-1)
Yandex本身有一个更简单的解决方案。
// div-container of the map
<div id="YMapsID" style="width: 450px; height: 350px;"></div>
<script type="text/javascript">
var myMap;
function init (ymaps) {
myMap = new ymaps.Map("YMapsID", {
center: [55.87, 37.66],
zoom: 10
});
...
}
</script>
// Just after API is loaded the function init will be invoked
// On that moment the container will be ready for usage
<script src="https://...?load=package.full&lang=ru_RU&onload=init">
<强>更新强>
要正确使用此功能,您必须确保init
已准备好加载Yandex-scirpt。这可以通过以下方式实现。
您将init
放在html页面上。
您可以从放置init
的相同脚本开始加载Yandex脚本。
您在html页面上创建一个调度程序,用于捕获两个组件中的ready
个事件。
您还需要检查您的容器是否已创建到Yandex脚本加载的那一刻。
更新2
有时发生init
脚本的加载晚于Yandex-lib。在这种情况下,值得检查:
if(typeof ymaps !== 'undefined' && typeof ymaps.Map !== 'undefined') {
initMap();
}
当我相对于容器移动地图画布时,我遇到了定位地图画布的问题。例如,当容器处于衰落模态窗口中时,这可能发生。在这种情况下,最好是调用窗口大小调整事件:
$('#modal').on('shown.bs.modal', function (e) {
window.dispatchEvent(new Event('resize'));
});