教程:将图像上传到必应视觉搜索 API
警告
2020 年 10 月 30 日,必应搜索 API 从 Azure AI 服务迁移到必应搜索服务。 本文档仅供参考。 有关更新的文档,请参阅必应搜索 API 文档。 关于为必应搜索创建新的 Azure 资源的说明,请参阅通过 Azure 市场创建必应搜索资源。
使用必应视觉搜索 API 可在 Web 中搜索类似于所上传图像的图像。 本教程介绍如何创建一个可向该 API 发送图像并在网页中显示该 API 返回的见解的 Web 应用程序。 请注意,此应用程序并不遵守有关使用该 API 的所有必应用法和显示要求。
在 GitHub 上可以找到此示例的完整源代码,以及附加的错误处理方法和注释。
教程应用演示了如何:
- 将图像上传到必应视觉搜索 API
- 在 Web 应用程序中显示图像搜索结果
- 浏览该 API 提供的不同见解
先决条件
创建 Azure 资源
通过创建以下 Azure 资源之一开始使用必应视觉搜索 API:
- 在删除资源前,可通过 Azure 门户使用。
- 选择
S9
定价层。
- 在删除资源前,可通过 Azure 门户使用。
- 在多个 Azure AI 服务中对应用程序使用相同的密钥和终结点。
创建和构造网页
创建一个 HTML 页面,用于将图像发送到必应视觉搜索 API,接收见解,然后显示见解。 在偏好的编辑器或 IDE 中,创建名为“uploaddemo.html”的文件。 将以下基本 HTML 结构添加到该文件:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Visual Search Upload Demo</title>
</head>
<body>
</body>
</html>
将页面划分为请求部分和响应部分。用户在请求部分提供发出请求所需的全部信息,而见解显示在响应部分。 将以下 <div>
标记添加到 <body>
。 <hr>
标记将请求部分和响应部分直观区分开来:
<div id="requestSection"></div>
<hr />
<div id="responseSection"></div>
将 <script>
标记添加到 <head>
标记,以包含应用程序的 JavaScript:
<script>
<\script>
获取上传文件
为了让用户选择要上传的图像,该应用程序使用了 type 属性设置为 file
的 <input>
标记。 UI 中需要明确指出该应用程序将使用必应来获取搜索结果。
将以下 <div>
添加到 requestSection
<div>
。 文件输入接受任何图像类型的单个文件(例如 .jpg、.gif、.png)。 onchange
事件指定用户选择文件时调用的处理程序。
<output>
标记用于显示所选图像的缩略图:
<div>
<p>Select image to get insights from Bing:
<input type="file" accept="image/*" id="uploadImage" name="files[]" size=40 onchange="handleFileSelect('uploadImage')" />
</p>
<output id="thumbnail"></output>
</div>
创建文件处理程序
创建一个可以读入所要上传的图像的处理程序函数。 在循环访问 FileList
对象中的文件时,该处理程序应确保所选的文件是图像文件,并且其大小为 1 MB 或更小。 如果图像更大,则必须先减小其大小,然后再上传。 最后,该处理程序会显示图像的缩略图。
function handleFileSelect(selector) {
var files = document.getElementById(selector).files; // A FileList object
for (var i = 0, f; f = files[i]; i++) {
// Ensure the file is an image file.
if (!f.type.match('image.*')) {
alert("Selected file must be an image file.");
document.getElementById("uploadImage").value = null;
continue;
}
// Image must be <= 1 MB and should be about 1500px.
if (f.size > 1000000) {
alert("Image must be less than 1 MB.");
document.getElementById("uploadImage").value = null;
continue;
}
var reader = new FileReader();
// Capture the file information.
reader.onload = (function(theFile) {
return function(e) {
var fileOutput = document.getElementById('thumbnail');
if (fileOutput.childElementCount > 0) {
fileOutput.removeChild(fileOutput.lastChild); // Remove the current pic, if it exists
}
// Render thumbnail.
var span = document.createElement('span');
span.innerHTML = ['<img class="thumb" src="', e.target.result,
'" title="', escape(theFile.name), '"/>'].join('');
fileOutput.insertBefore(span, null);
};
})(f);
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
添加和存储订阅密钥
该应用程序需要使用订阅密钥来调用必应视觉搜索 API。 对于本教程,你将在 UI 中提供该密钥。 将以下 <input>
标记(type 属性设置为 text)添加到紧靠在文件 <output>
标记下面的 <body>
:
<div>
<p>Subscription key:
<input type="text" id="key" name="subscription" size=40 maxlength="32" />
</p>
</div>
使用图像和订阅密钥可以调用必应视觉搜索来获取有关图像的见解。 在本教程中,调用将使用默认市场 (en-us
) 和安全搜索值 (moderate
)。
此应用程序提供一个用于更改这些值的选项。 在订阅密钥 <div>
下面添加以下 <div>
。 该应用程序使用 <select>
标记提供市场和安全搜索值的下拉列表。 两个列表都显示默认值。
<div>
<p><a href="#" onclick="expandCollapse(options.id)">Query options</a></p>
<div id="options" style="display:none">
<p style="margin-left: 20px">Market:
<select id="mkt">
<option value="es-AR">Argentina (Spanish)</option>
<option value="en-AU">Australia (English)</option>
<option value="de-AT">Austria (German)</option>
<option value="nl-BE">Belgium (Dutch)</option>
<option value="fr-BE">Belgium (French)</option>
<option value="pt-BR">Brazil (Portuguese)</option>
<option value="en-CA">Canada (English)</option>
<option value="fr-CA">Canada (French)</option>
<option value="es-CL">Chile (Spanish)</option>
<option value="da-DK">Denmark (Danish)</option>
<option value="fi-FI">Finland (Finnish)</option>
<option value="fr-FR">France (French)</option>
<option value="de-DE">Germany (German)</option>
<option value="zh-HK">Hong Kong SAR(Traditional Chinese)</option>
<option value="en-IN">India (English)</option>
<option value="en-ID">Indonesia (English)</option>
<option value="it-IT">Italy (Italian)</option>
<option value="ja-JP">Japan (Japanese)</option>
<option value="ko-KR">Korea (Korean)</option>
<option value="en-MY">Malaysia (English)</option>
<option value="es-MX">Mexico (Spanish)</option>
<option value="nl-NL">Netherlands (Dutch)</option>
<option value="en-NZ">New Zealand (English)</option>
<option value="no-NO">Norway (Norwegian)</option>
<option value="zh-CN">People's Republic of China (Chinese)</option>
<option value="pl-PL">Poland (Polish)</option>
<option value="pt-PT">Portugal (Portuguese)</option>
<option value="en-PH">Philippines (English)</option>
<option value="ru-RU">Russia (Russian)</option>
<option value="ar-SA">Saudi Arabia (Arabic)</option>
<option value="en-ZA">South Africa (English)</option>
<option value="es-ES">Spain (Spanish)</option>
<option value="sv-SE">Sweden (Swedish)</option>
<option value="fr-CH">Switzerland (French)</option>
<option value="de-CH">Switzerland (German)</option>
<option value="zh-TW">Taiwan (Traditional Chinese)</option>
<option value="tr-TR">Türkiye (Turkish)</option>
<option value="en-GB">United Kingdom (English)</option>
<option value="en-US" selected>United States (English)</option>
<option value="es-US">United States (Spanish)</option>
</select>
</p>
<p style="margin-left: 20px">Safe search:
<select id="safesearch">
<option value="moderate" selected>Moderate</option>
<option value="strict">Strict</option>
<option value="off">off</option>
</select>
</p>
</div>
</div>
将搜索选项添加到网页
该应用程序在“查询”选项链接控制的可折叠 <div>
中隐藏列表。 单击“查询”选项链接时,<div>
将展开,让你能够查看和修改查询选项。 如果再次单击“查询”选项链接,<div>
将折叠并隐藏。 以下代码片段显示“查询”选项链接的 onclick
处理程序。 处理程序控制 <div>
是展开还是折叠。 将此处理程序添加到 <script>
部分。 该演示中的所有可折叠的 <div>
部分都使用处理程序。
// Contains the toggle state of divs.
var divToggleMap = {}; // divToggleMap['foo'] = 0; // 1 = show, 0 = hide
// Toggles between showing and hiding the specified div.
function expandCollapse(divToToggle) {
var div = document.getElementById(divToToggle);
if (divToggleMap[divToToggle] == 1) { // if div is expanded
div.style.display = "none";
divToggleMap[divToToggle] = 0;
}
else { // if div is collapsed
div.style.display = "inline-block";
divToggleMap[divToToggle] = 1;
}
}
调用 onclick
处理程序
在正文中选项 <div>
下面添加以下 "Get insights"
按钮。 使用该按钮可以发起调用。 单击该按钮时,游标将更改为旋转等待游标并调用 onclick
处理程序。
<p><input type="button" id="query" value="Get insights" onclick="document.body.style.cursor='wait'; handleQuery()" /></p>
将按钮的 onclick
处理程序 handleQuery()
添加到 <script>
标记。
处理查询
处理程序 handleQuery()
确保订阅密钥存在且长度为 32 个字符,同时确保已选择图像。 它还清除上一查询的所有见解。 然后,它调用 sendRequest()
函数来发出调用。
function handleQuery() {
var subscriptionKey = document.getElementById('key').value;
// Make sure user provided a subscription key and image.
// For this demo, the user provides the key but typically you'd
// get it from secured storage.
if (subscriptionKey.length !== 32) {
alert("Subscription key length is not valid. Enter a valid key.");
document.getElementById('key').focus();
return;
}
var imagePath = document.getElementById('uploadImage').value;
if (imagePath.length === 0)
{
alert("Please select an image to upload.");
document.getElementById('uploadImage').focus();
return;
}
var responseDiv = document.getElementById('responseSection');
// Clear out the response from the last query.
while (responseDiv.childElementCount > 0) {
responseDiv.removeChild(responseDiv.lastChild);
}
// Send the request to Bing to get insights about the image.
var f = document.getElementById('uploadImage').files[0];
sendRequest(f, subscriptionKey);
}
发送搜索请求
sendRequest()
函数可设置终结点 URL 的格式、将 Ocp-Apim-Subscription-Key
标头设置为订阅密钥、追加要上传的图像二进制文件、指定响应处理程序并发出调用。
function sendRequest(file, key) {
var market = document.getElementById('mkt').value;
var safeSearch = document.getElementById('safesearch').value;
var baseUri = `https://api.cognitive.microsoft.com/bing/v7.0/images/visualsearch?mkt=${market}&safesearch=${safeSearch}`;
var form = new FormData();
form.append("image", file);
var request = new XMLHttpRequest();
request.open("POST", baseUri);
request.setRequestHeader('Ocp-Apim-Subscription-Key', key);
request.addEventListener('load', handleResponse);
request.send(form);
}
获取和处理 API 响应
handleResponse()
函数处理调用必应视觉搜索后返回的响应。 如果调用成功,它会将 JSON 响应分析为包含见解的各个标记。 接下来,它将搜索结果添加到页面。 然后,应用程序将为每个标记创建可折叠的 <div>
,用于管理要显示的数据量。 将处理程序添加到 <script>
部分。
function handleResponse() {
if(this.status !== 200){
alert("Error calling Bing Visual Search. See console log for details.");
console.log(this.responseText);
return;
}
var tags = parseResponse(JSON.parse(this.responseText));
var h4 = document.createElement('h4');
h4.textContent = 'Bing internet search results';
document.getElementById('responseSection').appendChild(h4);
buildTagSections(tags);
document.body.style.cursor = 'default'; // reset the wait cursor set by query insights button
}
分析响应
parseResponse
函数通过循环访问 json.tags
将 JSON 响应转换为字典对象。
function parseResponse(json) {
var dict = {};
for (var i =0; i < json.tags.length; i++) {
var tag = json.tags[i];
if (tag.displayName === '') {
dict['Default'] = JSON.stringify(tag);
}
else {
dict[tag.displayName] = JSON.stringify(tag);
}
}
return(dict);
}
生成标记部分
buildTagSections()
函数循环访问已分析的 JSON 标记,并调用 buildDiv()
函数来为每个标记生成一个 <div>
。 每个标记显示为链接。 单击该链接时,该标记将会展开,显示其相关见解。 再次单击该链接会折叠该部分。
function buildTagSections(tags) {
for (var tag in tags) {
if (tags.hasOwnProperty(tag)) {
var tagSection = buildDiv(tags, tag);
document.getElementById('responseSection').appendChild(tagSection);
}
}
}
function buildDiv(tags, tag) {
var tagSection = document.createElement('div');
tagSection.setAttribute('class', 'subSection');
var link = document.createElement('a');
link.setAttribute('href', '#');
link.setAttribute('style', 'float: left;')
link.text = tag;
tagSection.appendChild(link);
var contentDiv = document.createElement('div');
contentDiv.setAttribute('id', tag);
contentDiv.setAttribute('style', 'clear: left;')
contentDiv.setAttribute('class', 'section');
tagSection.appendChild(contentDiv);
link.setAttribute('onclick', `expandCollapse("${tag}")`);
divToggleMap[tag] = 0; // 1 = show, 0 = hide
addDivContent(contentDiv, tag, tags[tag]);
return tagSection;
}
在网页中显示搜索结果
buildDiv()
函数调用 addDivContent
函数来生成每个标记的可折叠 <div>
的内容。
标记的内容包括标记响应中的 JSON。 最初,只显示 JSON 的前 100 个字符,但你可单击 JSON 字符串以显示完整的 JSON。 如果再次单击,JSON 字符串将折叠回 100 个字符。
接下来,添加标记中找到的操作类型。 对于每个操作类型,请调用相应的函数来添加其见解:
function addDivContent(div, tag, json) {
// Adds the first 100 characters of the json that contains
// the tag's data. The user can click the text to show the
// full json. They can click it again to collapse the json.
var para = document.createElement('p');
para.textContent = String(json).substr(0, 100) + '...';
para.setAttribute('title', 'click to expand');
para.setAttribute('style', 'cursor: pointer;')
para.setAttribute('data-json', json);
para.addEventListener('click', function(e) {
var json = e.target.getAttribute('data-json');
if (e.target.textContent.length <= 103) { // 100 + '...'
e.target.textContent = json;
para.setAttribute('title', 'click to collapse');
}
else {
para.textContent = String(json).substr(0, 100) + '...';
para.setAttribute('title', 'click to expand');
}
});
div.appendChild(para);
var parsedJson = JSON.parse(json);
// Loop through all the actions in the tag and display them.
for (var j = 0; j < parsedJson.actions.length; j++) {
var action = parsedJson.actions[j];
var subSectionDiv = document.createElement('div');
subSectionDiv.setAttribute('class', 'subSection');
div.appendChild(subSectionDiv);
var h4 = document.createElement('h4');
h4.innerHTML = action.actionType;
subSectionDiv.appendChild(h4);
if (action.actionType === 'ImageResults') {
addImageWithWebSearchUrl(subSectionDiv, parsedJson.image, action);
}
else if (action.actionType === 'DocumentLevelSuggestions') {
addRelatedSearches(subSectionDiv, action.data.value);
}
else if (action.actionType === 'RelatedSearches') {
addRelatedSearches(subSectionDiv, action.data.value);
}
else if (action.actionType === 'PagesIncluding') {
addPagesIncluding(subSectionDiv, action.data.value);
}
else if (action.actionType === 'VisualSearch') {
addRelatedImages(subSectionDiv, action.data.value);
}
else if (action.actionType === 'Recipes') {
addRecipes(subSectionDiv, action.data.value);
}
else if (action.actionType === 'ShoppingSources') {
addShopping(subSectionDiv, action.data.offers);
}
else if (action.actionType === 'ProductVisualSearch') {
addProducts(subSectionDiv, action.data.value);
}
else if (action.actionType === 'TextResults') {
addTextResult(subSectionDiv, action);
}
else if (action.actionType === 'Entity') {
addEntity(subSectionDiv, action);
}
}
}
显示不同操作的见解
以下函数显示不同操作的见解。 函数将提供可单击的图像或可单击的链接,单击该图像或链接会向你发送一个网页,其中包含有关该图像的详细信息。 此页面由 Bing.com 或图像的原始网站托管。 并非所有见解数据都会显示在此应用程序中。 若要查看某项见解的所有可用字段,请参阅图像 - 视觉搜索参考。
注意
必须在页面中显示最少量的见解信息。 有关详细信息,请参阅必应搜索 API 的使用和显示要求。
RelatedImages 见解
addRelatedImages()
函数通过循环访问 RelatedImages
操作的列表,并将 <img>
标记添加到每个操作的外部 <div>
,来为托管相关图像的每个网站创建一个标题:
function addRelatedImages(div, images) {
var length = (images.length > 10) ? 10 : images.length;
// Set the title to the website that hosts the image. The title displays
// when the user hovers over the image.
// Make the image clickable. If the user clicks the image, they're taken
// to the image in Bing.com.
for (var j = 0; j < length; j++) {
var img = document.createElement('img');
img.setAttribute('src', images[j].thumbnailUrl + '&w=120&h=120');
img.setAttribute('style', 'margin: 20px 20px 0 0; cursor: pointer;');
img.setAttribute('title', images[j].hostPageDisplayUrl);
img.setAttribute('data-webSearchUrl', images[j].webSearchUrl)
img.addEventListener('click', function(e) {
var url = e.target.getAttribute('data-webSearchUrl');
window.open(url, 'foo');
})
div.appendChild(img);
}
}
PagesIncluding 见解
addPagesIncluding()
函数通过循环访问 PagesIncluding
操作的列表,并将 <img>
标记添加到每个操作的外部 <div>
,来为托管上传图像的每个网站创建一个链接:
// Display links to the first 5 webpages that include the image.
// TODO: Add 'more' link in case the user wants to see all of them.
function addPagesIncluding(div, pages) {
var length = (pages.length > 5) ? 5 : pages.length;
for (var j = 0; j < length; j++) {
var page = document.createElement('a');
page.text = pages[j].name;
page.setAttribute('href', pages[j].hostPageUrl);
page.setAttribute('style', 'margin: 20px 20px 0 0');
page.setAttribute('target', '_blank')
div.appendChild(page);
div.appendChild(document.createElement('br'));
}
}
RelatedSearches 见解
addRelatedSearches()
函数通过循环访问 RelatedSearches
操作的列表,并将 <img>
标记添加到每个操作的外部 <div>
,来为托管图像的网站创建一个链接:
// Display the first 10 related searches. Include a link with the image
// that when clicked, takes the user to Bing.com and displays the
// related search results.
// TODO: Add 'more' link in case the user wants to see all of them.
function addRelatedSearches(div, relatedSearches) {
var length = (relatedSearches.length > 10) ? 10 : relatedSearches.length;
for (var j = 0; j < length; j++) {
var childDiv = document.createElement('div');
childDiv.setAttribute('class', 'stackLink');
div.appendChild(childDiv);
var img = document.createElement('img');
img.setAttribute('src', relatedSearches[j].thumbnail.url + '&w=120&h=120');
img.setAttribute('style', 'margin: 20px 20px 0 0;');
childDiv.appendChild(img);
var relatedSearch = document.createElement('a');
relatedSearch.text = relatedSearches[j].displayText;
relatedSearch.setAttribute('href', relatedSearches[j].webSearchUrl);
relatedSearch.setAttribute('target', '_blank');
childDiv.appendChild(relatedSearch);
}
}
Recipes 见解
addRecipes()
函数通过循环访问 Recipes
操作的列表,并将 <img>
标记添加到每个操作的外部 <div>
,来为返回的每个配方创建一个链接:
// Display links to the first 10 recipes. Include the recipe's rating,
// if available.
// TODO: Add 'more' link in case the user wants to see all of them.
function addRecipes(div, recipes) {
var length = (recipes.length > 10) ? 10 : recipes.length;
for (var j = 0; j < length; j++) {
var para = document.createElement('p');
var recipe = document.createElement('a');
recipe.text = recipes[j].name;
recipe.setAttribute('href', recipes[j].url);
recipe.setAttribute('style', 'margin: 20px 20px 0 0');
recipe.setAttribute('target', '_blank')
para.appendChild(recipe);
if (recipes[j].hasOwnProperty('aggregateRating')) {
var span = document.createElement('span');
span.textContent = 'rating: ' + recipes[j].aggregateRating.text;
para.appendChild(span);
}
div.appendChild(para);
}
}
Shopping 见解
addShopping()
函数通过循环访问 RelatedImages
操作的列表,并将 <img>
标记添加到每个操作的外部 <div>
,来为返回的任何购物结果创建一个链接:
// Display links for the first 10 shopping offers.
// TODO: Add 'more' link in case the user wants to see all of them.
function addShopping(div, offers) {
var length = (offers.length > 10) ? 10 : offers.length;
for (var j = 0; j < length; j++) {
var para = document.createElement('p');
var offer = document.createElement('a');
offer.text = offers[j].name;
offer.setAttribute('href', offers[j].url);
offer.setAttribute('style', 'margin: 20px 20px 0 0');
offer.setAttribute('target', '_blank')
para.appendChild(offer);
var span = document.createElement('span');
span.textContent = 'by ' + offers[j].seller.name + ' | ' + offers[j].price + ' ' + offers[j].priceCurrency;
para.appendChild(span);
div.appendChild(para);
}
}
Products 见解
addProducts()
函数通过循环访问 Products
操作的列表,并将 <img>
标记添加到每个操作的外部 <div>
,来为返回的任何产品结果创建一个链接:
// Display the first 10 related products. Display a clickable image of the
// product that takes the user to Bing.com search results for the product.
// If there are any offers associated with the product, provide links to the offers.
// TODO: Add 'more' link in case the user wants to see all of them.
function addProducts(div, products) {
var length = (products.length > 10) ? 10 : products.length;
for (var j = 0; j < length; j++) {
var childDiv = document.createElement('div');
childDiv.setAttribute('class', 'stackLink');
div.appendChild(childDiv);
var img = document.createElement('img');
img.setAttribute('src', products[j].thumbnailUrl + '&w=120&h=120');
img.setAttribute('title', products[j].name);
img.setAttribute('style', 'margin: 20px 20px 0 0; cursor: pointer;');
img.setAttribute('data-webSearchUrl', products[j].webSearchUrl)
img.addEventListener('click', function(e) {
var url = e.target.getAttribute('data-webSearchUrl');
window.open(url, 'foo');
})
childDiv.appendChild(img);
if (products[j].insightsMetadata.hasOwnProperty('aggregateOffer')) {
if (products[j].insightsMetadata.aggregateOffer.offerCount > 0) {
var offers = products[j].insightsMetadata.aggregateOffer.offers;
// Show all the offers. Not all markets provide links to offers.
for (var i = 0; i < offers.length; i++) {
var para = document.createElement('p');
var offer = document.createElement('a');
offer.text = offers[i].name;
offer.setAttribute('href', offers[i].url);
offer.setAttribute('style', 'margin: 20px 20px 0 0');
offer.setAttribute('target', '_blank')
para.appendChild(offer);
var span = document.createElement('span');
span.textContent = 'by ' + offers[i].seller.name + ' | ' + offers[i].price + ' ' + offers[i].priceCurrency;
para.appendChild(span);
childDiv.appendChild(para);
}
}
else { // Otherwise, just show the lowest price that Bing found.
var offer = products[j].insightsMetadata.aggregateOffer;
var para = document.createElement('p');
para.textContent = `${offer.name} | ${offer.lowPrice} ${offer.priceCurrency}`;
childDiv.appendChild(para);
}
}
}
}
TextResult 见解
addTextResult()
函数显示在图像中识别到的任何文本:
function addTextResult(div, action) {
var text = document.createElement('p');
text.textContent = action.displayName;
div.appendChild(text);
}
addEntity()
函数显示一个链接,将用户定向到 Bing.com,让他们获取有关图像中的实体类型的详细信息(如果已检测到任何实体类型):
// If the image is of a person, the tag might include an entity
// action type. Display a link that takes the user to Bing.com
// where they can get details about the entity.
function addEntity(div, action) {
var entity = document.createElement('a');
entity.text = action.displayName;
entity.setAttribute('href', action.webSearchUrl);
entity.setAttribute('style', 'margin: 20px 20px 0 0');
entity.setAttribute('target', '_blank');
div.appendChild(entity);
}
addImageWithWebSearchUrl()
函数在 <div>
中显示一个可单击的图像,用于将用户定向到 Bing.com 上的搜索结果:
function addImageWithWebSearchUrl(div, image, action) {
var img = document.createElement('img');
img.setAttribute('src', image.thumbnailUrl + '&w=120&h=120');
img.setAttribute('style', 'margin: 20px 20px 0 0; cursor: pointer;');
img.setAttribute('data-webSearchUrl', action.webSearchUrl);
img.addEventListener('click', function(e) {
var url = e.target.getAttribute('data-webSearchUrl');
window.open(url, 'foo');
})
div.appendChild(img);
}
添加 CSS 样式
将以下 <style>
部分添加到 <head>
标记,以组织网页的布局:
<style>
.thumb {
height: 75px;
border: 1px solid #000;
}
.stackLink {
width:180px;
min-height:210px;
display:inline-block;
}
.stackLink a {
float:left;
clear:left;
}
.section {
float:left;
display:none;
}
.subSection {
clear:left;
float:left;
}
</style>