【问题标题】:Scale a Polygon of an image缩放图像的多边形
【发布时间】:2017-10-01 11:40:28
【问题描述】:

我希望能够在悬停时缩放图像的多边形。

准确地说,它是一张世界地图,当您将鼠标悬停在它上面时,我想将这个国家放大,然后在不再悬停时再次缩小它。

我知道,例如 CSS 有 transform: scale(2),它适用于普通图像或世界地图。但实际上我不想缩放孔图,而是地图的一个多边形(或者至少,如果一个多边形不可能,那就是一个正方形(所以只有地图的一小部分)。

有没有办法做到这一点?不必只有CSS,也可以是JS/jQuery。

悬停部分不是问题的一部分,那是简单的 jQuery,但问题是如何仅缩放图像的特定部分。

感谢您的帮助!

【问题讨论】:

  • 您必须为每个国家/地区制作一张图片才能做到这一点,或者每个国家/地区都有一个单独的功能的大型 SVG。
  • 您是否可以选择使用画布元素?
  • @bfontaine “独立功能”是什么意思?实际上,图像确实很大(宽度为 6400 像素),但它仅以大约 1000 像素的宽度显示,所以我将有足够的图像大小来显示它的 6 倍大并且仍然清晰
  • @enhzflep 当然,欢迎使用每个选项。
  • @nameless 你需要一种将每个国家与其他国家区分开来的方法;我正在考虑这样的事情:simplemaps.com/resources/svg-world

标签: javascript jquery html css image


【解决方案1】:

这是一个嘈杂的夜晚。所以我拼凑了一些可以玩的东西。这是一个不完整的实现——我只是抓住了图像的一部分,并将其与原始图像一起放入图片中。

这取决于你 (0) 显示大于全尺寸的图像 (1) 将其定位在原件的前面,使用绝对定位和z-index (2) 处理鼠标悬停区域。

我在 cmets 中链接的答案演示了定位、z 排序以及将指针事件传递给底层地图元素。

您需要决定放大版的显示方式/位置。也许你想要它 150%,中心点位于地图元素的中心点之上。

正如 cmets 所指出的,您通常会使用现有图像,而不是像我在这里所做的那样从头开始创建图像。

首先,一个完整的源代码 - 只需将其全部复制并粘贴到一个新的 html 文件中。

<!DOCTYPE html>
<html>
<head>
<script>
"use strict";
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
window.addEventListener('load', onDocLoaded, false);


function onDocLoaded(e)
{
    createImageFromMapAreasForUseAtStackoverflow();
    attachHoverHandlers();
}


// this function is only used for the purpose of answering this question. 
// Ordinarily, you would set the src of #img1 to be that of your image. - since cross-origin contamination prevents
//  access to the image data if the image comes from a different server, I've just opted to create the image from scratch.
//
// the original image may be found here: https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Australia_location_map_recolored.png/500px-Australia_location_map_recolored.png
function createImageFromMapAreasForUseAtStackoverflow()
{
    var imgWidth = 500, imgHeight = 360;
    var can = newEl('canvas');
    can.width = imgWidth;
    can.height = imgHeight;
    var ctx = can.getContext('2d');
    ctx.fillStyle = '#ecf5f6';
    ctx.fillRect(0,0,imgWidth,imgHeight);

    var areas = document.querySelectorAll('area');
    var i, n = areas.length;
    var colours = ['#f4f100', '#b2d025', '#f67c60', '#8fbce1', '#f4f236', '#fca164', '#bc9eee', 'black'];

    for (i=0; i<n; i++)
    {
        var areaType = areas[i].getAttribute('shape');
        if ((areaType == 'polygon') || (areaType == 'poly'))
            drawMapPoly(areas[i].getAttribute('coords'), colours[i]);
    }
    byId('img1').src = can.toDataURL();

    function drawMapPoly(coordStr, colour)
    {
        ctx.beginPath();
        var ptArray = coordStrToPointArray(coordStr);
        var i, n=ptArray.length;
        ctx.moveTo(ptArray[0].x, ptArray[0].y);
        for (i=0;i<n;i++)
            ctx.lineTo(ptArray[i].x,ptArray[i].y);
        ctx.closePath();
        ctx.fillStyle=colour;
        ctx.fill();
    }
}

function attachHoverHandlers()
{
    //
    // Attach event-listeners to each of the areas in the image-map to handle mouseover
    //
    var areas = document.querySelectorAll('area');
    var i, n = areas.length;
    for (i=0; i<n; i++)
    {
        areas[i].addEventListener('mouseover', onAreaHovered, false);
    }
}

function onAreaHovered(e)
{
    var hoveredElement = this;
    var coordStr = this.getAttribute('coords');
    var areaType = this.getAttribute('shape');

    switch (areaType)
    {
        case 'polygon':
        case 'poly':
            showPolyCoords(coordStr);
            break;

        default:
            alert("You need to add a handler for areas of type '" + areaType + "'");
    }
}

function coordStrToPointArray(coordStr)
{
    var mCoords = coordStr.split(',');
    var i, n = mCoords.length;
    var coordArray = [];

    coordArray.push( new p2d(mCoords[0], mCoords[1]) );
    for (i=2; i<n; i+=2)
    {
        coordArray.push( new p2d(mCoords[i], mCoords[i+1]) );
    }
    coordArray.push( new p2d(mCoords[0], mCoords[1]) );
    return coordArray;
}


// takes a string that contains coords eg - "227,307,261,309, 339,354, 328,371, 240,331"
// draws a line from each co-ord pair to the next - assumes starting point needs to be repeated as ending point.
function showPolyCoords(coOrdStr)
{
    var coordArray = coordStrToPointArray(coOrdStr);

    var sortedArray = coordArray.slice();
    sortedArray.sort(sortX);
    var minX = sortedArray[0].x;
    var maxX = sortedArray[sortedArray.length-1].x;

    sortedArray.sort(sortY);
    var minY = sortedArray[0].y;
    var maxY = sortedArray[sortedArray.length-1].y;

    var topLeft = new p2d(minX, minY);
    var botRight = new p2d(maxX, maxY);

    testFuncWithClipping(topLeft, botRight, 'img1', coordArray);
}

function p2d(x, y)
{
    this.x = Number(x);
    this.y = Number(y);
    return this;
}

// unneccesary - just makes displaying the point easier. 
// Having this prototype available means that (in chrome at least) 
// the code: "console.log( pt2d );" or "alert( pt2d );"  will result in "<xCoord, yCoord>" being printed/alerted
p2d.prototype.toString = function()
{
    return "<"+this.x+", "+this.y+">";
}

// comparison functions used when sorting the point list to obtain the min/max values of both X and Y
function sortX(a, b){return a.x - b.x;}
function sortY(a, b){return a.y - b.y;}

function testFuncWithClipping(topLeft, botRight, srcImgId, pointArray)
{
    var width = botRight.x - topLeft.x;
    var height = botRight.y - topLeft.y;

    var can = newEl('canvas');
    can.width = width;
    can.height = height;
    var ctx = can.getContext('2d');
    var img = byId(srcImgId);

    ctx.beginPath();
    ctx.moveTo( pointArray[0].x - topLeft.x, pointArray[0].y-topLeft.y );
    var i, n = pointArray.length;
    for (i=0; i<n; i++)
    {
        ctx.lineTo( pointArray[i].x - topLeft.x, pointArray[i].y-topLeft.y );
    }
    ctx.clip();

    ctx.drawImage(img, topLeft.x, topLeft.y, width, height, 0,0, width,height);
    byId('img2').src = can.toDataURL();
}
</script>
<style>
body
{
    background-color: gray;
}

#canvas2
{
    pointer-events: none;       /* make the canvas transparent to the mouse - needed since canvas is position infront of image */
    position: absolute;         /* you'll need to use this trick to allow the area to know when the mouse leaves it, so you can hide/destroy the */
}                               /* enlarged version of the hovered area */
</style>
</head>
<body>
    <!-- 
        Usually, you would use this element.
        For the purpose of making a working demo, I've used the next element and have created the picture using the map data and
        the funtion createImageFromMapAreasForUseAtStackoverflow
        As mentioned above, the original image is:
            https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Australia_location_map_recolored.png/500px-Australia_location_map_recolored.png
        which was saved as ausMap.png in the img folder of my localhost.

        <img id='img1' usemap='#imgMap1' src='img/ausMap.png' /> 

    -->
    <img id='img1' usemap='#imgMap1'/>

    <map name='imgMap1' id='imgMap1'>
        <area shape="polygon" coords="359, 324, 373, 332, 392, 327, 393, 346, 375, 356, 364, 343" title="Tasmania">
        <area shape="polygon" coords="325, 258, 335, 258, 339, 265, 346, 265, 347, 271, 357, 279, 360, 279, 361, 276, 368, 278, 380, 279, 388, 277, 390, 288, 406, 293, 405, 296, 391, 297, 377, 308, 376, 310, 372, 308, 356, 303, 350, 307, 338, 303, 332, 301, 325, 300" title="Victoria">
        <area shape="polygon" coords="325, 207, 397, 206, 403, 201, 417, 203, 420, 209, 425, 205, 427, 200, 440, 197, 440, 204, 436, 222, 432, 235, 432, 241, 424, 245, 419, 255, 415, 267, 408, 277, 408, 293, 391, 286, 389, 278, 381, 279, 373, 279, 364, 276, 361, 278, 348, 271, 346, 265, 340, 266, 338, 261, 333, 258, 325, 258" title="New South Wales">
        <area shape="polygon" coords="325, 206, 398, 207, 404, 201, 417, 203, 423, 207, 427, 201, 434, 198, 441, 198, 436, 169, 429, 159, 419, 150, 416, 142, 410, 138, 406, 139, 400, 121, 397, 114, 375, 101, 372, 82, 367, 79, 365, 61, 357, 52, 353, 56, 349, 39, 345, 31, 341, 22, 336, 18, 336, 27, 330, 35, 334, 38, 331, 41, 330, 46, 331, 60, 329, 66, 329, 74, 326, 77, 326, 85, 320, 90, 312, 88, 308, 82, 298, 78, 298, 175, 325, 175" title="Queensland">
        <area shape="polygon" coords="297, 175, 297, 79, 273, 60, 286, 34, 281, 30, 276, 33, 268, 33, 254, 29, 250, 25, 247, 22, 243, 22, 244, 26, 249, 31, 246, 33, 237, 33, 235, 33, 234, 31, 236, 28, 236, 23, 231, 25, 228, 24, 225, 27, 230, 29, 233, 33, 229, 37, 223, 41, 227, 45, 222, 46, 218, 54, 221, 62, 214, 63, 213, 175" title="Northern Territory">
        <area shape="polygon" coords="214, 234, 214, 61, 211, 60, 205, 65, 205, 59, 197, 50, 194, 54, 190, 52, 187, 56, 180, 56, 179, 62, 174, 64, 174, 75, 163, 74, 167, 81, 164, 85, 160, 77, 150, 82, 149, 90, 153, 93, 148, 97, 143, 108, 127, 114, 122, 113, 121, 115, 111, 120, 103, 118, 94, 125, 90, 130, 85, 132, 80, 138, 78, 136, 80, 131, 73, 138, 75, 144, 75, 148, 72, 150, 72, 160, 75, 170, 78, 176, 75, 179, 69, 172, 76, 187, 76, 193, 82, 200, 82, 205, 84, 210, 84, 218, 91, 234, 92, 241, 93, 250, 90, 253, 86, 253, 86, 261, 92, 262, 99, 269, 112, 269, 125, 263, 132, 256, 145, 254, 164, 257, 168, 248, 172, 248, 186, 241, 192, 242" title="Western Australia">
        <area shape="polygon" coords="324, 299, 323, 175, 213, 175, 213, 234, 233, 232, 242, 238, 249, 236, 261, 242, 258, 246, 265, 249, 269, 256, 270, 261, 272, 263, 277, 267, 277, 261, 281, 257, 288, 254, 291, 249, 295, 246, 295, 243, 297, 250, 294, 254, 290, 259, 291, 265, 287, 269, 294, 268, 297, 262, 301, 268, 299, 272, 295, 275, 290, 273, 285, 274, 283, 277, 290, 278, 294, 275, 301, 273, 315, 286, 314, 291" title="South Australia">
        <area shape='rect' coords='0,0,100,100' title='unsupported area type'>
    </map>
    <img id='img2'/>
</body>
</html>

接下来是一个工作演示,您可以在页面的此处尝试(使用全屏以获得最佳体验)

"use strict";
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
window.addEventListener('load', onDocLoaded, false);


function onDocLoaded(e)
{
	createImageFromMapAreasForUseAtStackoverflow();
	attachHoverHandlers();
}


// this function is only used for the purpose of answering this question. 
// Ordinarily, you would set the src of #img1 to be that of your image. - since cross-origin contamination prevents
//  access to the image data if the image comes from a different server, I've just opted to create the image from scratch.
//
// the original image may be found here: https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Australia_location_map_recolored.png/500px-Australia_location_map_recolored.png
function createImageFromMapAreasForUseAtStackoverflow()
{
	var imgWidth = 500, imgHeight = 360;
	var can = newEl('canvas');
	can.width = imgWidth;
	can.height = imgHeight;
	var ctx = can.getContext('2d');
	ctx.fillStyle = '#ecf5f6';
	ctx.fillRect(0,0,imgWidth,imgHeight);
	
	var areas = document.querySelectorAll('area');
	var i, n = areas.length;
	var colours = ['#f4f100', '#b2d025', '#f67c60', '#8fbce1', '#f4f236', '#fca164', '#bc9eee', 'black'];
	
	for (i=0; i<n; i++)
	{
		var areaType = areas[i].getAttribute('shape');
		if ((areaType == 'polygon') || (areaType == 'poly'))
			drawMapPoly(areas[i].getAttribute('coords'), colours[i]);
	}
	byId('img1').src = can.toDataURL();

	function drawMapPoly(coordStr, colour)
	{
		ctx.beginPath();
		var ptArray = coordStrToPointArray(coordStr);
		var i, n=ptArray.length;
		ctx.moveTo(ptArray[0].x, ptArray[0].y);
		for (i=0;i<n;i++)
			ctx.lineTo(ptArray[i].x,ptArray[i].y);
		ctx.closePath();
		ctx.fillStyle=colour;
		ctx.fill();
	}
}

function attachHoverHandlers()
{
	//
	// Attach event-listeners to each of the areas in the image-map to handle mouseover
	//
	var areas = document.querySelectorAll('area');
	var i, n = areas.length;
	for (i=0; i<n; i++)
	{
		areas[i].addEventListener('mouseover', onAreaHovered, false);
	}
}

function onAreaHovered(e)
{
	var hoveredElement = this;
	var coordStr = this.getAttribute('coords');
	var areaType = this.getAttribute('shape');

	switch (areaType)
	{
		case 'polygon':
		case 'poly':
			showPolyCoords(coordStr);
			break;

		default:
			alert("You need to add a handler for areas of type '" + areaType + "'");
	}
}

function coordStrToPointArray(coordStr)
{
	var mCoords = coordStr.split(',');
	var i, n = mCoords.length;
	var coordArray = [];
	
	coordArray.push( new p2d(mCoords[0], mCoords[1]) );
	for (i=2; i<n; i+=2)
	{
		coordArray.push( new p2d(mCoords[i], mCoords[i+1]) );
	}
	coordArray.push( new p2d(mCoords[0], mCoords[1]) );
	return coordArray;
}


// takes a string that contains coords eg - "227,307,261,309, 339,354, 328,371, 240,331"
// draws a line from each co-ord pair to the next - assumes starting point needs to be repeated as ending point.
function showPolyCoords(coOrdStr)
{
	var coordArray = coordStrToPointArray(coOrdStr);
	
	var sortedArray = coordArray.slice();
	sortedArray.sort(sortX);
	var minX = sortedArray[0].x;
	var maxX = sortedArray[sortedArray.length-1].x;

	sortedArray.sort(sortY);
	var minY = sortedArray[0].y;
	var maxY = sortedArray[sortedArray.length-1].y;
	
	var topLeft = new p2d(minX, minY);
	var botRight = new p2d(maxX, maxY);

	testFuncWithClipping(topLeft, botRight, 'img1', coordArray);
}

function p2d(x, y)
{
	this.x = Number(x);
	this.y = Number(y);
	return this;
}

// unneccesary - just makes displaying the point easier. 
// Having this prototype available means that (in chrome at least) 
// the code: "console.log( pt2d );" or "alert( pt2d );"  will result in "<xCoord, yCoord>" being printed/alerted
p2d.prototype.toString = function()
{
	return "<"+this.x+", "+this.y+">";
}

// comparison functions used when sorting the point list to obtain the min/max values of both X and Y
function sortX(a, b){return a.x - b.x;}
function sortY(a, b){return a.y - b.y;}

function testFuncWithClipping(topLeft, botRight, srcImgId, pointArray)
{
	var width = botRight.x - topLeft.x;
	var height = botRight.y - topLeft.y;
	
	var can = newEl('canvas');
	can.width = width;
	can.height = height;
	var ctx = can.getContext('2d');
	var img = byId(srcImgId);
	
	ctx.beginPath();
	ctx.moveTo( pointArray[0].x - topLeft.x, pointArray[0].y-topLeft.y );
	var i, n = pointArray.length;
	for (i=0; i<n; i++)
	{
		ctx.lineTo( pointArray[i].x - topLeft.x, pointArray[i].y-topLeft.y );
	}

	// comment the below line to see the effect of drawing a rectangular
	// portion of the image without clipping.
	ctx.clip();
	
	ctx.drawImage(img, topLeft.x, topLeft.y, width, height, 0,0, width,height);
	byId('img2').src = can.toDataURL();
}
body
{
	background-color: gray;
}

#canvas2
{
	pointer-events: none;		/* make the canvas transparent to the mouse - needed since canvas is position infront of image */
	position: absolute;			/* you'll need to use this trick to allow the area to know when the mouse leaves it, so you can hide/destroy the */
}								/* enlarged version of the hovered area */
	<!-- 
		Usually, you would use this element.
		For the purpose of making a working demo, I've used the next element and have created the picture using the map data and
		the funtion createImageFromMapAreasForUseAtStackoverflow
		As mentioned above, the original image is:
			https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Australia_location_map_recolored.png/500px-Australia_location_map_recolored.png
		which was saved as ausMap.png in the img folder of my localhost.
		
		<img id='img1' usemap='#imgMap1' src='img/ausMap.png' /> 
		
	-->
	<img id='img1' usemap='#imgMap1'/>
	
	<map name='imgMap1' id='imgMap1'>
		<area shape="polygon" coords="359, 324, 373, 332, 392, 327, 393, 346, 375, 356, 364, 343" title="Tasmania">
		<area shape="polygon" coords="325, 258, 335, 258, 339, 265, 346, 265, 347, 271, 357, 279, 360, 279, 361, 276, 368, 278, 380, 279, 388, 277, 390, 288, 406, 293, 405, 296, 391, 297, 377, 308, 376, 310, 372, 308, 356, 303, 350, 307, 338, 303, 332, 301, 325, 300" title="Victoria">
		<area shape="polygon" coords="325, 207, 397, 206, 403, 201, 417, 203, 420, 209, 425, 205, 427, 200, 440, 197, 440, 204, 436, 222, 432, 235, 432, 241, 424, 245, 419, 255, 415, 267, 408, 277, 408, 293, 391, 286, 389, 278, 381, 279, 373, 279, 364, 276, 361, 278, 348, 271, 346, 265, 340, 266, 338, 261, 333, 258, 325, 258" title="New South Wales">
		<area shape="polygon" coords="325, 206, 398, 207, 404, 201, 417, 203, 423, 207, 427, 201, 434, 198, 441, 198, 436, 169, 429, 159, 419, 150, 416, 142, 410, 138, 406, 139, 400, 121, 397, 114, 375, 101, 372, 82, 367, 79, 365, 61, 357, 52, 353, 56, 349, 39, 345, 31, 341, 22, 336, 18, 336, 27, 330, 35, 334, 38, 331, 41, 330, 46, 331, 60, 329, 66, 329, 74, 326, 77, 326, 85, 320, 90, 312, 88, 308, 82, 298, 78, 298, 175, 325, 175" title="Queensland">
		<area shape="polygon" coords="297, 175, 297, 79, 273, 60, 286, 34, 281, 30, 276, 33, 268, 33, 254, 29, 250, 25, 247, 22, 243, 22, 244, 26, 249, 31, 246, 33, 237, 33, 235, 33, 234, 31, 236, 28, 236, 23, 231, 25, 228, 24, 225, 27, 230, 29, 233, 33, 229, 37, 223, 41, 227, 45, 222, 46, 218, 54, 221, 62, 214, 63, 213, 175" title="Northern Territory">
		<area shape="polygon" coords="214, 234, 214, 61, 211, 60, 205, 65, 205, 59, 197, 50, 194, 54, 190, 52, 187, 56, 180, 56, 179, 62, 174, 64, 174, 75, 163, 74, 167, 81, 164, 85, 160, 77, 150, 82, 149, 90, 153, 93, 148, 97, 143, 108, 127, 114, 122, 113, 121, 115, 111, 120, 103, 118, 94, 125, 90, 130, 85, 132, 80, 138, 78, 136, 80, 131, 73, 138, 75, 144, 75, 148, 72, 150, 72, 160, 75, 170, 78, 176, 75, 179, 69, 172, 76, 187, 76, 193, 82, 200, 82, 205, 84, 210, 84, 218, 91, 234, 92, 241, 93, 250, 90, 253, 86, 253, 86, 261, 92, 262, 99, 269, 112, 269, 125, 263, 132, 256, 145, 254, 164, 257, 168, 248, 172, 248, 186, 241, 192, 242" title="Western Australia">
		<area shape="polygon" coords="324, 299, 323, 175, 213, 175, 213, 234, 233, 232, 242, 238, 249, 236, 261, 242, 258, 246, 265, 249, 269, 256, 270, 261, 272, 263, 277, 267, 277, 261, 281, 257, 288, 254, 291, 249, 295, 246, 295, 243, 297, 250, 294, 254, 290, 259, 291, 265, 287, 269, 294, 268, 297, 262, 301, 268, 299, 272, 295, 275, 290, 273, 285, 274, 283, 277, 290, 278, 294, 275, 301, 273, 315, 286, 314, 291" title="South Australia">
		<area shape='rect' coords='0,0,100,100' title='unsupported area type'>
	</map>
	<img id='img2'/>

玩得开心!

【讨论】:

  • 哇,非常感谢。我会尽快对其进行精确的查看,并尝试在其他需要的东西中实现它(0,1,2),如果一切正常,然后再回复你。但是感谢您的努力!
  • 一个直接的问题,扫码:colours 数组是干什么用的?只是为复制的多边形着色?因为我的地图总是有相同的颜色。我可以简单地向数组中添加一种颜色,或者需要什么?
  • 还有一件事(只是快速尝试了一下,它通常也适用于我的页面,这太棒了)。使用正常的scale-函数,多边形会变得模糊,这不是我想要的......实际上,正如 cmets 中已经提到的:图像真的很大(宽度为 6400 像素),但它只是以大约 1000 像素的宽度显示,所以我将有足够的图像尺寸来显示它的 6 倍大并且仍然清晰......但是当然,我从较小的版本中切割了多边形......有没有办法切割完全不显示的图像中的多边形? (何时提供区域?)
  • @nameless - 我想这样做,你只需要知道显示尺寸和实际尺寸之间的精确比例。一旦知道这一点,您就可以将多边形坐标缩放相同的数量,然后它们对于较大的图像将是准确的。当然,较大的源图像不必在页面上可见。 :) 您可以做的另一件事是设置上下文的 . imageSmoothingEnabled 标志(上下文是 canvas.getContext('2d') 调用的结果)将其设置为 true 将有助于使事情变得顺利一点,但是您的想法抓取全尺寸图像的一部分几乎肯定会更好
  • 哦,抱歉,我错过了您关于颜色的问题 - 是的。颜色数组仅用于从头开始创建图像。我只是在我下载的 png 图像中的状态上使用了吸管。您根本不需要使用该数组。这就是为什么我把它放在绘图函数中,createImageFromMapAreasForUseAtStackoverflow
【解决方案2】:

老实说,我会让每个国家/地区都有自己的形象,并在悬停时重新调整它。 JS/jQuery/CSS 对图像效果的支持不是很好,如果你不想要多个图像,那么我会开始寻找一个库来为你做这件事。

例如,您可能要考虑使用Pieces

另一种可能性是使用canvassvg 绘制地图并使用JS 使其具有交互性。

【讨论】:

    猜你喜欢
    • 2018-09-08
    • 2011-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多