I'm trying to offset the element containing the image which has 21 frames. 0 - 21. I've placed 21 vertical columns over the image to visualize which frame should be present when the user's cursor is within the column lines. So each time your cursor moves into a different column of the grid, it should display a new frame. I need help figuring out whey the last frame (20) only shows when the user's cursor is on the very last pixel to the far right of the frame?
All the work is done in the javascript. I've commented each step and print to the console useful information regarding the math.
https://jsfiddle.net/JokerMartini/2e9awc4u/67/
window.onload = function() {
console.log('go')
$("#viewport").mousemove(function(e) {
// step 0: value to offset each frame (without scale)
const frameWidth = 320
// step 1: get the current mouse position in relation to the current element
const x = e.offsetX
// step 3: get width of viewable content, subtract 1 pixel starts at 0px
const viewWidth = $("#viewport").width() - 1
// step 4: find the % of the current position (in decimals 0-1.0)
const percent = x / viewWidth
// step 5: find the frame by the current percentage
const filmstripWidth = $("#filmstrip").width()
const frameByPercent = Math.round((filmstripWidth - frameWidth) * percent)
// step 6: find the nearest multiplier to frameWidth to offset
const offset = Math.floor(frameByPercent / frameWidth) * frameWidth
// const offset = -frameByPercent // smooth
// step 7: set that as the current position in negative (for offset reasons)
$("#filmstrip").css('transform', 'translate(' + -offset + 'px)')
console.log(
'CURSOR:', x,
'VIEW:', viewWidth,
'PERCENT:', percent,
'IMAGE WIDTH:', filmstripWidth,
frameByPercent
)
});
};
html {
height: 100%;
width: 100%;
}
#filmstrip {
will-change: transform;
pointer-events:none;
}
#margin-center {
background: grey;
padding: 30px
}
#viewport {
height: 180px;
width: 320px;
background: #FFFFAA;
display: block;
margin: auto;
position: relative;
overflow: hidden; /* Comment for debugging */
}
#guides {
position: absolute;
top: 0;
left: 0;
pointer-events:none;
}
#content {
display: inline-block;
font-size: 0;
height: auto;
max-width: 400px;
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="content">
<div id="margin-center">
<div id='viewport'>
<img id='filmstrip' src="https://i.ibb.co/7XDpcnd/timer.jpg" width="auto" height="180">
<svg id="guides" width="320px" height="180px">
<defs>
<pattern id="grid" width="15.238" height="180" patternUnits="userSpaceOnUse">
<path d="M 16 0 L 0 0 0 180" fill="none" stroke="black" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
</div>
</div>
Your results are offset by 1 because you deducted one full frameWidth.
Added code to cap percent at 0.999, to prevent it jumping to 22nd frame. mousemove positions will sometimes be at end position or greater.
window.onload = function() {
console.log('go')
$("#viewport").mousemove(function(e) {
// step 0: value to offset each frame (without scale)
const frameWidth = 320
// step 1: get the current mouse position in relation to the current element
const x = e.offsetX
// step 3: get width of viewable content, subtract 1 pixel starts at 0px
const viewWidth = $("#viewport").width() - 1
// step 4: find the % of the current position (in decimals 0-1.0)
const percent = x / viewWidth
// step 5: find the frame by the current percentage
const filmstripWidth = $("#filmstrip").width()
const frameByPercent = Math.round((filmstripWidth) * Math.min(percent,0.999))
// step 6: find the nearest multiplier to frameWidth to offset
const offset = Math.floor(frameByPercent / frameWidth) * frameWidth
// const offset = -frameByPercent // smooth
// step 7: set that as the current position in negative (for offset reasons)
$("#filmstrip").css('transform', 'translate(' + -offset + 'px)')
console.log(
'CURSOR:', x,
'VIEW:', viewWidth,
'PERCENT:', percent,
'IMAGE WIDTH:', filmstripWidth,
frameByPercent
)
});
};
html {
height: 100%;
width: 100%;
}
#filmstrip {
will-change: transform;
pointer-events:none;
}
#margin-center {
background: grey;
padding: 30px
}
#viewport {
height: 180px;
width: 320px;
background: #FFFFAA;
display: block;
margin: auto;
position: relative;
overflow: hidden; /* Comment for debugging */
}
#guides {
position: absolute;
top: 0;
left: 0;
pointer-events:none;
}
#content {
display: inline-block;
font-size: 0;
height: auto;
max-width: 400px;
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="content">
<div id="margin-center">
<div id='viewport'>
<img id='filmstrip' src="https://i.ibb.co/7XDpcnd/timer.jpg" width="auto" height="180">
<svg id="guides" width="320px" height="180px">
<defs>
<pattern id="grid" width="15.238" height="180" patternUnits="userSpaceOnUse">
<path d="M 16 0 L 0 0 0 180" fill="none" stroke="black" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
</div>
</div>
Related
This question already has answers here:
CSS Only Pie Chart - How to add spacing/padding between slices?
(2 answers)
Closed 5 months ago.
For the following code:
REPL
How could I make each field of the circle be the same size in a simple way? The fields on the top and bottom are currently bigger than the other ones for obvious reasons.
I could do some complicated calculations with position relative and absolute, but I wonder if there's an easy solution for my problem?
This is what it should look like:
Here's a variable <svg> solution REPL
Unfortunately not every field is seperately clickable which I just saw you mentioned in a comment. That's a crucial fact which you might add to your question.
Edit: By changing to fill: none; actually every field is seperately clickable, thanks #herrstrietzel
<script>
const size = 300
let strokeWidth = 25
let gap = 20
$: circumference = Math.PI * (size - strokeWidth)
$: r = size/2 - strokeWidth/2
let pieces = [
{stroke: 'teal'},
{stroke: 'magenta'},
{stroke: 'orange'},
]
let color = '#1411DF'
</script>
<div style:width="{size}px">
<svg width={size} height={size} style="transform: rotate({-90+(gap/2/r/Math.PI*180)}deg)">
{#each pieces as piece, index}
{#const ownLength = circumference / pieces.length - gap}
<circle r={r}
cx={size/2}
cy={size/2}
style:stroke-width={strokeWidth}
style:stroke={piece.stroke}
style:stroke-dasharray="{ownLength} {circumference}"
style="fill: none; transform-origin: center;"
style:transform="rotate({index * 360 / pieces.length}deg)"
on:click="{() => console.log(piece.stroke)}"
/>
{/each}
</svg>
<input type="color" bind:value={color}>
<button on:click={() => pieces = [...pieces, {stroke: color}]}>
add piece
</button>
<label>
stroke-width:
<input type="range" bind:value={strokeWidth} min="1" max={size/5}>
{strokeWidth}
</label>
<label>
gap:
<input type="range" bind:value={gap} min="1" max={size/5}>
{gap}
</label>
</div>
<style>
circle:hover {
stroke: black !important;
}
div {
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
}
svg {
display: block;
margin-bottom: 2rem;
}
label {
width: 100%;
font-size: .9rem;
padding: 1rem 0;
}
input {
padding: 0;
}
</style>
And inspired by H.B.'s answer a different solution with path elements REPL
<script>
let colors = ['teal', 'DarkOrchid', 'orange']
let color = '#2E15D1'
const size = 300
let strokeWidth = 10
let gap = 10 // degree
$: r = size/2 - strokeWidth/2
$: deg = (180 - (360 / colors.length) + gap) / 2
$: x = r * Math.cos(rad(deg))
$: y = r * Math.sin(rad(deg))
function rad(angle) {
return angle * Math.PI / 180;
}
</script>
<div style:width="{size}px">
<svg width={size} height={size} viewbox="{-size/2} {-size/2} {size} {size}" xmlns="http://www.w3.org/2000/svg">
{#each colors as color, index}
<path d="M -{x} -{y} A {r} {r} 0 0 1 {x} -{y}"
style="fill: none;"
style:stroke={color}
style:stroke-width="{strokeWidth}"
style:transform="rotate({360 / colors.length * index}deg)"
on:click="{() => console.log(color)}"
/>
{/each}
<!-- <circle cx="0" cy="0" {r} fill="none" stroke="black"></circle> -->
<!-- <circle cx="0" cy="0" r="2"></circle> -->
</svg>
<input type="color" bind:value={color}>
<button on:click={() => colors = [...colors, color]}>
add piece
</button>
<label>
stroke-width:
<input type="range" bind:value={strokeWidth} min="1" max={size/5}>
{strokeWidth}
</label>
<label>
gap:
<input type="range" bind:value={gap} min="1" max={(360/colors.length)-1}>
{gap}°
</label>
</div>
<style>
div {
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
}
svg {
margin: 2rem;
/* transform: rotate(180deg); */
}
path:hover {
stroke: black !important;
}
input {
padding: 0;
margin: .4rem;
}
label {
display: grid;
align-items: center;
grid-template-columns: 1fr max-content 1fr;
font-size: .9rem;
white-space: nowrap;
}
</style>
I could do some complicated calculations with position relative and absolute, but I wonder if there's an easy solution for my problem?
I'm afraid there is no solution completely without calculations, but at least this one is not complicated.
You can use conic-gradient and split it by degrees (in my example by 60deg) but the corners tend to be too pixelated. You can also use repeating-conic-gradient.
div {
position: relative;
width: 200px;
height: 200px;
border-radius: 100%;
background: conic-gradient(
/* per 60deg - 5*2deg for white space */
white 5deg,
red 5deg 55deg, white 55deg 65deg,
orange 65deg 115deg, white 115deg 125deg,
blue 125deg 175deg, white 175deg 185deg,
pink 185deg 235deg, white 235deg 245deg,
gray 245deg 295deg, white 295deg 305deg,
yellow 305deg 355deg, white 355deg 360deg
);
}
div:after {
content: '';
position: absolute;
left: 20px;
top: 20px;
width: 160px;
height: 160px;
background: white;
border-radius: 100%;
}
<div></div>
You could use an SVG. You can create path elements with arcs and stroke them in the respective color.
Arcs are fairly complicated with many parameters:
A rx ry x-axis-rotation large-arc-flag sweep-flag x y
a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy
I would recommend reading the MDN documentation or the spec.
Example of a segment:
<svg width="320" height="320" xmlns="http://www.w3.org/2000/svg">
<path d="M 50 50 a 50 50 0 0 1 50 0" stroke="black" stroke-width="20" fill="none"/>
</svg>
The easiest method is probably calculating the angles (start/end) and converting from polar to cartesian. The paths only need two commands: Absolute move to start location & absolute arc to end location.
Full example with clickable segments and keyboard support for additional accessibility (could still be improved, e.g. by supplying screen reader texts):
<script>
let radius = 150;
let stroke = 20;
let gap = 5;
let segments = [
'#ff0000',
'#00ff00',
'#0000ff',
];
function getCoordinates(i, gap) {
const angleDelta = 360 / segments.length;
const start = polarToCartesian(radius, i * angleDelta + gap);
const end = polarToCartesian(radius, i * angleDelta + angleDelta);
return { start, end };
}
const polarToCartesian = (r, angle) => {
return {
x: r * Math.cos(rad(angle)),
y: r * Math.sin(rad(angle)),
}
}
const rad = x => x * Math.PI / 180;
const onClick = i => alert('Segment ' + i);
</script>
<div>
<svg width="320" height="320" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(160, 160) rotate({-90 - gap/2})">
{#each segments as segment, i (i)}
{#const { start, end } = getCoordinates(i, gap)}
<path d="M {start.x} {start.y}
A {radius} {radius} 0 0 1 {end.x} {end.y}"
stroke={segment} stroke-width={stroke} fill="none"
tabindex={0}
on:keydown={e => { if (e.key == 'Enter') onClick(i); }}
on:click={() => onClick(i)} />
{/each}
</g>
</svg>
</div>
REPL
for (el of chart.children) {
i++
previous__element = chart.children[i - 1]
if (el.classList.contains('rule')) {
//pass
} else {
line = el.children[0].children[0]
pos1 = previous__element.children[2].getBoundingClientRect()
position1 = {
top: pos1.top,
left: pos1.left,
}
pos2 = el.children[2].getBoundingClientRect()
console.log(previous__element.children[2])
console.log(el.children[2])
position2 = {
top: pos2.top,
left: pos2.left,
}
line.setAttribute('x1', Math.trunc(pos1.left))
line.setAttribute('y1', Math.trunc(pos1.top))
line.setAttribute('x2', Math.trunc(pos1.left))
line.setAttribute('y2', Math.trunc(pos1.top))
line.setAttribute('stroke', 'white')
}
}
html that gets output:
For some reason this does not actually show the lines, when hovering over them in dev tools it shows the height and width is 0. I'm trying to get the line to connect to the markers in the elements.
In this example I use position relative/absolute on all the elements. I don't know if that fits your solution, but the core of the example is that the SVG document is in the background of all the boxes. So, all the lines could be placed in that one SVG document.
Maybe the reason why your lines are not showing up is that they mis the stroke-width or that your SVG element does not have a width and a height.
let chart = document.querySelector('#chart');
let poschart = chart.getBoundingClientRect();
let line = chart.querySelector('svg line');
let boxs = chart.querySelectorAll('div.box');
let pos1 = boxs[0].getBoundingClientRect();
line.setAttribute('x1', pos1.x+pos1.width/2-poschart.x);
line.setAttribute('y1', pos1.y+pos1.height/2-poschart.y);
let pos2 = boxs[1].getBoundingClientRect();
line.setAttribute('x2', pos2.x+pos2.width/2-poschart.x);
line.setAttribute('y2', pos2.y+pos2.height/2-poschart.y);
#chart {
position: relative;
width: 400px;
height: 300px;
margin: 10px 20px;
border: thin solid black;
}
#chart svg {
position: absolute;
}
.box {
position: absolute;
padding: .5em;
border: thin solid black;
background-color: white;
}
<div id="chart">
<svg viewBox="0 0 400 300" width="100%" height="100%">
<line stroke="black" stroke-width="2"/>
</svg>
<div class="box" style="left:50px;top:180px">Box 1</div>
<div class="box" style="left:310px;top:100px">Box 2</div>
</div>
I want to create a spin load component that infinitely loops adding more dashoffset to the stroke
Here is a example:
const spinLoad = setInterval(() => {
const loading = document.querySelector('#loading')
loading.style.strokeDashoffset = `${Number(loading.style.strokeDashoffset) + 800}`
}, 1000)
#loading {
width: 100px;
overflow: visible;
position: absolute;
z-index: 2;
fill: transparent;
stroke: #f1f1f1;
stroke-width: 0.5rem;
stroke-dasharray: 130px;
stroke-dashoffset: 0;
transition: ease-in-out 1s;
top: calc(50% - 50px);
left: calc(50% - 50px);
filter: drop-shadow(0px 3px 2px rgba(0, 0, 0, 0.6));
}
<svg id="loading" viewBox="0 0 240 240" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="icon" d="M42.3975 90C72.7709 37.3917 87.9576 11.0876 108.479 3.61845C121.734 -1.20615 136.266 -1.20615 149.521 3.61845C170.042 11.0876 185.229 37.3917 215.603 90C245.976 142.608 261.163 168.912 257.37 190.419C254.921 204.311 247.655 216.895 236.849 225.963C220.12 240 189.747 240 129 240C68.2532 240 37.8798 240 21.1507 225.963C10.3448 216.895 3.07901 204.311 0.629501 190.419C-3.16266 168.912 12.024 142.608 42.3975 90Z" />
</svg>
But this have three problems:
It takes too much long for start spinning
It spin at different speed sometimes, like lagging when go to other tab
Sometimes happens a bug and the speed go CRAZY
Anyone knows how to build this spinning load? I would be very thankful
:D
When doing animations it's much better to use requestAnimationFrame to control it. In this case it's best to remove the css transition because adding two different ways of animating the same thing causes chaos. Then just keep adding to the dash-offset in the frame function.
const loading = document.querySelector('#loading')
let loadingAnimation;
const spinLoad = (time) => {
const speed = 0.7 // lower number goes slower
loading.style.strokeDashoffset = time * speed
loadingAnimation = requestAnimationFrame(spinLoad)
}
loadingAnimation = requestAnimationFrame(spinLoad);
#loading {
width: 100px;
overflow: visible;
position: absolute;
z-index: 2;
fill: transparent;
stroke:#f1f1f1;
stroke-width: 0.5rem;
stroke-dasharray: 130px;
stroke-dashoffset: 0;
top: calc(50% - 50px);
left: calc(50% - 50px);
filter: drop-shadow(0px 3px 2px rgba(0, 0, 0, 0.6));
}
<svg
id="loading"
viewBox="0 0 240 240"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
id="icon"
d="M42.3975 90C72.7709 37.3917 87.9576 11.0876 108.479 3.61845C121.734 -1.20615 136.266 -1.20615 149.521 3.61845C170.042 11.0876 185.229 37.3917 215.603 90C245.976 142.608 261.163 168.912 257.37 190.419C254.921 204.311 247.655 216.895 236.849 225.963C220.12 240 189.747 240 129 240C68.2532 240 37.8798 240 21.1507 225.963C10.3448 216.895 3.07901 204.311 0.629501 190.419C-3.16266 168.912 12.024 142.608 42.3975 90Z"
/>
</svg>
I am trying to drag a circular knob from 0 to 360 degree using green sock library.
Adding a codepen below in which I have used bounds property which bound drag rotation from 0 to 359 degree but because of this when I start to drag from last quadrant(between 270 to 360 degree) then, the drag jumps to 1st quadrant(0 degree) and starts to drag from 0 degree. In the 1st, 2nd and 3rd quadrant the drag works properly but the 4th quadrant has some problem.
I want to keep the bounds but also wants to drag if I drag between 270 to 360 degree. Please have a look over the codepen and help me out with this. Thank you.
Steps to reproduce
1. Drag till the last quadrant(between 270 degree to 360 degree) similar to clock between 9 - 12 and leave the mouse.
Press from last quadrant where you left the mouse, here you can see the drag starts from 0 degree.
var rotationOffset = 90, //in case the dial's "home" position isn't at 0 degrees (pointing right). In this case, we use 90 degrees.
RAD2DEG = 180 / Math.PI, //for converting radians to degrees
adjusting;
TweenLite.set("#spinner", {
transformOrigin: "center"
});
Draggable.create("#spinner", {
type: "rotation",
sticky: true,
bounds: {
minRotation: 0,
maxRotation: 359,
},
trigger: "#svg",
onPress: function(e) {
if (!adjusting) {
//figure out the angle from the pointer to the rotational origin (in degrees)
var rotation = Math.atan2(this.pointerY - this.rotationOrigin.y, this.pointerX - this.rotationOrigin.x) * RAD2DEG;
//set the rotation (with any offset that's necessary)
TweenLite.set(this.target, {
rotation: rotation + rotationOffset
});
//now we'll end the drag and start it again from this new place, but when we start again, it'll call the onPress of course so to avoid an endless loop, we use the "adjusting" variable to skip it in the triggered onPress.
adjusting = true;
this.endDrag(e);
this.startDrag(e);
adjusting = false;
}
},
onDrag: function() {
var rotation = Math.atan2(this.pointerY - this.rotationOrigin.y, this.pointerX - this.rotationOrigin.x) * RAD2DEG;
$("#percent").text(rotation.toFixed(2))
}
});
#svg {
position: fixed;
width: 100%;
height: 100%;
touch-action: none;
}
#spinner {
cursor: pointer;
}
.big-circle {
fill: dodgerblue;
stroke: black;
stroke-width: 6;
}
.small-circle {
fill: black;
}
.line {
fill: none;
stroke: black;
stroke-width: 6;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/utils/Draggable.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/DrawSVGPlugin.min.js"></script>
<div id="percent">0</div>
<svg id="svg" viewBox="0 0 1000 1000">
<g id="spinner">
<circle class="big-circle" cx="500" cy="500" r="200" />
<circle class="small-circle" cx="500" cy="500" r="12" />
<polyline class="line" points="500,500 500,300" />
</g>
</svg>
codepen of knob
* UPDATED *
I have updated the above codepen link with working solution if anybody comes in future to check similar problem. Thank you.
Any reason why you're using bounds and trigger parameters?
If you remove them, your code will work accordingly.
var rotationOffset = 90, //in case the dial's "home" position isn't at 0 degrees (pointing right). In this case, we use 90 degrees.
RAD2DEG = 180 / Math.PI, //for converting radians to degrees
adjusting;
TweenLite.set("#spinner", {transformOrigin: "center"});
Draggable.create("#spinner", {
type: "rotation",
sticky: true,
/*bounds: {
minRotation: 0,
maxRotation: 360,
},
trigger: "#svg",*/
onPress: function(e) {
if (!adjusting) {
//figure out the angle from the pointer to the rotational origin (in degrees)
var rotation = Math.atan2(this.pointerY - this.rotationOrigin.y, this.pointerX - this.rotationOrigin.x) * RAD2DEG;
//set the rotation (with any offset that's necessary)
TweenLite.set(this.target, {rotation:rotation + rotationOffset});
//now we'll end the drag and start it again from this new place, but when we start again, it'll call the onPress of course so to avoid an endless loop, we use the "adjusting" variable to skip it in the triggered onPress.
adjusting = true;
this.endDrag(e);
this.startDrag(e);
adjusting = false;
}
},
onDrag: function(){
var rotation = Math.atan2(this.pointerY - this.rotationOrigin.y, this.pointerX - this.rotationOrigin.x) * RAD2DEG;
}
});
#svg {
position: fixed;
width: 100%;
height: 100%;
touch-action: none;
}
#spinner {
cursor: pointer;
}
.big-circle {
fill: dodgerblue;
stroke: black;
stroke-width: 6;
}
.small-circle {
fill: black;
}
.line {
fill: none;
stroke: black;
stroke-width: 6;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/utils/Draggable.min.js"></script>
<div id="percent">0</div>
<svg id="svg" viewBox="0 0 1000 1000">
<g id="spinner">
<circle class="big-circle" cx="500" cy="500" r="200" />
<circle class="small-circle" cx="500" cy="500" r="12" />
<polyline class="line" points="500,500 500,300" />
</g>
</svg>
https://greensock.com/docs/Utilities/Draggable/static.create()
Question updated
This what I have done so far.
It works fine but I want to do an animation which is little much complicated for me.
// Get the id of the <path> element and the length of <path>
var myline = document.getElementById("myline");
var length = myline.getTotalLength();
// The start position of the drawing
myline.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
myline.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
// What % down is it?
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Length to offset the dashes
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
myline.style.strokeDashoffset = length - draw;
}
body {
height: 2000px;
background: #f1f1f1;
}
#mySVG {
position: fixed;
top: 15%;
width: 100vw;
height: 100vh;
margin-left: -50px;
}
.st0 {
fill: none;
stroke-dashoffset: 3px;
stroke: red;
stroke-width: 5;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
<h2>Scroll down this window to draw my path.</h2>
<p>Scroll back up to reverse the drawing.</p>
<svg id="mySVG" viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice" style="width: 6%; padding-bottom: 42%; height: 1px; overflow: visible">
<path id="myline" class="st0" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" /> Sorry, your browser does not support inline SVG.
</svg>
what I want is to animate a <circle> with the growth of the <path> like
I know the path is growing using the strokeDasharray. But still, is there a way to attain what am looking for.? if no then please do suggest another way. thank you..!
Use getPointAtLength()
Looks like another answer already suggested this :)
// Get the id of the <path> element and the length of <path>
var myline = document.getElementById("myline");
var length = myline.getTotalLength();
circle = document.getElementById("circle");
// The start position of the drawing
myline.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
myline.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
// What % down is it?
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Length to offset the dashes
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
myline.style.strokeDashoffset = length - draw;
//get point at length
endPoint = myline.getPointAtLength(draw);
circle.setAttribute("cx", endPoint.x);
circle.setAttribute("cy", endPoint.y);
}
body {
height: 2000px;
background: #f1f1f1;
}
#circle{
fill:red;
}
#mySVG {
position: fixed;
top: 15%;
width: 100vw;
height: 100vh;
margin-left: -50px;
}
.st0 {
fill: none;
stroke-dashoffset: 3px;
stroke: red;
stroke-width: 5;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
<h2>Scroll down this window to draw my path.</h2>
<p>Scroll back up to reverse the drawing.</p>
<svg id="mySVG" viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice" style="width: 6%; padding-bottom: 42%; height: 1px; overflow: visible">
<circle id="circle" cx="10" cy="10" r="10"/>
<path id="myline" class="st0" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" /> Sorry, your browser does not support inline SVG.
</svg>
I'm not sure if you want to just draw a circle or a circle at the end of the line, but maybe getPointAtLength() could help you.
You can get the point at the end the line with myline.getPointAtLength(draw) you can use this to draw a circle at that point
I've added the code to console.log(myline.getPointAtLength(draw)).
My lunch break is just about over, otherwise I'd draw the circle for you too.
// Get the id of the <path> element and the length of <path>
var myline = document.getElementById("myline");
var length = myline.getTotalLength();
// The start position of the drawing
myline.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
myline.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
// What % down is it?
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Length to offset the dashes
var draw = length * scrollpercent;
console.log(myline.getPointAtLength(draw));
// Reverse the drawing (when scrolling upwards)
myline.style.strokeDashoffset = length - draw;
}
body {
height: 2000px;
background: #f1f1f1;
}
#mySVG {
position: fixed;
top: 15%;
width: 100vw;
height: 100vh;
margin-left: -50px;
}
.st0 {
fill: none;
stroke-dashoffset: 3px;
stroke: red;
stroke-width: 5;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
<h2>Scroll down this window to draw my path.</h2>
<p>Scroll back up to reverse the drawing.</p>
<svg id="mySVG" viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice" style="width: 6%; padding-bottom: 42%; height: 1px; overflow: visible">
<path id="myline" class="st0" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" /> Sorry, your browser does not support inline SVG.
</svg>