Drag a knob to 360 degree using green sock - javascript

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()

Related

How could I get each field in the circle to be the same size and clickable? [duplicate]

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

Offset div based on image width

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>

Stopping a function and starting another

I'm trying to make a small animation (using Velocity.js) that alternates between two looping states when an svg is clicked. The first state is horizontally from scaleX(1) to scaleX(2.5) back to scaleX(1) at scaleY(1), the second is vertically scaleY(1) to scaleY(8) back to scaleY(1) at scaleX(2.5). When the svg is clicked the animation starts in it's horizontal state, clicked again, the state that was just active (horizontal) should stop, and the alternative state should start (vertical), every click changes to the alternate state. Ideally the state change is seamless in the sense that svg should scale to the correct scale on the axis that is not animating whilst the new active state is animating.
This is a gif of what i'm trying to achieve, the blue dot symbolises a click:
My current outcome is embedded, the problem i'm having is that only one state change occurs so I need to stop the previous animation. The other problem is that in the transition, the scaling does not happen at the same time, i.e the new active state's animation does not happen at the same time as scaling of the axis that is not animating in the new state. Any pointers in the right direction would be greatly appreciated.
// LINKs TO VELOCITY
// https://rawgit.com/julianshapiro/velocity/master/velocity.min.js
// https://cdnjs.cloudflare.com/ajax/libs/velocity/1.5.0/velocity.min.js
var Rightscale = {
chooser: 0,
svg: $('#right').find('svg'),
init: function() {
this.bindEvents();
},
bindEvents: function() {
Rightscale.svg.on("click", function() {
console.log(Rightscale.chooser)
if(Rightscale.chooser === 0) {
Rightscale.chooser = 1;
Rightscale.horizontal();
} else {
Rightscale.chooser = 0;
Rightscale.vertical();
}
})
},
horizontal: function() {
Rightscale.svg.velocity({
scaleX: 2.5,
scaleY: 1
}, {
duration: 3000,
loop: true,
easing: "linear"
})
},
vertical: function() {
Rightscale.svg.velocity({
scaleY: 8,
scaleX: 2.5
}, {
duration: 3000,
loop: true,
easing: "linear"
})
},
}
$(document).ready(function() {
Rightscale.init();
});
#right {
width: 100vw;
height: 100vh;
background-color: blue;
display: flex;
justify-content: center;
align-items: center;
}
.scale {
width: 40%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.5.0/velocity.min.js"></script>
<script src="https://rawgit.com/julianshapiro/velocity/master/velocity.min.js"></script>
<div id="right">
<div class="scale">
<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 392 132" style="enable-background:new 0 0 392 132;" xml:space="preserve">
<g>
<path d="M76.1,26.4v100.7H44.5V26.2H20.2V3.6h80v22.8H76.1z"/>
<path d="M113,127.1V3.6h69.5v22.6H145v27.3h32.4v22.6h-32.2v28.2h38.4v22.8H113z"/>
<path d="M235.6,129.3c-26.7,0-40.7-11.9-40.7-34.6c0-4.5,0.2-6.6,1.1-11.5h26.4l-0.2,2.6c-0.2,2.3-0.2,4.5-0.2,6.6
c0,10.7,4.7,16.4,13.7,16.4c8.1,0,12.8-5.1,12.8-13.7c0-7.5-3.6-13-9.8-15.6l-12.4-4.9C204.5,65.9,197,56.3,197,37.7
C197,13.4,210.9,1,238.6,1c24.5,0,36.7,10,36.7,29.9c0,4.3-0.4,6.4-1.3,11.9h-26.5c0.6-4.5,0.8-6.2,0.8-8.8
c0-8.5-3.6-12.8-10.2-12.8c-6.4,0-10.9,4.9-10.9,11.9c0,7.2,3.6,10.9,14.7,15.4l15.8,6.2c15.8,6.4,23.7,18.1,23.7,35.4
C281.3,115.4,265.1,129.3,235.6,129.3z"/>
<path d="M345.5,26.4v100.7h-31.6V26.2h-24.3V3.6h80v22.8H345.5z"/>
</g>
</svg>
</div>
</div>
I'm not sure if this is the desired effect you wanted. But I added a line in your click handler that stops current animations.
// LINKs TO VELOCITY
// https://rawgit.com/julianshapiro/velocity/master/velocity.min.js
// https://cdnjs.cloudflare.com/ajax/libs/velocity/1.5.0/velocity.min.js
var Rightscale = {
chooser: 0,
svg: $('#right').find('svg'),
init: function() {
this.bindEvents();
},
bindEvents: function() {
Rightscale.svg.on("click", function() {
Rightscale.svg.velocity("stop", true);
console.log(Rightscale.chooser)
if(Rightscale.chooser === 0) {
Rightscale.chooser = 1;
Rightscale.horizontal();
} else {
Rightscale.chooser = 0;
Rightscale.vertical();
}
})
},
horizontal: function() {
Rightscale.svg.velocity({
scaleX: 2.5,
scaleY: 1
}, {
duration: 3000,
loop: true,
easing: "linear"
})
},
vertical: function() {
Rightscale.svg.velocity({
scaleY: 8,
scaleX: 2.5
}, {
duration: 3000,
loop: true,
easing: "linear"
})
},
}
$(document).ready(function() {
Rightscale.init();
});
#right {
width: 100vw;
height: 100vh;
background-color: blue;
display: flex;
justify-content: center;
align-items: center;
}
.scale {
width: 40%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.5.0/velocity.min.js"></script>
<script src="https://rawgit.com/julianshapiro/velocity/master/velocity.min.js"></script>
<div id="right">
<div class="scale">
<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 392 132" style="enable-background:new 0 0 392 132;" xml:space="preserve">
<g>
<path d="M76.1,26.4v100.7H44.5V26.2H20.2V3.6h80v22.8H76.1z"/>
<path d="M113,127.1V3.6h69.5v22.6H145v27.3h32.4v22.6h-32.2v28.2h38.4v22.8H113z"/>
<path d="M235.6,129.3c-26.7,0-40.7-11.9-40.7-34.6c0-4.5,0.2-6.6,1.1-11.5h26.4l-0.2,2.6c-0.2,2.3-0.2,4.5-0.2,6.6
c0,10.7,4.7,16.4,13.7,16.4c8.1,0,12.8-5.1,12.8-13.7c0-7.5-3.6-13-9.8-15.6l-12.4-4.9C204.5,65.9,197,56.3,197,37.7
C197,13.4,210.9,1,238.6,1c24.5,0,36.7,10,36.7,29.9c0,4.3-0.4,6.4-1.3,11.9h-26.5c0.6-4.5,0.8-6.2,0.8-8.8
c0-8.5-3.6-12.8-10.2-12.8c-6.4,0-10.9,4.9-10.9,11.9c0,7.2,3.6,10.9,14.7,15.4l15.8,6.2c15.8,6.4,23.7,18.1,23.7,35.4
C281.3,115.4,265.1,129.3,235.6,129.3z"/>
<path d="M345.5,26.4v100.7h-31.6V26.2h-24.3V3.6h80v22.8H345.5z"/>
</g>
</svg>
</div>
</div>

Animate a circle with the growth of <path> on scroll

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>

I want to place boxes of text and images on the corners of the path. How to do that?

I have defined a path over which a red dot moves on scrolling up and down. I want to place boxes of text and images on the corners of the path. So how do I go about doing that?
function positionTheDot() {
// What percentage down the page are we?
var scrollPercentage = (document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Get path length
var path = document.getElementById("theMotionPath");
var pathLen = path.getTotalLength();
// Get the position of a point at <scrollPercentage> along the path.
var pt = path.getPointAtLength(scrollPercentage * pathLen);
// Position the red dot at this point
var dot = document.getElementById("dot");
dot.setAttribute("transform", "translate(" + pt.x + "," + pt.y + ")");
};
// Update dot position when we get a scroll event.
window.addEventListener("scroll", positionTheDot);
// Set the initial position of the dot.
positionTheDot();
.verylong {
height: 2000px;
}
svg {
width: 1000px;
height: 1000px;
align: center;
}
body {
background-color: #333333;
}
h1 {
color: red;
font-size: 50px;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="/scripts/snippet-javascript-console.min.js?v=1"></script>
<svg viewBox="0 0 820 820" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M10 10 H 600 V 500 H 10 L 10 10" stroke="#d3d3d3" stroke-width="5" fill="none" id="theMotionPath"/>
<!--<circle cx="10" cy="110" r="3" fill="#000"/> <!--The bottom grey dot-->
<!-- <circle cx="110" cy="10" r="3" fill="#000"/> <!--The top grey dot-->
<!-- Red circle which will be moved along the motion path. -->
<circle cx="0" cy="0" r="5" fill="red" id="dot"/>
</svg>
<div class="verylong">
</div>
This is what I tried to do by adding div, I had added cx and cy in div but that didn't help.
div {
width: 320px;
padding: 10px;
border: 5px solid gray;
margin: 0;
}
</style>
</head>
<body>
<svg viewBox="0 0 120 120">
<svg viewBox="0 0 820 820" xmlns="http://www.w3.org/2000/svg"
version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M10 10 H 600 V 500 H 10 L 10 10" stroke="#d3d3d3" stroke-
width="5" fill="none" id="theMotionPath"/>
<!-- Red circle which will be moved along the motion path. -->
<circle cx="0" cy="0" r="5" fill="red" id="dot"/>
<div>This text is the actual content of the box.</div>
</svg>

Categories