I'm trying to generate this sort of shape within my design:
In which a canvas element appears in the middle part of the circle, and this kind of elongated hexagon appears around the outside.
However, I am having some issues with the shape - primarily how I would make the shape with a gradient or background image as well (i.e. the internal puzzle piece looking bit will be an image). The outer background will be a solid background colour (#222).
Current Attempt
My current attempt is using a background gradient to generate most of the shape, and using pseudo elements for the two circlular parts either side::
var a = 100; // size of the black hole.
var b = 200; // distance of black hole from canvas center.
var c = 1; // speed of black hole rotation.
var d = 20; // the amount of stars to spawn every frame.
// ---------------------------------------------
var canvas = document.getElementById('c'),
ctx = canvas.getContext('2d'),
stars = [],
m = {},
r = 0
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
m.x = null;
m.y = null;
ctx.strokeStyle = '#fff';
ctx.translate(0.5, 0.5);
// create stars
function createStars(n) {
if (m.x == null) return;
for (var i = 0; i < n; i++) {
var shape = {
x: m.x,
y: m.y,
r: 1,
speed: 1,
accel: 1.01,
accel2: 0.001,
angle: Math.random() * 360
}
var vel = {
x: a * Math.cos(shape.angle * Math.PI / 180),
y: a * Math.sin(shape.angle * Math.PI / 180)
};
shape.x += vel.x;
shape.y += vel.y;
stars.push(shape);
}
}
function render() {
createStars(d);
var bench = [];
// ctx.save();
// ctx.fillStyle = 'rgba(0,0,0,0.5)';
// ctx.fillRect(0, 0, canvas.width, canvas.height);
// ctx.restore();
ctx.clearRect(0, 0, canvas.width, canvas.height);
r += c;
if (r < 360) {
m = {
x: canvas.width / 2,
y: canvas.height / 2,
angle: r
}
var targetAngle = m.angle * Math.PI / 180;
m.x += b * Math.cos(targetAngle);
m.y += b * Math.sin(targetAngle);
} else {
r = 0;
}
while (stars.length) {
var star = stars.pop();
var vel = {
x: star.speed * Math.cos(star.angle * Math.PI / 180),
y: star.speed * Math.sin(star.angle * Math.PI / 180)
};
ctx.beginPath();
ctx.moveTo(star.x, star.y);
ctx.lineTo(star.x + vel.x, star.y + vel.y);
ctx.closePath();
ctx.stroke();
star.x += vel.x;
star.y += vel.y;
star.speed *= star.accel;
star.accel += star.accel2;
if (star.x < canvas.width && star.x > 0 && star.y < canvas.height && star.y > 0) {
bench.push(star);
}
}
stars = bench.slice(0).reverse();
}
window.requestAnimFrame = (function() {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
(function animloop() {
requestAnimFrame(animloop);
render();
})();
#import url(https://fonts.googleapis.com/css?family=Hammersmith+One);
html {
margin: 0;
padding: 0;
height: 100vh;
background: #222;
color: cornflowerblue;
overflow-x: hidden;
font-size: 18px;
font-family: 'Hammersmith One', sans-serif;
}
/************************************/
#c {
height: 200px;
width: 200px;
border-radius: 50%;
border: 5px double lightgray;
margin-left: 150px;
margin-right: 150px;
background: #222;
z-index: 10;
position: absolute;
left: 0px;
top: 50px;
}
.canwrap {
background: url(http://placekitten.com/g/300/300);
height: 300px;
width: 500px;
position: relative;
z-index: 0;
margin-left: 50px;
margin-right: 50px;
}
.canwrap:before {
content: "";
position: absolute;
width: 150px;
height: 100%;
top: 0;
left: 50%;
transform: translateX(-50%);
background: #222;
z-index: -1;
}
.canwrap:after {
content: "";
position: absolute;
height: 100px;
width: 100px;
top: 100px;
left: -50px;
background: red;
border-radius: 50%;
box-shadow: 500px 0 20px -5px red;
}
<div class="canwrap">
<canvas id="c" class="img"></canvas>
</div>
However, this leaves me with a few issues:
Background part can't be an image
circles can't replicate the image part
gradient stop colours means there is not a 'clear edge' to the shape.
I would be really grateful to get another view/angle on constructing this shape, as I haven't been able to produce this in a way that I need to.
I know svg might be the way to do this, but I've always had a hard time getting svg's to animate properly compared to css - but if either work, I'd be happy (seeing as my design doesn't exactly allow my image design)!
As always, any suggestions would be great.
How about something like this i used svg Fiddle
<svg width="1000" height="500">
<polygon points="100 10, 50 245, 100 500, 200 500, 200 10,100 10" fill="orange" />
<polygon points="400 10, 460 245, 400 500, 300 500, 300 10,200 10" fill="orange" />
<circle r="50" cx="50" cy="245" fill="orange" />
<circle r="50" cx="460" cy="245" fill="orange" />
<circle r="100" cx="255" cy="245" fill="midnightblue" />
</svg>
You can also do it with image as background
<svg width="1000" height="500">
<defs>
<pattern id="pattern" patternUnits="userSpaceOnUse" width="1000" height="1000">
<image xlink:href="https://placekitten.com/g/200/300" x="-200" y="-10" width="1000" height="1000" />
</pattern>
</defs>
<polygon points="100 10, 50 245, 100 500, 200 500, 200 10,100 10" fill="url(#pattern)"/>
<polygon points="400 10, 460 245, 400 500, 300 500, 300 10,200 10" fill="url(#pattern)"/>
<circle r="50" cx="50" cy="245" fill="url(#pattern)"/>
<circle r="50" cx="460" cy="245" fill="url(#pattern)"/>
<circle r="100" cx="255" cy="245" fill="midnightblue"/>
although this is not for canvas but rather for html element you can do the same technique in canvas.
I assume you already know basic css 3,now since it's a lot of work to code, I'll give you the instructions:
1) head out to this link
2) create 2 Trapezoids and rotate them 90 degrees
3) create 2 small circles and position each one (relative/absolute) on the edge of the Trapezoid with z-index smaller than the Trapezoids
4) 1 circle in the middle and your done!
You can use svg, which simplifies the creation of shape to a large extent.
Create a path, and fill in pattern.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" viewBox="0 0 65 50" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<pattern id="image" patternUnits="userSpaceOnUse" width="100" height="100">
<image xlink:href="http://www.placecage.com/g/200/300" width="50" x="-10" y="0" height="50"/>
<image xlink:href="http://www.placekitten.com/g/200/300" width="50" x="30" y="0" height="50"/>
</pattern>
</defs>
<path d="M 25 0 H 15 L 10 20 A 5.3 5.3 0 1 0 10 30 L15 50 H25 z M 40 0 H 50 L 55 20 A 5.3 5.3 0 1 1 55 30 L 50 50 H 40 z" fill="url(#image)" stroke="none"/>
</svg>
Height and width have been set to 100%, so this should be responsive. Also, the position of images can be changed using x,y attributes.
Related
I have created a design for angular circular progress bar. The issue is that i want to give the percentage value dynamic . I have got the dynamic value from API but i have no idea how should i implement it to create a circular progress bar using SVG. I'll share my html skeleton and the css i use.
.track,
.filled {
stroke-width: 10;
fill: none;
}
.track {
stroke: rgba(160, 215, 231, 0.85);
}
.filled {
stroke: blue;
stroke-dashoffset: 202;
stroke-dasharray: 440;
}
<div class="col-lg-3">
<div class="iconbox-singleD7 dashboard-height">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-35 0 190 190" class="mw">
<circle class="track" cx="60" cy="60" r="55" />
<text x="40" y="60" fill="#fff">Patients</text>
<text x="50" y="75" fill="#fff">{{totalPatient}}</text>
<circle class="filled" cx="60" cy="60" r="55" />
<circle cx="30" cy="136" r="5" stroke="blue" stroke-width="3" fill="blue" /><text x="40" y="140" fill="#fff">Male {{malePercentage}}%</text>
<circle cx="30" cy="152" r="5" stroke="rgba(160, 215, 231, 0.85)" stroke-width="3" fill="rgba(160, 215, 231, 0.85)" /><text x="40" y="155" fill="#fff">Female {{femalePercentage}}%</text>
</svg>
</div>
</div>
What i want is that. Lets say i get a male percentage = 70% or any value from an API i want to set the progress bar according to the percentage i get from API. How can i achieve that .i'm not familier with SVG. So any help will be appreciated. Thanks
Hello here is the code pen: https://codepen.io/Echyzen/pen/LYBraGB
var percentage = 75;
var a = -3.46;
var b = 440;
function changePer() {
const finalOffset = Math.round(a*percentage+b)
const concernedCircle = document.getElementsByClassName('filled')[0];
concernedCircle.style.strokeDashoffset = finalOffset;
}
which extract the a and b of the affine function.
You have to click on the button to execute the 75% percentage.
Or using querySelector:
var percentage = 70;
var a = -3.46;
var b = 440;
function changePer() {
const finalOffset = Math.round(a*percentage+b)
const concernedCircle = document.querySelector('.filled');
concernedCircle.style.strokeDashoffset = finalOffset;
}
Start with the <progress-circle> native Web Component I built : https://pie-meister.github.io/
It is unlicensed Open Source code; do with it whatever your want. Documentation at Dev.to
<progress-circle value="100%">Web Components</progress-circle>
<progress-circle value="71%" stroke="red">React</progress-circle>
<progress-circle value="90%" stroke="orange">Others</progress-circle>
<script src="https://pie-meister.github.io/PieMeister-with-Progress.min.js"></script>
<style>
progress-circle {
font-family: Arial;
width: 180px; /* StackOverflow viewport */
}
progress-circle::part(label) {
font-size:70px;
}
</style>
I am trying to position a scale with numbers and lines on the top of my black half-circle gauge. The problem is that I have two groups of elements that do not fit with each other in the same group .. Thus the local coordinate system of the scale element is located on the bottom and I cannot use transform functions to place it in the center..
var r = 400;
var circles = document.querySelectorAll('.circle');
var total_circles = circles.length;
for (var i = 0; i < total_circles; i++) {
circles[i].setAttribute('r', r);
}
var meter_dimension = (r * 2) + 100;
var wrapper = document.querySelector('#wrapper');
wrapper.style.width = meter_dimension + 'px';
wrapper.style.height = meter_dimension + 'px';
var cf = 2 * Math.PI * r;
var semi_cf = cf / 2;
var z = 40 * Math.PI;
document.querySelector('#outline_ends')
.setAttribute('stroke-dasharray', 2 + ',' + (semi_cf - 2)); -
document.querySelector('#mask')
.setAttribute('stroke-dasharray', semi_cf + ',' + cf);
document.querySelector('#low')
.setAttribute('stroke-dasharray', semi_cf - z + ',' + cf);
for (i = 0; i <= 180; i = i + 10) {
var new_tick = noon.cloneNode(true);
new_tick.getElementsByTagName('text')[0].textContent = i;
new_tick.removeAttribute("id");
new_tick.setAttribute("transform", "rotate(" + i + " 0 0)");
gauge.appendChild(new_tick);
}
body {
background-color: grey;
}
#wrapper {
position: relative;
margin: auto;
}
#meter {
width: 100%;
height: 100%;
transform: rotateX(180deg);
}
.circle {
fill: none;
}
#mask {
stroke: yellow;
stroke-width: 60;
}
.black {
fill: none;
stroke: #000000;
stroke-width: 30;
}
.range {
stroke-width: 60;
}
.scale {
stroke: #ffffff;
}
rect {
fill: transparent;
}
<div id="wrapper">
<svg id="meter">
<g class="scale">
<circle id="mask" class="circle" cx="50%" cy="50%">
</circle>
<circle id="low" class="black" cx="50%" cy="50%" r="360">
</circle>
<circle id="outline_ends" class="circle outline" cx="50%" cy="50%">
</circle>
<g id="gauge" transform="rotate(-90)">
<g id="noon">
<rect x="-10" y="-220" width="20" height="100" />
<line x1="0" y1="-190" x2="0" y2="-180" />
<text x="0" y="-200"></text>
</g>
</g>
</g>
</svg>
</div>
This is an example of positioning a group in SVG:
You put the group in the <defs> and you reuse it with <use>. The <use> element may have a x and a y attributes that are defining the new position.
svg{border:1px solid; max-height:100vh;}
<svg viewBox="0 0 200 75">
<defs>
<g id="g1">
<rect width="20" height="20" />"
</g>
</defs>
<use xlink:href ="#g1" x="100" y="50"/>
<use xlink:href ="#g1" x="50" y="10" fill="red"/>
</svg>
In the example above I draw the same group twice, every time in a different position.
I am having troubles to scale the SVG to fit the window size.
In this example, I have a wavy path and a text element, what I want to achieve here is to move the text element along the wavy path from left to right (which is done by GSAP) and stop in the half way of the path in the initial load; it will move to the end when users start scrolling.
My problem is that, the wavy path created by SVG is too long, even the half way of the path will be off the window, I tried to scale down the wavy path by using viewBox, failed; using css width: 100vw, failed.
I also used the css transform property, it did scale the wavy path down, but that was a fixed size, I want to make it as responsive as possible, which means regardless the window width, the text element always stops in the middle of the screen first ( half way of the wavy path ), then moves to the right hand side of the screen. Is this possible when using inline SVG element? If so, please point me in the right direction.
Thank you in advance!
(Please view the example in full page mode, that will explain my problem perfectly, but the scroll function will be disabled because the height in that mode is 100vh, there is no room for scrolling)
document.getElementById("MyPath").setAttribute("d", document.getElementById("Path").getAttribute("d"));
var tl = new TimelineMax({
repeat: 0,
delay: 1
});
tl.to("#Text", 3, {
attr: {
startOffset: '50%',
opacity: 1
}
});
window.addEventListener('scroll', function() {
tl.to("#Text", 3, {
attr: {
startOffset: '100%',
opacity: 0
}
});
}, true);
#import url(https://fonts.googleapis.com/css?family=Oswald);
body {
background-color: #222;
}
svg {
overflow: visible;
width: 100%;
height: 100%;
background-color: #fff;
left: 0;
top: 0;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/plugins/TextPlugin.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/TweenMax.min.js"></script>
<svg xml:space="preserve">
<defs><path id="MyPath"/></defs>
<path id="Path" fill="none" stroke="#000" stroke-miterlimit="10" d="M0,188.2c186.2,84,261.8,84.9,440,66.4s295.2-130,535.2-129.4c240,0.6,357,144.3,591.1,144.3
s450.1-141.2,651.1-141.2c271.7,0,354.2,141.2,612.1,141.2c240,0,423-141.2,669.1-141.2c119.1,0,202.3,33.8,281,68.7"/>
<text font-size="7em" >
<textPath id="Text" fill='#88CE02' font-family=Oswald xlink:href="#MyPath" opacity=0 startOffset="0%" letter-spacing="5px">Love the little things.</textPath>
</text>
</svg>
If you want your SVG to scale to fit the screen (or any parent container), it needs to have a viewBox attribute. This attribute tells the browser the dimensions of the SVG content. Without it, the browser has know way of knowing how much it needs to be scaled.
Your path is about 3780 width, and the bottom of it is at y=144. So a reasonable value of viewBox would be:
viewBox="0 0 3780 150"
document.getElementById("MyPath").setAttribute("d", document.getElementById("Path").getAttribute("d"));
var tl = new TimelineMax({
repeat: 0,
delay: 1
});
tl.to("#Text", 3, {
attr: {
startOffset: '50%',
opacity: 1
}
});
window.addEventListener('scroll', function() {
tl.to("#Text", 3, {
attr: {
startOffset: '100%',
opacity: 0
}
});
}, true);
#import url(https://fonts.googleapis.com/css?family=Oswald);
body {
background-color: #222;
}
svg {
overflow: visible;
width: 100%;
height: 100%;
background-color: #fff;
left: 0;
top: 0;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/plugins/TextPlugin.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/TweenMax.min.js"></script>
<svg viewBox="0 0 3780 150" xml:space="preserve">
<defs><path id="MyPath"/></defs>
<path id="Path" fill="none" stroke="#000" stroke-miterlimit="10" d="M0,188.2c186.2,84,261.8,84.9,440,66.4s295.2-130,535.2-129.4c240,0.6,357,144.3,591.1,144.3
s450.1-141.2,651.1-141.2c271.7,0,354.2,141.2,612.1,141.2c240,0,423-141.2,669.1-141.2c119.1,0,202.3,33.8,281,68.7"/>
<text font-size="7em" >
<textPath id="Text" fill='#88CE02' font-family=Oswald xlink:href="#MyPath" opacity=0 startOffset="0%" letter-spacing="5px">Love the little things.</textPath>
</text>
</svg>
Here's what you could do: add a media query defining the desired width and then apply the following styles separately for the text and path elements inside the svg element:
text {
font-size: 2em;
}
path {
-webkit-transform: scale(.3);
-ms-transform: scale(.3);
transform: scale(.3);
}
See the snippet below:
document.getElementById("MyPath").setAttribute("d", document.getElementById("Path").getAttribute("d"));
var tl = new TimelineMax({
repeat: 0,
delay: 1
});
tl.to("#Text", 3, {
attr: {
startOffset: '50%',
opacity: 1
}
});
window.addEventListener('scroll', function() {
tl.to("#Text", 3, {
attr: {
startOffset: '100%',
opacity: 0
}
});
}, true);
#import url(https://fonts.googleapis.com/css?family=Oswald);
body {
background-color: #222;
}
svg {
overflow: visible;
width: 100%;
height: 100%;
background-color: #fff;
left: 0;
top: 0;
position: absolute;
}
#media only screen and (max-width: 500px) {
text {
font-size: 2em;
}
path {
-webkit-transform: scale(.3);
-ms-transform: scale(.3);
transform: scale(.3);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/plugins/TextPlugin.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/TweenMax.min.js"></script>
<svg xml:space="preserve">
<defs><path id="MyPath"/></defs>
<path id="Path" fill="none" stroke="#000" stroke-miterlimit="10" d="M0,188.2c186.2,84,261.8,84.9,440,66.4s295.2-130,535.2-129.4c240,0.6,357,144.3,591.1,144.3
s450.1-141.2,651.1-141.2c271.7,0,354.2,141.2,612.1,141.2c240,0,423-141.2,669.1-141.2c119.1,0,202.3,33.8,281,68.7"/>
<text font-size="7em" >
<textPath id="Text" fill='#88CE02' font-family=Oswald xlink:href="#MyPath" opacity=0 startOffset="0%" letter-spacing="5px">Love the little things.</textPath>
</text>
</svg>
I hope it helps!
I'm trying to center the <path> animation horizontally and vertically (after having scaled it down with transform="scale(0.5)) while keeping the <svg> at 100% width and height of its container. I'm using Snap.svg.
As you can see below, the <svg> is fine but the <path> is all crammed up in the upper left corner.
var s = Snap("#me");
var myPath = s.select("#mypath");
function reset( el ) {
el.stop();
el.attr({ "stroke-dashoffset": 125 });
};
function startAnim( el ) {
el.animate( { "stroke-dashoffset": 600 }, 1000 );
};
reset( myPath );
s.mouseover( function() {
startAnim( myPath );
} );
s.mouseout( function() {
reset( myPath );
} );
.test {
border: 1px solid black;
height: 500px;
width: 500px;
}
#me {
border: 2px solid green;
}
#mypath {
border: 2px solid blue;
display: flex;
justify-content: center;
align-content: center;
flex-direction: column;
}
<script src="http://tedbeer.net/lib/snap.svg-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test">
<div class="pie">
<svg id="me" viewBox="0 0 350 350">
<!--<svg id="me" viewBox="0 0 350 350" width="100" height="100">-->
<path id="mypath" d="M 175, 175 m 0, -75 a 75, 75 0 1, 0 0, 150 a 75, 75 0 1, 0 0, -150" fill="none" stroke="#ccc" stroke-width="150" stroke-dasharray="0 600 600 0" stroke-dashoffset="1000" transform="scale(0.5)">
</path>
</svg>
</div>
</div>
If you want to amend the transform via Snap you would do it very slightly different.
The end result is probably the same to the browser as mefs solution, as it will end up having a similar matrix on the element itself, its really down to whats the most convenient.
You can specify a center point for the scaling, just add it after the scale x & y parameters.
myPath.transform('s0.5,0.5,175,175');
jsfiddle
Edit: Actually I'm an idiot, its even more simple. I just remembered that Snap automatically uses the center of an object on transform strings if not specified. So in fact you can reduce it to this..
myPath.transform('s0.5');
jsfiddle
The position of your object is affected by the scaling transform. Add a translation in order to move the object to the correct position, right after scale:
transform="scale(0.5)translate(175,175)"
updated snippet:
var s = Snap("#me");
var myPath = s.select("#mypath");
function reset( el ) {
el.stop();
el.attr({ "stroke-dashoffset": 125 });
};
function startAnim( el ) {
el.animate( { "stroke-dashoffset": 600 }, 1000 );
};
reset( myPath );
s.mouseover( function() {
startAnim( myPath );
} );
s.mouseout( function() {
reset( myPath );
} );
.test {
border: 1px solid black;
height: 500px;
width: 500px;
}
#me {
border: 2px solid green;
}
#mypath {
border: 2px solid blue;
display: flex;
justify-content: center;
align-content: center;
flex-direction: column;
}
<script src="http://tedbeer.net/lib/snap.svg-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test">
<div class="pie">
<svg id="me" viewBox="0 0 350 350">
<!--<svg id="me" viewBox="0 0 350 350" width="100" height="100">-->
<path id="mypath" d="M 175, 175 m 0, -75 a 75, 75 0 1, 0 0, 150 a 75, 75 0 1, 0 0, -150" fill="none" stroke="#ccc" stroke-width="150" stroke-dasharray="0 600 600 0" stroke-dashoffset="1000" transform="scale(0.5)translate(175,175)">
</path>
</svg>
</div>
</div>
Edit: possible other options based on the question in your comment:
option 1
If you do not want to have the translate at all, You can change the viewBox coordinates, this way:
<svg id="me" viewBox="-75 -75 350 350" width="100%" height="100%">
Please note that I haven't taken the time to calculate the exact ones that would be relevant to you, but you get the idea. You should adapt the four values so that they match to the viewport you want to obtain.
option 2
do some math to replace your current path coordinates by the ones for an object twice smaller, so that you don't need the scale transform anymore.
I've read a bunch of posts and been playing around with trying to animate a clip-path. At this point I can only get it show the clip-path in Firefox and it always pins the path to 0,0.
Here is an example code -> http://jsfiddle.net/justintense/4torLok9/1/
I'm using a simple inline SVG as the path:
<clipPath id="clipping">
<polygon fill="#FFFFFF" points="40,35 40,15 20,35 "/>
<polygon fill="#FFFFFF" points="0,35 20,35 0,15 "/>
<circle id="white_overlay_9_" fill="#FFFFFF" cx="20" cy="18.77" r="7.393"/>
</clipPath>
I'm unsure if what I trying to do is even possible so am just looking for pointer so I can get this one track.
Edit:
I'm trying to recreate the behaviour similar to this site --------> http://uppymama.com/
Defining the clipPath:
You don't need three elements in your clipPath, you could simplify it to a single path element.
<path d="M0,0 L40,0 L20,15z M20,5 m-5,0 a5,5 0 1,0 10,0 a5,5 0 1,0 -10,0z" />
Applying the clipPath to the div:
For applying the clip-path, if you want maximum browser support, you could import the div into an svg element through svg's foreignObject element. Then, apply the clip-path to the foreignObject.
Here's the browser support, if you apply the clip-path through CSS.
Animating the clipPath:
Now, for aligning the pointer right in the middle of the menu item, you'll need JavaScript to do some calculations and animate the clipPath's transform property.
When page loads calculate the distance from the left window corner to the first menu-item and move the clip-path to that co-ordinate.
When the window resizes, calculate the distance again and move the pointer.
When the menu item is clicked, calculate the distance and animate the pointer using setTimeout() and synchronous code until it reaches the correct position.
Synchronous code means that the operations are performed one at a time and that, until one operation is over, the code execution is blocked from moving onto the next operation.
Exact replica of that site:
As you can see on that site, the pointer isn't centered.
My solution will always center the pointer depending on the width of the menu item.
Demo on CodePen
var items = document.getElementsByClassName('menu-item');
var clipPath = document.getElementById('clip-path');
var lastActive = items[0];
for (i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
var x = this.getBoundingClientRect().left + (this.offsetWidth / 2) - 20;
animStart(x);
lastActive = this;
})
}
function animStart(end) {
for (start = 0; start <= end; start += 0.1) {
anim(start);
}
}
function anim(start) {
setTimeout(function() {
clipPath.setAttribute('transform', 'translate(' + start + ', 0)');
}, 1 * start);
}
function align() {
var x = lastActive.getBoundingClientRect().left + (lastActive.offsetWidth / 2) - 20;
animStart(x);
}
function resize() {
var x = lastActive.getBoundingClientRect().left + (lastActive.offsetWidth / 2) - 20;
clipPath.setAttribute('transform', 'translate(' + x + ', 0)');
}
window.onresize = resize;
window.onload = align;
body {
background: #222222;
margin: 0;
}
.nav-container {
top: 20px;
position: relative;
width: 100%;
height: 50px;
background: repeating-linear-gradient(90deg, palegoldenrod 5px, palegreen 10px, palegreen 15px, paleturquoise 15px, paleturquoise 20px, plum 20px, plum 25px);
}
.nav {
width: 100%;
height: 50px;
margin: 0 auto;
text-align: center;
}
.menu-item {
display: inline-block;
width: 70px;
padding: 17px 20px;
color: #222222;
font-size: 14px;
}
.menu-item:hover {
color: seagreen;
cursor: pointer;
}
#bottom {
width: 100%;
height: 35px;
background: repeating-linear-gradient(90deg, palegoldenrod 5px, palegreen 10px, palegreen 15px, paleturquoise 15px, paleturquoise 20px, plum 20px, plum 25px);
}
#clip-path {
transition: 0.5s transform;
}
<body>
<div class="nav-container">
<div class="nav">
<div id="menu-item-1" class="menu-item">Home</div>
<div id="menu-item-2" class="menu-item">About</div>
<div id="menu-item-3" class="menu-item">Services</div>
<div id="menu-item-4" class="menu-item">Locations</div>
<div id="menu-item-5" class="menu-item">Contact Us</div>
</div>
<svg style="position: relative; top: -1px;" class="svg-defs" height="35" width="100%">
<defs>
<clipPath id="clipping">
<path id="clip-path" transform="translate(0,0)" d="M0,0 L40,0 L20,15z M20,6 m-5,0 a5,5 0 1,0 10,0 a5,5 0 1,0 -10,0z" />
</clipPath>
</defs>
<foreignObject x="0" y="0" clip-path="url(#clipping)" height="35" width="100%">
<div id="bottom"></div>
</foreignObject>
</svg>
</div>
</body>
Even Better:
Retaining the last position of the pointer.
Demo on CodePen
var items = document.getElementsByClassName('menu-item');
var clipPath = document.getElementById('clip-path');
var last = items[0];
for (i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
var x = this.getBoundingClientRect().left + (this.offsetWidth / 2) - 20;
var currentX = clipPath.getAttribute('transform').replace('translate(', '');
currentX = parseInt(currentX.replace(', 0)', ''), 10);
animStart(currentX, x);
last = this;
})
}
function animStart(begin, end) {
if (begin < end) {
for (start = begin; start <= end; start += 0.1) {
anim(start);
}
} else {
var c = end;
for (start = begin; end <= start; start -= 0.1) {
animD(start, c);
c += 0.1
}
}
}
function anim(start) {
setTimeout(function() {
clipPath.setAttribute('transform', 'translate(' + start + ', 0)');
}, 1 * start);
}
function animD(start, c) {
setTimeout(function() {
clipPath.setAttribute('transform', 'translate(' + start + ', 0)');
}, 1 * c);
}
function align() {
var x = last.getBoundingClientRect().left + (last.offsetWidth / 2) - 20;
animStart(0, x);
}
function resize() {
var x = last.getBoundingClientRect().left + (last.offsetWidth / 2) - 20;
clipPath.setAttribute('transform', 'translate(' + x + ', 0)');
}
window.onresize = resize;
window.onload = align;
body {
background: #222222;
margin: 0;
}
.nav-container {
top: 20px;
position: relative;
width: 100%;
height: 50px;
background: repeating-linear-gradient(90deg, palegoldenrod 5px, palegreen 10px, palegreen 15px, paleturquoise 15px, paleturquoise 20px, plum 20px, plum 25px);
}
.nav {
width: 600px;
height: 50px;
margin: 0 auto;
text-align: center;
}
.menu-item {
display: inline-block;
width: 70px;
padding: 17px 20px;
color: #222222;
font-size: 14px;
}
.menu-item:hover {
color: seagreen;
cursor: pointer;
}
#bottom {
width: 100%;
height: 35px;
background: repeating-linear-gradient(90deg, palegoldenrod 5px, palegreen 10px, palegreen 15px, paleturquoise 15px, paleturquoise 20px, plum 20px, plum 25px);
}
<body>
<div class="nav-container">
<div class="nav">
<div id="menu-item-1" class="menu-item">Home</div>
<div id="menu-item-2" class="menu-item">About</div>
<div id="menu-item-3" class="menu-item">Services</div>
<div id="menu-item-4" class="menu-item">Locations</div>
<div id="menu-item-5" class="menu-item">Contact Us</div>
</div>
<svg style="position: relative; top: -1px;" class="svg-defs" height="35" width="100%">
<defs>
<clipPath id="clipping">
<path id="clip-path" transform="translate(0,0)" d="M0,0 L40,0 L20,15z M20,6 m-5,0 a5,5 0 1,0 10,0 a5,5 0 1,0 -10,0z" />
</clipPath>
</defs>
<foreignObject x="0" y="0" clip-path="url(#clipping)" height="35" width="100%">
<div id="bottom"></div>
</foreignObject>
</svg>
</div>
</body>