Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
$(document).ready(function() {
window.imagemap = window.imagemap || {};
var i18n,
// Added to all elements created by the script that should be removed when recreating
artifactClass = 'imageMapHighlighterArtifact',
// Default "2d context" attributes used for highlighting
// Some can be overridden by data or CSS attributes, see handleOneMap(), which will always take precedence
defaultAreaHighlight = {
fillStyle: window.imagemap.hightlightfill || 'rgba(0, 0, 0, 0.35)',
strokeStyle: window.imagemap.hightlightcolor || '#FFC500',
lineJoin: 'round',
lineWidth: window.imagemap.hightlightstrokewidth || 2
};
// Convert ImageMap area(s) to highlights on the canvas
function drawMarker(context, areas) {
function drawPoly(coords) {
context.moveTo(coords.shift(), coords.shift());
while (coords.length)
context.lineTo(coords.shift(), coords.shift());
}
for (var i in areas) {
var coords = areas[i].coords.split(',');
context.beginPath();
switch (areas[i].shape) {
case 'rect': drawPoly([coords[0], coords[1], coords[0], coords[3], coords[2], coords[3], coords[2], coords[1]]); break;
case 'circle': context.arc(coords[0], coords[1], coords[2], 0, Math.PI * 2); break; //x,y,r,startAngle,endAngle
case 'poly': drawPoly(coords); break;
}
context.closePath();
context.stroke();
context.fill();
}
}
// Add highlighting on hover
function mouseAction(e) {
var $this = $(this),
activate = e.type == 'mouseover',
caption = $this.text(),
ol = $this.parent(),
context = ol.data('context'),
areaHighlight = ol.data('area-highlight'); // Contains styles for the highlights
$this.toggleClass('liHighlighting', activate); // Mark/unmark the list item
context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clear previous highlights from the canvas
ol.find('li').each(function() {
var $li = $(this);
var licap = $li.text();
if (activate && licap === caption) { // Highlight!!!
$.extend(context, areaHighlight || defaultAreaHighlight);
drawMarker(context, $li.data('areas'));
}
});
}
// Add highlighting to an ImageMap
function handleOneMap() {
var img = $(this),
w = img.width(),
h = img.height(),
dims = { position: 'absolute', width: w + 'px', height: h + 'px', border: 0, top: 0, left: 0 },
parentMarker = img.closest('.imageMapHighlighter'),
liClasses = parentMarker.data('list-classes'),
map = img.siblings('map:first'),
defaultLink = false;
if (!map.length) { // for maps with "default" link, img is inside <a>
map = img.parent().siblings('map:first');
defaultLink = true;
}
if (!$('area', map).length) return; // not an ImageMap, or map with 0 areas
img.addClass('highlighted');
// remove hardcoded height/width that makes legend overflow (might break something?)
$(img).closest('div.noresize').css({ height: '', width: '' });
var jcanvas = $('<canvas>', { 'class': artifactClass })
.css(dims)
.attr({ width: w, height: h });
var bgimg = $('<img>', { 'class': artifactClass, src: img.attr('src') })
.css(dims); // completely inert the original image
// Extend area highlighting with data attributes
var areaHighlight = $.extend({}, defaultAreaHighlight, {
fillStyle: parentMarker.data('fill') || parentMarker.css('--imagemaphighlight-fill'),
strokeStyle: parentMarker.data('stroke') || parentMarker.css('--imagemaphighlight-stroke'),
lineWidth: parentMarker.data('line-width') || parentMarker.css('--imagemaphighlight-stroke-width')
});
var context = $.extend(jcanvas[0].getContext('2d'), areaHighlight);
// This is where the magic is done: prepare a sandwich of the inert bgimg at the bottom,
// the canvas above it, and the original image on top,
// so canvas won't steal the mouse events.
// Pack them all TIGHTLY in a newly minted "relative" div, so when page changes
// (other scripts adding elements, window resize etc.), canvas and imagese remain aligned.
var div = $('<div>').css({ position: 'relative', width: w + 'px', height: h + 'px' });
(defaultLink ? img.parent() : img).before(div); // put the div just above the image
div.append(bgimg) // place the background image in the div
.append(jcanvas) // and the canvas. both are "absolute", so they don't occupy space in the div
.append(defaultLink ? img.parent() : img); // now yank the original image from the window and place it on the div.
img.fadeTo(1, 0); // make the image transparent - we see canvas and bgimg through it, but it still creates the mouse events
var ol = $('<ol>', { 'class': artifactClass })
.css({ maxWidth: w + 'px' })
.attr({ 'data-expandtext': i18n.msg('show').plain(), 'data-collapsetext': i18n.msg('hide').plain() })
.data('area-highlight', areaHighlight)
.data('context', context);
if (![0, false, 'no'].includes(parentMarker.data('legend'))) { // data-legend
mw.loader.using('jquery.makeCollapsible').then(function() {
ol.addClass('mw-collapsed').makeCollapsible();
});
} else {
ol.css('display', 'none');
}
div.after(ol);
var lis = {}; // collapse areas with same caption to one list item
var someli; // select arbitrary one
$('area', map).each(function() {
var text = this.title;
var li = lis[text]; // title already met? use the same li
if (!li) { // no? create a new one.
var href = this.href;
lis[text] = li = $('<li>', { 'class': artifactClass })
.append($('<a>', { href: href }).html(mw.html.escape(this.title)))
.on('mouseover mouseout', mouseAction)
.data('areas', [])
.addClass(liClasses && (liClasses[text] || liClasses['default']))
.appendTo(ol);
}
li.data('areas').push(this); // add the area to the li
someli = li; // whichever - we just want one...
$(this).on('mouseover mouseout', function(e) {
li.trigger(e.type);
});
});
if (someli) someli.trigger('mouseout');
}
function init(i18no) {
i18n = i18no;
mw.util.addCSS(
'ol.' + artifactClass + ' { columns: 2; margin: 0; list-style: none; z-index: 500; }' + // css for main ol element
'li.' + artifactClass + ' { white-space: nowrap; border: solid 1px transparent; border-radius: 6px; }' + // css for li elements
'li.' + artifactClass + '.liHighlighting { background-color: var(--imagemaphighlight-legend-highlight, rgba(var(--theme-link-color--rgb, 255,255,0), 0.1)); }' + // css for highlighted li element
// hack for centering Legend toggle
'#content ol.' + artifactClass + ' { position: relative; margin-top: 1.5em; margin-left: unset; }' +
'#content ol.' + artifactClass + ' li.mw-collapsible-toggle-li { position: absolute; inset: -1.5em 0 auto; }'
);
var selector = '.imageMapHighlighter img[usemap]:not(.highlighted)';
$(selector).each(handleOneMap);
// Highlight maps added later on
mw.hook('wikipage.content').add(function($content) {
$content.find(selector).each(handleOneMap);
});
}
mw.hook('dev.i18n').add(function(i18no) {
// Only run if there is at least one marker div and the browser supports canvas
if ($('.imageMapHighlighter').length && !!window.CanvasRenderingContext2D) {
$.when(
i18no.loadMessages('ImageMapHighlight'),
mw.loader.using(['jquery.makeCollapsible', 'mediawiki.util'])
).then(init);
}
});
if (!window.dev || !window.dev.i18n) {
importArticle({
type: 'script',
article: 'u:dev:MediaWiki:I18n-js/code.js'
});
}
});