With all the recent advances in JavaScript/HTML5 it would be nice to think there would be a more modern way of implementing an image map. I know you can set a map property for the tag but I don't think this supports nicely formatted tooltips on rollover (near the region). My requirement are really just tooltips, onClick/doubleClick actions on a region within an image.
Last questions on here about similar things I found were from 5 years ago.
Ideally I'd like to use pure js/html/css and not use JQuery plugins etc although I see there are a few available.
Responsive SVG solution or classic image map
After taking Paulie_D's comment about SVG into account, I wrote an alternative using SVG to the classic image map. Both work fine, but the SVG version clearly wins when it comes to responsiveness. Both versions have a connection between the anchors and the respective tooltip using the href-attribute. Both solutions work with vanilla JavaScript, without an extra library.
SVG version
Advantages
responsive
tooltips can be placed easily using JavaScript
HTML
<svg id="map" version="1.1" viewBox="0 0 300 300">
<image width="300" height="300" xlink:href="http://placehold.it/300"/>
<a xlink:href="#t_1">
<rect x="50" y="50" width="50" height="50" />
</a>
<a xlink:href="#t_2">
<rect x="150" y="150" width="50" height="50" />
</a>
</svg>
<div class="t" id="t_1">Tooltip 1</div>
<div class="t" id="t_2">Tooltip 2</div>
CSS
html, body {
width: 100%;
margin: 0;
padding: 0;
}
svg {
display: block;
width: 80%;
max-width: 300px;
margin: 0 auto;
}
svg rect {
fill: white;
opacity: 0.1;
transition: all 0.2s linear;
}
svg rect:hover {
opacity: 0.8;
}
.t {
opacity: 0;
position: absolute;
left: 0;
top: 0;
transition: opacity 0.4s linear;
}
.t.active {
opacity: 1;
}
JavaScript*
var map = document.getElementById('map');
var areas = map.getElementsByTagName('a');
var offset = { left: 30, top: 70 };
for (var i = 0; i < areas.length; i++) {
areas[i].onmouseover = function() {
// get child element
var c = this.firstElementChild;
// get tooltip
var t = document.getElementById(this.getAttribute('xlink:href').substr(1));
// set styles
t.style.left = (map.offsetLeft + parseInt(c.getAttribute('x')) + offset.left) + 'px';
t.style.top = (map.offsetTop + parseInt(c.getAttribute('y')) + offset.top) + 'px';
// show it
t.classList.toggle('active');
}
areas[i].onmouseout = function() {
// get tooltip
var t = document.getElementById(this.getAttribute('xlink:href').substr(1));
// hide it
t.classList.toggle('active');
}
}
Notes
the positioning could be improved, it's just to show a direction
Demo
Try before buy
Classic image map version
HTML
<img src="http://placehold.it/300" alt="" usemap="#map">
<map id="map" name="map">
<area shape="rect" coords="0,0,50,50" href="#t_1" alt="Tip 1" data-left="80px" data-top="80px" />
<area shape="rect" coords="100,100,150,150" href="#t_2" alt="Tip 2" data-left="180px" data-top="180px" />
</map>
<div class="t" id="t_1">Tooltip 1</div>
<div class="t" id="t_2">Tooltip 2</div>
CSS
.t {
opacity: 0;
position: absolute;
left: 0;
top: 0;
transition: opacity 0.4s linear;
}
.t.active {
opacity: 1;
}
JavaScript*
var areas = document.getElementById('map').children;
for (var i = 0; i < areas.length; i++) {
areas[i].onmouseover = function() {
var t = document.getElementById(this.hash.substr(1));
t.style.left = this.dataset.left;
t.style.top = this.dataset.top;
t.classList.toggle('active');
}
areas[i].onmouseout = function() {
var t = document.getElementById(this.hash.substr(1));
t.classList.toggle('active');
}
}
Notes
attaching the position using the data-*-attributes, decouples the JavaScript (unfortunately you can't use offsetLeft/Top and determine the position based on the area-element) - you could however calculate it by using the coords-attribute
the JavaScript code could be improved (for example store tooltips instead of re-query them all the time)
Demo
Try before buy
* In both examples the JavaScript could be improved, e.g. store tooltip elements in a variable instead of re-query them all the time.
Related
I have been struggling for days about this but it seems that I will not solve this on my own. I hope someone can help...or just tell me it is not possible at all and I will find another way :)
Here is a simplified version of my problem:
.left {
fill: yellow;
pointer-events: visible;
}
.left:hover {
opacity: 0.3;
}
.middle {
fill: red;
pointer-events: visible;
}
.middle:hover {
opacity: 0.8;
pointer-events: visible;
}
.right {
fill: blue;
}
.right:hover {
opacity: 0.6;
pointer-events: visible;
}
<svg class="test" width="500px" height="500px">
<g name="Layer" class="group">
<ellipse class="left" cx="120" cy="160" rx="80" ry="81" />
<ellipse class="right" cx="342" cy="271" rx="93" ry="97" />
<ellipse class="middle" cx="223" cy="176" rx="115" ry="153" />
</g>
</svg>
When hovering over an ellipse, its opacity is modified. This is OK.
What I would like to achieve is when hovering over an intersection of two ellipses, the two defined :hover of the concerned ellipses are triggered. Currently, when the mouse pointer is over the red ellipse AND the blue ellipse (in the intersection), only the red one is concerned by the hover.
I cannot group them because:
All 3 ellipses will be considered as hovered all the time
The :hover effect differs
I thought the whole point of 'pointer-events' was to deal with multiple overlapping shapes at a time but I have been trying to use that property in every possible way, without success.
I am using Reactjs so any possible hint on a Javascript solution would help.
I love the solution #Connum came with but I think it can be simplified:
let ellipses = document.querySelectorAll("ellipse")
function getAllElementsFromPoint(rootEl, x, y) {
var item = document.elementFromPoint(x, y);
//in this case is tagName == "ellipse" but you can find something else in commun, like a class - for example.
while (item && item.tagName == "ellipse") {
item.classList.add("hover")
item.style.pointerEvents = "none";
item = document.elementFromPoint(x, y);
}
}
var svg = document.querySelector('svg.test');
svg.addEventListener('mousemove', function(ev) {
// first add pointer-events:all and remove the class .hover from all elements
ellipses.forEach(e=> {
e.style.pointerEvents = "all";
e.classList.remove('hover');
});
// then get all elements at the mouse position
// and add the class "hover" to them
getAllElementsFromPoint(svg, ev.clientX, ev.clientY)
});
.left {
fill: yellow;
}
.left.hover {
opacity: 0.3;
}
.middle {
fill: red;
}
.middle.hover {
opacity: 0.8;
}
.right {
fill: blue;
}
.right.hover {
opacity: 0.6;
}
svg {
border: 1px solid;
}
<svg class="test" width="500px" height="500px">
<g name="Layer" class="group">
<ellipse class="left" cx="120" cy="160" rx="80" ry="81" />
<ellipse class="right" cx="342" cy="271" rx="93" ry="97" />
<ellipse class="middle" cx="223" cy="176" rx="115" ry="153" />
</g>
</svg>
Using getIntersectionList() as demonstrated in this very similar question is probably the cleanest and most performant solution. However, it is not yet supported by Firefox, so I came up with a solution based on a slightly adapted function taken from this answer to another question.
But caution: This is probably very performance-hungry due to the combination of the mousemove event with two forEach loops iterating over DOM elements, combined with the re-rendering that might be caused due to hiding/showing the elements for a minimal amount of time, depending on how the client will handle and optimize this. So this will possibly cause very poor performance on weaker devices. Having said that, it seems to work in all major browsers (tested in Firefox, Chrome and Edge; I haven't tried IE though).
In the comments to the answer in the second link I provided, there's a suggestion for another function using CSS' pointer-events instead of hiding the elements. One would have to compare the performance of those two approaches to decide which one to use best.
function getAllElementsFromPoint(rootEl, x, y) {
var elements = [];
var display = [];
var item = document.elementFromPoint(x, y);
while (item && item !== document.body && item !== window && item !== document && item !== document.documentElement && item !== rootEl) {
elements.push(item);
display.push(item.style.display);
item.style.display = "none";
item = document.elementFromPoint(x, y);
}
// restore display property
for (var i = 0; i < elements.length; i++) {
elements[i].style.display = display[i];
}
return elements;
}
var svg = document.querySelector('svg.test');
svg.addEventListener('mousemove', function(ev) {
// first remove the class .hover from all elements
svg.querySelectorAll('*').forEach(function(subEl) {
subEl.classList.remove('hover');
});
// then get all elements at the mouse position
// and add the class "hover" to them
getAllElementsFromPoint(svg, ev.clientX, ev.clientY).forEach(function(hoveredEl) {
hoveredEl.classList.add('hover');
})
});
.left {
fill: yellow;
pointer-events: visible;
}
.left:hover,
.left.hover {
opacity: 0.3;
}
.middle {
fill: red;
pointer-events: visible;
}
.middle:hover,
.middle.hover {
opacity: 0.8;
pointer-events: visible;
}
.right {
fill: blue;
}
.right:hover,
.right.hover {
opacity: 0.6;
pointer-events: visible;
}
<svg class="test" width="500px" height="500px">
<g name="Layer" class="group">
<ellipse class="left" cx="120" cy="160" rx="80" ry="81" />
<ellipse class="right" cx="342" cy="271" rx="93" ry="97" />
<ellipse class="middle" cx="223" cy="176" rx="115" ry="153" />
</g>
</svg>
I have a php sql query that will generate a lot of images, and I need a code that will overlay a semi transparent image on top of the original image on hover.
I've seen a lot of code to do this with CSS, but that will add a ton of html code that I don't think is needed. The query can return up to like 4000 results with 40x40 images and I need just one overlay image to overlay all of them (only the one hovering) on hover.
So technically, this is what I need
Javascript
find class or id iconoverlay
onhover overlay this transparent image
HTML
<img src="" class or id="iconoverlay" />
I'm currently using JQuery in my site but I'm not familiar with javascript.
If you have a span, a or similar block tag wrapping img. You can do this:
<a class="imgHover" href="#"><img src="" /></a>
<style>
.imgHover { display: inline-block; position: relative;}
.imgHover:after {content:''; width: 100%; height: 100%; background: #000 url('MyPlaceholderURI.jpg') no-repeat center center; display: block; position: absolute; top: 0; left: 0; opacity: 0; transition: opacity .5s linear; }
.imgHover:hover:after {opacity: 1}
</style>
You can see this in action here:
https://codepen.io/fabioarantes89/pen/rwMqNE
here's some code to float a div on hovering over an element:
function createTooltips(elem) {
if (!elem.getAttribute) return;
if (elem.getAttribute('tooltip')) {
$(elem).hover(
function (event) {
$('#tt').html(this.getAttribute('tooltip'));
$('#tt').css('left',(event.pageX + 10) + 'px');
$('#tt').css('top',event.pageY + 'px');
$('#tt').show();
},
function (event) {
$('#tt').hide();
});
}
for (var i = 0; i < elem.childNodes.length; i++) {
createTooltips(elem.childNodes[i], num);
}
}
createTooltips(document.body[0]);
All you need to do then if put your img tags into the "tooltip=" attribute and add to your page
Currently, I am working on a quiz game and in that, for each question, I wish to place a countdown timer. I got some plugins, but I wish if I could create it myself. What I am trying to create looks like the one in the image below.Can you please tell me how I can do it?
Is there a way to assign a border to only up to a specified percentage of the perimeter, so that I could give a border, first in full, and then as each second advances, I can keep decreasing/increasing it so that I would get it in the perfect way.
The timer I wish to create should look somewhat like this (hope you understand how its blue border will increase every second):
Here is something i was playing around with a while ago. It uses a combination of SVG, css transitions and javascript. You should be able to rip it apart and use as a starting point...
/**
* The setTimeout({},0) is a workaround for what appears to be a bug in StackSnippets.
* It should not be required. See JSFiddle version.
*/
setTimeout(function() {
var time = 10; /* how long the timer will run (seconds) */
var initialOffset = '440';
var i = 1
/* Need initial run as interval hasn't yet occured... */
$('.circle_animation').css('stroke-dashoffset', initialOffset-(1*(initialOffset/time)));
var interval = setInterval(function() {
$('h2').text(i);
if (i == time) {
clearInterval(interval);
return;
}
$('.circle_animation').css('stroke-dashoffset', initialOffset-((i+1)*(initialOffset/time)));
i++;
}, 1000);
}, 0)
.item {
position: relative;
float: left;
}
.item h2 {
text-align:center;
position: absolute;
line-height: 125px;
width: 100%;
}
svg {
-webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
}
.circle_animation {
stroke-dasharray: 440; /* this value is the pixel circumference of the circle */
stroke-dashoffset: 440;
transition: all 1s linear;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="item html">
<h2>0</h2>
<svg width="160" height="160" xmlns="http://www.w3.org/2000/svg">
<g>
<title>Layer 1</title>
<circle id="circle" class="circle_animation" r="70" cy="81" cx="81" stroke-width="8" stroke="#6fdb6f" fill="none"/>
</g>
</svg>
</div>
JSFiddle version
you should look at the jquery plugin Knob https://github.com/aterrien/jQuery-Knob, generated canvas circular input, and set timer behavior like :
var time = 0,
maxTime = 60;
$('#dial').knob({
readOnly : true,
thickness : 0.1,
max : maxTime
});
setInterval(function() {
if(time>maxTime) time = 0;
time++;
$('#dial')
.val(time)
.trigger('change');
}, 1000);
I made a codepen here : http://codepen.io/pik_at/pen/azeYRg
I have a series of svg rectangles (using D3.js) and I want to display a message on mouseover, the message should be surrounded by a box that acts as background. They should both be perfectly aligned to each other and to the rectangle (on top and centered). What is the best way to do this?
I tried adding an svg text using the "x", "y", "width" and "height" attributes, and then prepending an svg rect. The problem is that the reference point for the text is in the middle (since I want it centered aligned I used text-anchor: middle), but for the rectangle it's the top left coordinate, plus I wanted a bit of margin around the text which makes it kind of a pain.
The other option was using an html div, which would be nice, because I can add the text and padding directly but I don't know how to get the absolute coordinates for each rectangle. Is there a way to do this?
Can you use simply the SVG <title> element and the default browser rendering it conveys? (Note: this is not the same as the title attribute you can use on div/img/spans in html, it needs to be a child element named title)
rect {
width: 100%;
height: 100%;
fill: #69c;
stroke: #069;
stroke-width: 5px;
opacity: 0.5
}
<p>Mouseover the rect to see the tooltip on supporting browsers.</p>
<svg xmlns="http://www.w3.org/2000/svg">
<rect>
<title>Hello, World!</title>
</rect>
</svg>
Alternatively, if you really want to show HTML in your SVG, you can embed HTML directly:
rect {
width: 100%;
height: 100%;
fill: #69c;
stroke: #069;
stroke-width: 5px;
opacity: 0.5
}
foreignObject {
width: 100%;
}
svg div {
text-align: center;
line-height: 150px;
}
<svg xmlns="http://www.w3.org/2000/svg">
<rect/>
<foreignObject>
<body xmlns="http://www.w3.org/1999/xhtml">
<div>
Hello, <b>World</b>!
</div>
</body>
</foreignObject>
</svg>
…but then you'd need JS to turn the display on and off. As shown above, one way to make the label appear at the right spot is to wrap the rect and HTML in the same <g> that positions them both together.
To use JS to find where an SVG element is on screen, you can use getBoundingClientRect(), e.g. http://phrogz.net/svg/html_location_in_svg_in_html.xhtml
The only good way I found was to use Javascript to move a tooltip <div> around. Obviously this only works if you have SVG inside an HTML document - not standalone. And it requires Javascript.
function showTooltip(evt, text) {
let tooltip = document.getElementById("tooltip");
tooltip.innerHTML = text;
tooltip.style.display = "block";
tooltip.style.left = evt.pageX + 10 + 'px';
tooltip.style.top = evt.pageY + 10 + 'px';
}
function hideTooltip() {
var tooltip = document.getElementById("tooltip");
tooltip.style.display = "none";
}
#tooltip {
background: cornsilk;
border: 1px solid black;
border-radius: 5px;
padding: 5px;
}
<div id="tooltip" display="none" style="position: absolute; display: none;"></div>
<svg>
<rect width="100" height="50" style="fill: blue;" onmousemove="showTooltip(evt, 'This is blue');" onmouseout="hideTooltip();" >
</rect>
</svg>
You can use the title element as Phrogz indicated. There are also some good tooltips like jQuery's Tipsy http://onehackoranother.com/projects/jquery/tipsy/ (which can be used to replace all title elements), Bob Monteverde's nvd3 or even the Twitter's tooltip from their Bootstrap http://twitter.github.com/bootstrap/
On svg, the right way to write the title
<svg>
<title id="unique-id">Checkout</title>
</svg>
check here for more details https://css-tricks.com/svg-title-vs-html-title-attribute/
I came up with something using HTML + CSS only. Hope it works for you
.mzhrttltp {
position: relative;
display: inline-block;
}
.mzhrttltp .hrttltptxt {
visibility: hidden;
width: 120px;
background-color: #040505;
font-size:13px;color:#fff;font-family:IranYekanWeb;
text-align: center;
border-radius: 3px;
padding: 4px 0;
position: absolute;
z-index: 1;
top: 105%;
left: 50%;
margin-left: -60px;
}
.mzhrttltp .hrttltptxt::after {
content: "";
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #040505 transparent;
}
.mzhrttltp:hover .hrttltptxt {
visibility: visible;
}
<div class="mzhrttltp"><svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="#e2062c" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg><div class="hrttltptxt">علاقهمندیها</div></div>
I always go with the generic css title with my setup. I'm just building analytics for my blog admin page. I don't need anything fancy. Here's some code...
let comps = g.selectAll('.myClass')
.data(data)
.enter()
.append('rect')
...styling...
...transitions...
...whatever...
g.selectAll('.myClass')
.append('svg:title')
.text((d, i) => d.name + '-' + i);
And a screenshot of chrome...
I use heroicons for the project I am working on. (This is JSX format) I will handle the tooltip issue with this code.
<svg className="h-6 w-6">
<title>{reasons.join(" ")}</title>
<QuestionMarkCircleIcon className={style} />
</svg>
What I'm trying to achieve is to have an image fade upon mouseover so that text is visible - the image is (eg) a cd cover, and when the mouse-pointer is placed over the image, it should fade to show the track listing of the album. Each track listing is a hyperlink to the track on youtube.
I've zero experience of jquery, so am hoping to do this with javascript.
I'd be very appreciative of any help, as I've spent about two days now trying to google an answer.
If under "fade" you mean animation, try this:
function SetObjectOpacity(obj, op, op100)
{
obj.style.opacity = op.toString();
obj.style["-moz-opacity"] = op.toString();
obj.style["filter"] = "alpha(opacity=" + op100 + ")";
}
function FadeImage(imageid)
{
var framedelay = 50;
var frame = 0, img = document.getElementById(imageid), animint;
animint = setInterval(function() {
frame++;
SetObjectOpacity(img, frame / 20, frame * 5);
if (frame == 20) {
img.style.visibility = 'hidden';
clearInterval(animint);
}
}, framedelay);
}
And in HTML code:
<div style="width: 200px; height: 200px; position: relative;">
<div style="position: absolute; top: 0; left: 0; width: 200px; height: 200px;">
text you want to show
</div>
<img src="yourimage.jpg" id="yourimage" onMouseOver="FadeImage('yourimage');" style="position: absolute; top: 0; left: 0;">
</div>
Fading back on mouse out is obvious.
I really do recommend you use jQuery—it makes everything so much easier. And it's not really difficult. After all, we have the code ready for you (and I would be glad to explain it further), so all you need to do is include it.
So, assuming you go with jQuery, try this:
$(".overlay").hover(function(){
$(this).stop().fadeTo(300,0.8);
},function(){
$(this).stop().fadeTo(300,0);
});
And here's your HTML/CSS:
<div class="container">
<img src="http://placehold.it/200/200" />
<div class="overlay">
Hi<br>
Hi<br>
Hi<br>
</div>
</div>
.overlay {
position:absolute;
margin-top:-205px;
width:200px;
height:200px;
background-color:black;
opacity:0;
}