Drawing curved SVG arrow lines from div to div
Make an svg
element that (invisibly) underlies the entire document. This will hold both arrows. Insert two svg path
elements (the arrows) whose start and end coordinates are calculated based on the positions of the div's to be connected, and whose curve is created in whatever way you want based on those start and end coordinates.
For the example below, click on "Run code snippet". Then click and drag either of the div's to see how the arrows are dynamically created, i.e. they move with the divs. jQuery and jQueryUI are used in the code snippet simply to allow the easy draggability of the divs and have nothing to do with the creation and use of the arrows.
This example has two arrows starting and ending at the middle of the divs' sides. The details of the curve are, of course, up to you. The arrow lines are constructed using the d
attribute of the svg path
. In this example, "M" is the "moveTo" coordinates where the path will start and the "C" points are the first and second control points and final coordinate for a cubic bezier curve. You'll have to look those up to understand what they are, but they are a general way of creating smooth curves in an svg element. The arrowheads are added using an svg <marker>
element which you can read about here.
A more complex document would need more care to determine the start and end coordinates of the svg path
elements, i.e. the arrows, but this example at least gives you a place to begin.
Answers to your specific questions:
If SVG takes coordinates, do I have to find the coordinate position of the elements before creating the SVG drawing? Yes, as I've done in my code.
Does it have to be re-drawn if the window size is adjusted? Probably yes, depending on what happens to the divs themselves when the window is resized.
var divA = document.querySelector("#a");var divB = document.querySelector("#b");var arrowLeft = document.querySelector("#arrowLeft");var arrowRight = document.querySelector("#arrowRight");
var drawConnector = function() { var posnALeft = { x: divA.offsetLeft - 8, y: divA.offsetTop + divA.offsetHeight / 2 }; var posnARight = { x: divA.offsetLeft + divA.offsetWidth + 8, y: divA.offsetTop + divA.offsetHeight / 2 }; var posnBLeft = { x: divB.offsetLeft - 8, y: divB.offsetTop + divB.offsetHeight / 2 }; var posnBRight = { x: divB.offsetLeft + divB.offsetWidth + 8, y: divB.offsetTop + divB.offsetHeight / 2 }; var dStrLeft = "M" + (posnALeft.x ) + "," + (posnALeft.y) + " " + "C" + (posnALeft.x - 100) + "," + (posnALeft.y) + " " + (posnBLeft.x - 100) + "," + (posnBLeft.y) + " " + (posnBLeft.x ) + "," + (posnBLeft.y); arrowLeft.setAttribute("d", dStrLeft); var dStrRight = "M" + (posnBRight.x ) + "," + (posnBRight.y) + " " + "C" + (posnBRight.x + 100) + "," + (posnBRight.y) + " " + (posnARight.x + 100) + "," + (posnARight.y) + " " + (posnARight.x ) + "," + (posnARight.y); arrowRight.setAttribute("d", dStrRight);};
$("#a, #b").draggable({ drag: function(event, ui) { drawConnector(); }});
setTimeout(drawConnector, 250);/* The setTimeout delay here is only required to prevent * the initial appearance of the arrows from being * incorrect due to the animated expansion of the * Stack Overflow code snippet results after clicking * "Run Code Snippet." If this was a simpler website, * a simple command, i.e. `drawConnector();` would suffice. */
html,body { width: 100%; height: 100%; padding: 0; margin: 0;}#instructions { position: fixed; left: 50%;}#a, #b { color: white; text-align: center; padding: 10px; position: fixed; width: 100px; height: 20px; left: 100px;}#a { background-color: blue; top: 20px;}#b { background-color: red; top: 150px;}
<p id="instructions">Click and drag either div to see automatic arrow adjustments.</p><script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script><svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"> <defs> <marker id="arrowhead" viewBox="0 0 10 10" refX="3" refY="5" markerWidth="6" markerHeight="6" orient="auto"> <path d="M 0 0 L 10 5 L 0 10 z" /> </marker> </defs> <g fill="none" stroke="black" stroke-width="2" marker-end="url(#arrowhead)"> <path id="arrowLeft"/> <path id="arrowRight"/> </g></svg><div id="a">Div 1</div><div id="b">Div 2</div>
Draw arrow in svg path dynamically is not working
You don't seem to be appending newpath
to anything.
Also you are appending defs
to newpath
. I'm not sure that works (I've never tried it). You should probably avoid doing that. Append the defs to the SVG instead.
Add svg icon in the middle of an svg curved line
One way to achieve the result is a degenerate animation:
- Define the marker shape (
obj1
in the example below) - Position the marker at the beginning of the curve (
track1
below; this is the path definition from your example). Specify an animated motion of the marker shape along the curve with some particular settings:
- Explicit positioning along the track using
keyTimes
,keyPoints
attributes, limiting the range of positions to exactly one point: the midpoint of the curve - Infinite duration, infinite repeat
- Auto-rotation of the shape according to the orientation of the track curve (
rotate
attribute )
- Explicit positioning along the track using
Effectively there is no animation at all but the shape is positioned at the center of the curve, properly oriented.
Example
<html> <head> <title>SVG object centered on path</title> </head> <body> <svg width="200px" height="200px" viewBox="0 0 500 500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" > <defs> <path id="obj1" d="M11.18,0 L-2.5,10 -2.5,-10 Z" stroke="black" stroke-width="1" fill="green" > </path> <path id="track1" d="M70,260 C105,260 126,330 160,330" stroke="#ff4444" stroke-width="2" fill="none" /> </defs> <use xlink:href="#track1"/> <use xlink:href="#obj1"> <animateMotion calcMode="linear" dur="infinite" repeatCount="infinite" rotate="auto" keyPoints="0.5;0.5" keyTimes="0.0;1.0" > <mpath xlink:href="#track1"/> </animateMotion> </use> </svg> </body></html>
Add double lines in the middle of another svg line
So I discovered one way to implement that using polylines and calculating the middle of the source and target coordinates, so when it changes I change the middle point too. After that, I created a marker-mid with the double lines.
Curved thick arrows on canvas
You can draw a curved arrow using 2 canvas context drawings:
- A Bezier curve for the shaft
- A triangle of lines for the arrowhead.
Demo: http://jsfiddle.net/m1erickson/x2dy6/
The shaft is just context's cubic Bezier curve:
ctx.moveTo(bez.sx,bez.sy);
ctx.bezierCurveTo(bez.cx1,bez.cy1,bez.cx2,bez.cy2,bez.ex,bez.ey);
ctx.lineWidth=5;
ctx.stroke();
The arrowhead is just a triangle made of context line-to commands:
ctx.moveTo(0,0);
ctx.lineTo(0,-10);
ctx.lineTo(15,0);
ctx.lineTo(0,10);
ctx.lineTo(0,0);
The arrowhead is positioned at the end of the shaft using context's transforms (translate+rotate):
ctx.translate(bez.ex,bez.ey);
ctx.rotate(endingAngle);
You need the angle at the end of the curve to rotate your arrowhead at the proper angle.
That proper ending angle can be calculated like this:
var pointNearEnd=getCubicBezierXYatT(
{x:bez.sx,y:bez.sy},
{x:bez.cx1,y:bez.cy1},
{x:bez.cx2,y:bez.cy2},
{x:bez.ex,y:bez.ey},0.99);
var dx=bez.ex-pointNearEnd.x;
var dy=bez.ey-pointNearEnd.y;
var endingAngle=Math.atan2(dy,dx);
// helper functions
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
Here is a complete code example:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var bez1={sx:50,sy:150,cx1:125,cy1:75,cx2:100,cy2:225,ex:200,ey:130};
drawCurvedArrow(bez1);
function drawCurvedArrow(bez){
// calculate the ending angle of the curve
var pointNearEnd=getCubicBezierXYatT(
{x:bez.sx,y:bez.sy},
{x:bez.cx1,y:bez.cy1},
{x:bez.cx2,y:bez.cy2},
{x:bez.ex,y:bez.ey},0.99);
var dx=bez.ex-pointNearEnd.x;
var dy=bez.ey-pointNearEnd.y;
var endingAngle=Math.atan2(dy,dx);
// draw the arrow shaft
ctx.moveTo(bez.sx,bez.sy);
ctx.bezierCurveTo(bez.cx1,bez.cy1,bez.cx2,bez.cy2,bez.ex,bez.ey);
ctx.lineWidth=5;
ctx.stroke();
// draw the arrow head
var size=ctx.lineWidth;
ctx.beginPath();
ctx.save();
ctx.translate(bez.ex,bez.ey);
ctx.rotate(endingAngle);
ctx.moveTo(0,0);
ctx.lineTo(0,-size*2);
ctx.lineTo(size*3,0);
ctx.lineTo(0,size*2);
ctx.lineTo(0,0);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// helper functions
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
SVG arrow wrong sync animation
look at this, This may be helpful:
#desc {
max-width: 700px;
margin-top: 100px;
color: #b3b3b3;
font-size: 11px;
font-family: sans-serif;
}
#desc li {
margin-bottom: 1em;
}
#desc p {
padding: 20px 0 0 40px;
}
#doubled-separate {
width: 110px;
}
#doubled-separate #over-path {
fill: none;
stroke-dasharray: 150;
stroke-dashoffset: 0;
animation: 10s reveal linear infinite forwards;
}
@keyframes reveal {
50%, 100% {
stroke-dashoffset: 150;
}
}
#separate-marker {
width: 88px;
position: relative;
top:-4px;
}
#separate-marker .just-line {
fill: none;
stroke-dashoffset: 0;
animation: 10s reveal2 linear infinite forwards;
}
@keyframes reveal2 {
100% {
stroke-dashoffset: 150;
}
}
#doubled-separate-marker {
width: 110px;
}
#doubled-separate-marker .over-path {
fill: none;
stroke-dasharray: 150;
stroke-dashoffset: 0;
animation: 3s reveal3 linear infinite forwards;
}
@keyframes reveal3 {
50%, 100% {
stroke-dashoffset: 150;
}
}
#doubled-separate-marker-2 {
width: 110px;
}
#doubled-separate-marker-2 #arrow3 path {
opacity: 0;
animation: 10s revealarrow linear infinite forwards;
}
@keyframes revealarrow {
0%, 50% {
opacity: 0;
}
60%, 100% {
opacity: 1;
}
}
#doubled-separate-marker-2 .over-path {
fill: none;
stroke-dasharray: 150;
stroke-dashoffset: 0;
animation: 10s reveal4 linear infinite forwards;
}
@keyframes reveal4 {
50%, 100% {
stroke-dashoffset: 150;
}
}
#separate-marker-2 {
width: 88px;
position: relative;
top:-4px;
}
<svg viewBox="0 0 44 97" preserveAspectRatio="xMinYMin meet" id="separate-marker-2">
<defs>
<marker id="arrow4" viewBox="0 0 13 13" refX="11" refY="8" markerWidth="13" markerHeight="13" markerUnits="userSpaceOnUse" orient="auto-start-reverse" preserveAspectRatio="xMinYMin meet">
<path d="M3.66332316,0.125850427 L3.75090984,0.194245468 L12.2529105,7.89856961 C12.6592041,8.26674392 12.5414228,8.91869993 12.063138,9.1358919 L11.9627228,9.17332054 L0.963626457,12.4383634 C0.566538833,12.5562375 0.149079906,12.3298902 0.0312058479,11.9328025 C-0.0768453724,11.5688056 0.10434498,11.187691 0.441152309,11.0359066 L0.536766731,11.0003819 L10.2568836,8.11360225 L2.74367477,1.30576583 C2.46464034,1.05291103 2.41998014,0.63794112 2.62313708,0.333974789 L2.69153212,0.246388115 C2.94438692,-0.0326463148 3.35935683,-0.0773065179 3.66332316,0.125850427 L3.66332316,0.125850427 Z" fill="#B3B3B3" fill-rule="nonzero"></path>
</marker>
</defs>
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(1.000000, 1.000000)">
<path d="M31.6194299,0 C-0.925516514,8.99255645 -7.90071768,47.4229255 8.63466801,71.535115 C9.41568622,72.6740094 10.2491558,73.7809605 11.1348599,74.8513572" class="just-line" stroke="#B3B3B3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4" marker-end="url(#arrow4)">
<animate attributeName="d" from="M31.6194299,0 C-0.925516514,8.99255645 -7.90071768,47.4229255 8.63466801,71.535115 C9.41568622,72.6740094 10.2491558,73.7809605 11.1348599,74.8513572" to="M31.6194299,0 C-0.925516514,8.99255645 -7.90071768,47.4229255 8.63466801,71.535115 C15.4626235,81.4917594 26.2993953,89.006995 41,91" dur="1.5s" repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>
<div id="desc">
<ol>
<li>Line and arrowhead are two paths grouped together, with an identical (but wide and white) line path on top that animates to reveal.</li>
<li>Line path with arrowhead path attached as a marker. dash-offset animates on line path to add movement.</li>
<li>Fusion of 1 and 2. Line path with arrowhead as marker, white line path animating on top. Shows that path+marker connection is much smoother than path+path, and that butt linecaps create a smoother animation finish than square or round (although it required moving the white path start-point a step or two to cover the marker).</li>
<!-- figuring out CSS animation on an SVG marker was a JOURNEY -->
<li>Adaptation of 3. The arrowhead SVG marker has CSS animation applied. Since it doesn't appear until after the line path is fully visible, the white line path on top has a narrower stroke since it doesn't need to cover the marker. The animation timings are synced up via keyframe percentages.</li>
<li>native SVG animate!</li>
</ol>
<p>DISCLAIMER: honestly no idea about browser support for any of this</p>
</div>
Related Topics
Text Box to Appear When a Radio Button Is Selected
Hide Elements If User Logged In
Using Scrollintoview With a Fixed Position Header
Puppeteer Wait Until Page Is Completely Loaded
How to Convert from Google Sheet Date Number Value to JavaScript Date
How to Assign Empty Value to Select Dropdown
How to Redirect to Another Page in Reactjs When a If Condition Is Executed
Html2Canvas Generates Blurry Images
Check If an Array Is Empty in React Native
Css Div Height Not Expanding to Fit Content or Wrapping Content
How to Get the Url Parameters Using Angularjs
Assigning Id to All Elements of a Page in Angular Automatically
Get Array of Values from Multiple Inputs Using Jquery
How Insert JavaScript into Vue File
One Time Page Refresh After First Page Load
How to Download a File With Node.Js (Without Using Third-Party Libraries)
Laravel - How to Pass a PHP Variable to a JavaScript Function from a Blade File