In the following, I create a red circle that is drawn only part way around the edge. If I set strokeDasharray to 500 628, which should be the dash length and dash offset, I get the partial circle I am looking for. However, I cannot, afterwards, just set strokeDasharray = "600" afterwards to only set the dash length. I must include the dash offset as previous strokeDasharray = "600 628".
Why can't I change the dash length by itself?
The actual code is this line: el.style.strokeDasharray="500 628";
I would just like to change it by doing this: el.style.strokeDasharray="200";
var el=document.getElementsByTagName("circle")[0];
el.style.strokeDasharray="500 628";
svg {
width: 240px;
height: 240px;
background: #eee;
transform:rotate(-90deg);
}
svg #shape {
fill: none;
stroke: red;
stroke-width: 4;
}
<svg>
<circle id='shape' cx='120' cy='120' r='100' />
</svg>
No, the 2nd value you are passing is not the strokeDashoffset. It is a 2nd length value.
According to documentation:
A list of comma and/or white space separated lengths (which can have a unit identifier) and percentages. A percentage represents a distance as a percentage of the current viewport. A negative value is an error. If the sum of the values is zero, then the stroke is rendered as if a value of none were specified.
You can see in this example that it is different:
svg {
width: 240px;
height: 240px;
background: #eee;
transform:rotate(-90deg);
}
svg #shape1 {
fill: none;
stroke: red;
stroke-width: 4;
stroke-dasharray: 600 100;
}
svg #shape2 {
fill: none;
stroke: blue;
stroke-width: 4;
stroke-dasharray: 600;
stroke-dashoffset: 100;
}
<svg>
<circle id='shape1' cx='120' cy='120' r='100' />
</svg>
<svg>
<circle id='shape2' cx='120' cy='120' r='100' />
</svg>
So I'm currently trying to animate a SVG icon. I have a heart and a few lines around it all with the class of lines. By default I want the heart to just have an outline but then change the background color on click (I have that working).
The part I'm struggling with is only showing the lines around the heart on click. I know this is simple but I just can't seem to wrap my head around it. In my css I set .lines{display: none} by default and I also created a class .lines-show{fill: red} and I want them to show when the heart is clicked. Then I'm going to use a setTimeout() function to hide the lines after X amount of seconds. Here is a link to my codepen if this helps https://codepen.io/Brushel/pen/xXvqgK?editors=1111 Here is the code I have so far:
const lines = document.querySelectorAll(".lines");
const heart = document.querySelector('.heart');
heart.addEventListener('click', function(){
heart.classList.toggle("heart-fill-up");
});
svg {
max-height: 100px;
}
.heart {
fill: transparent;
transition: all .5s;
}
.heart-fill-up {
fill: red;
}
.lines {
display: none;
}
.lines-show {
fill: red;
}
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 298 281" style="enable-background:new 0 0 298 281;" xml:space="preserve">
<style type="text/css">
.heart{
stroke:#FF0606;
stroke-miterlimit:10;
stroke-width: 5px;
}
</style>
<title>icon_wishlist</title>
<g id="icon_wishlist">
<path class="heart" d="M66.3,69.4c-19.1,20-19.1,51.5,0,71.5l84.6,87.9l84.7-87.8c19.1-20,19.1-51.5,0-71.5
c-18-19.1-48.1-19.9-67.2-1.8c-0.6,0.6-1.2,1.2-1.8,1.8L151,85.7l-15.6-16.2c-18-19.1-48.1-19.9-67.2-1.8
C67.6,68.2,67,68.8,66.3,69.4L66.3,69.4z"/>
</g>
<path class="lines" d="M58.1,60.6L9.4,22.1c-1.5-1.2-1.8-3.4-0.6-4.9l0,0c1.2-1.5,3.4-1.8,4.9-0.6l48.6,38.5c1.5,1.2,1.8,3.4,0.6,4.9
l0,0C61.8,61.5,59.6,61.8,58.1,60.6z"/>
<path class="lines" d="M265.4,237l-48.6-38.5c-1.5-1.2-1.8-3.4-0.6-4.9v0c1.2-1.5,3.4-1.8,4.9-0.6l48.6,38.5c1.5,1.2,1.8,3.4,0.6,4.9
l0,0C269.1,237.9,266.9,238.2,265.4,237z"/>
<path class="lines" d="M40.1,230.6L88,191.3c1.5-1.2,3.7-1,4.9,0.5v0c1.2,1.5,1,3.7-0.5,4.9l-47.9,39.3c-1.5,1.2-3.7,1-4.9-0.5l0,0
C38.4,234.1,38.6,231.9,40.1,230.6z"/>
<path class="lines" d="M236.7,54.8l46-41.6c1.4-1.3,3.7-1.2,4.9,0.2v0c1.3,1.4,1.2,3.7-0.2,4.9l-46,41.6c-1.4,1.3-3.7,1.2-4.9-0.2v0
C235.2,58.3,235.3,56.1,236.7,54.8z"/>
<path class="lines" d="M16.9,139.2h34c1.9,0,3.5,1.6,3.5,3.5v0c0,1.9-1.6,3.5-3.5,3.5h-34c-1.9,0-3.5-1.6-3.5-3.5v0
C13.4,140.7,15,139.2,16.9,139.2z"/>
<path class="lines" d="M249.8,139.2h34c1.9,0,3.5,1.6,3.5,3.5v0c0,1.9-1.6,3.5-3.5,3.5h-34c-1.9,0-3.5-1.6-3.5-3.5v0
C246.3,140.7,247.8,139.2,249.8,139.2z"/>
<path class="lines" d="M154.5,237.3v34c0,1.9-1.6,3.5-3.5,3.5l0,0c-1.9,0-3.5-1.6-3.5-3.5v-34c0-1.9,1.6-3.5,3.5-3.5l0,0
C152.9,233.8,154.5,235.4,154.5,237.3z"/>
<path class="lines" d="M154.5,20.3v34c0,1.9-1.6,3.5-3.5,3.5l0,0c-1.9,0-3.5-1.6-3.5-3.5v-34c0-1.9,1.6-3.5,3.5-3.5l0,0
C152.9,16.8,154.5,18.4,154.5,20.3z"/>
</svg>
This is my desired result on click
Since you have more than one of the element "lines" you need to sort through each of them individually (I used a for loop to do this) whenever you want to toggle their classes. I also added a setTimeOut to remove them after 1000 milliseconds.
http://jsfiddle.net/jnqzoq1g/
const lines = document.querySelectorAll(".lines");
const heart = document.querySelector('.heart');
heart.addEventListener('click', function(){
heart.classList.toggle("heart-fill-up");
for (var x = 0; x < lines.length; x++){
lines[x].classList.toggle("lines-show");
}
setTimeout(function(){
for (var x = 0; x < lines.length; x++){
lines[x].classList.toggle("lines-show");
}
}, 1000);
});
.lines {
fill: transparent;
transition: all .5s;
}
.lines-show {
fill: red;
Edit for desired result:
You can loop over all the lines in an array by getting them all with document.getElementsByClassName("lines"); and if you convert that into an array you can do
Array.from(document.getElementsByClassName("lines")).forEach( line => {
line.style.display = "block";
});
You can have it toggle repeatedly by wrapping the toggle in a setInterval.
let interval = setInterval( () => {
heart.classList.toggle("heart-fill-up");
}, 1000);
If you don't want to use javascript, you can use a css animation set to repeat
#keyframes heart {
0% {
fill: transparent;
}
100% {
fill: red;
}
}
and apply it to your heart with
animation: heart 1s infinite
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.
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>