更簡單;我的應用情境單純,都用矩形就夠(萬一要不同形狀再說),至於設定點擊區域大小及位置。
我懶得 Survey 支援以上構想的現成軟體或工具,心想反正是 Github Copilot 擅長的 HTML/CSS/JavaScript 領域,自己寫也不會太費力。就醬,一個好用小工具誕生了。

直接看展示。
為圖檔加上點擊熱區
操作方式很簡單,提供一個圖檔連結當成底圖,在要設點擊熱區的地方按 Ctrl 點滑鼠左鍵,拉出所需要的大小。左方清單可查看已設定的熱區,透過數字欄位可微調位置跟大小,為每個熱區指定好連結或 JavaScript 指令,按匯出可得到一個可操作的示範網頁,再整合到網站專案就大功告成囉~
完整程式碼如下,有需要的同學請自取修改應用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Map</title>
<style>
.rectangle {
border: 3px dashed red;
position: absolute;
pointer-events: none;
}
#app {
display: flex;
flex-direction: row;
[lang] {
display: none;
}
.zh {
[lang='zh'] {
display: inline;
}
}
.en {
[lang='en'] {
display: inline;
}
}
.menu {
width: 350px;
flex-shrink: 0;
background-color: #f0f0f0;
padding: 8px;
>div {
margin-bottom: 8px;
}
.actions {
a {
cursor: pointer;
text-decoration: underline;
color: blue;
margin-right: 12px;
}
}
}
.canvas {
flex-grow: 1;
.hotspot.active {
border: 2pt solid red;
}
}
}
.editor {
height: 135px;
background-color: #ddd;
padding: 6px;
[type=number] {
width: 60px;
}
label {
cursor: pointer;
}
table {
width: 100%;
border-collapse: collapse;
}
td {
border: 1px solid #888;
&.hdr {
width: 50px;
text-align: right;
background-color: #ccc;
}
padding: 3px;
}
}
.list {
.item {
padding: 6px 12px;
background-color: 1px solid #444;
border: 1pt solid #ddd;
color: #666;
&.active {
color: brown;
font-weight: bold;
}
}
}
</style>
<style id="hotspot-style">
.canvas {
width: 100%;
overflow: hidden;
position: relative;
img {
width: 100%;
/* visibility: hidden; */
pointer-events: none;
}
.link {
position: absolute;
}
}
.hotspot {
position: absolute;
pointer-events: all;
cursor: pointer;
&:hover {
border-color: purple;
background-color: cyan;
opacity: 0.25;
}
}
</style>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<div class="menu" :class="{ zh: lang == 'zh', en: lang == 'en'}">
<div class="actions">
<span style="float: right">
🌎<a @click="lang='en'" v-show="lang=='zh'">En</a><a @click="lang='zh'" v-show="lang=='en'">中</a>
</span>
<button @click="exportHtml">
<span lang="en">Export HTML</span>
<span lang="zh">匯出 HTML 檔</span>
</button>
</div>
<div class="editor">
<table v-if="selectedHotspot">
<tr>
<td class="hdr">
<span lang="en">Link</span>
<span lang="zh">連結</span>
</td>
<td colspan="3">
<input type="text" v-model="selectedHotspot.link" placeholder="Link">
<button @click="hotspots.splice(hotspots.indexOf(selectedHotspot), 1)">
<span lang="en">Delete</span>
<span lang="zh">刪除</span>
</button>
</td>
</tr>
<tr>
<td class="hdr">
<span lang="en">Options</span>
<span lang="zh"> 選項</span>
</td>
<td colspan="3">
<label><input type="checkbox" v-model="selectedHotspot.js">JavaScript</label>
<label><input type="checkbox" v-model="selectedHotspot.ext">
<span lang="en">New window</span>
<span lang="zh">新視窗開啟</span>
</label>
</td>
</tr>
<tr>
<td class="hdr">X</td>
<td><input type="number" v-model="selectedHotspot.x" step="0.1"> %</td>
<td class="hdr">Y</td>
<td><input type="number" v-model="selectedHotspot.y" step="0.1"> %</td>
</tr>
<tr>
<td class="hdr">W</td>
<td><input type="number" v-model="selectedHotspot.width" step="0.1"> %</td>
<td class="hdr">H</td>
<td><input type="number" v-model="selectedHotspot.height" step="0.1"> %</td>
</tr>
</table>
</div>
<div class="list">
<span lang="en">Image:</span>
<span lang="zh">圖檔:</span>
<input v-model.lazy="imgPath" type="text" placeholder="Image path" style="width:250px">
<div class="item" v-for="(hotspot,idx) in hotspots" :class="{active: hotspot === selectedHotspot}"
@click="selectedHotspot = hotspot">
<span lang="en">Block</span>
<span lang="zh">區塊</span>
{{idx}}. <span>{{hotspot.link}}</span>
</div>
</div>
</div>
<div class="canvas">
<img :src="imgPath" style="pointer-events: none;" alt="eap" referrerpolicy="no-referrer">
<div v-for="hotspot in hotspots" class="hotspot" :class="{active: hotspot === selectedHotspot}"
@click="selectedHotspot = hotspot" :data-link="hotspot.link" :data-js="hotspot.js"
:data-ext="hotspot.ext"
:style="{left: hotspot.x + '%', top: hotspot.y + '%', width: hotspot.width + '%', height: hotspot.height + '%'}">
</div>
</div>
</div>
<script>
class Hotspot {
constructor(x, y, width, height, link) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.link = link ?? '#';
this.js = false;
this.ext = false;
}
}
const app = Vue.createApp({
data() {
return {
hotspots: [],
selectedHotspot: null,
lang: navigator.language.startsWith('zh') ? 'zh' : 'en',
imgPath: 'https://i.imgur.com/R8yWT5b.png'
}
},
watch: {
imgPath() {
this.hotspots = [];
this.selectedHotspot = null;
}
},
methods: {
exportHtml() {
const styles = document.getElementById('hotspot-style').innerText;
const canvasHtml = document.querySelector('.canvas').outerHTML;
const html = `[html]
[body]
<div><style>${styles}</style>${canvasHtml}</div>
[script]
document.addEventListener('click', (e) => {
if (!e.target.classList.contains('hotspot')) return;
const link = e.target.getAttribute('data-link');
if (!link) return;
const js = e.target.getAttribute('data-js') === 'true';
const ext = e.target.getAttribute('data-ext') === 'true';
if (js) eval(link);
else if (ext) window.open(link);
else location.href = link;
});
[/script]
[/body][/html]`.replace(/\[(\/?)(html|head|script|body)\]/g, '<$1$2>');
const blob = new Blob([html], {
type: 'text/html'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'image-map.html';
a.click();
}
}
})
const vm = app.mount('#app');
</script>
<script>
const canvas = document.querySelector('.canvas');
let startX, startY, rect;
let currRect = null;
canvas.addEventListener('mousedown', (e) => {
if (e.ctrlKey) {
startX = e.offsetX;
startY = e.offsetY;
rect = document.createElement('div');
rect.className = 'rectangle';
rect.style.left = `${startX}px`;
rect.style.top = `${startY}px`;
canvas.appendChild(rect);
currRect = rect;
}
});
canvas.addEventListener('mouseup', (e) => {
if (!currRect) return;
const width = currRect.offsetWidth / canvas.offsetWidth * 100;
const height = currRect.offsetHeight / canvas.offsetHeight * 100;
const left = currRect.offsetLeft / canvas.offsetWidth * 100;
const top = currRect.offsetTop / canvas.offsetHeight * 100;
vm.hotspots.push(new Hotspot(left.toFixed(3), top.toFixed(3), width.toFixed(3), height.toFixed(3)));
vm.selectedHotspot = vm.hotspots[vm.hotspots.length - 1];
currRect.remove();
currRect = null;
});
canvas.addEventListener('mousemove', (e) => {
if (!currRect) return;
const width = e.offsetX - startX;
const height = e.offsetY - startY;
rect.style.width = `${Math.abs(width)}px`;
rect.style.height = `${Math.abs(height)}px`;
rect.style.left = `${Math.min(startX, e.offsetX)}px`;
rect.style.top = `${Math.min(startY, e.offsetY)}px`;
});
</script>
</body>
</html>
For quickly converting images into clickable web pages, consider leveraging a tool to define clickable areas with ease. This method allows for a simple and fast implementation, especially for rectangular regions, enabling quick integration of interactive elements.
Comments
Be the first to post a comment