Related
I'd like to give Clip-path Musemove Effects.
If it's a circle, you can give a var value like this in css
clip-path: circle(40% at var(--x, 50%) var(--y, 50%));
But I don't know how to enter the var value if url goes in.
clip-path: url(#heart);
Is there any workaround?
<script>
const htmlElem = document.documentElement;
document.addEventListener('mousemove', onDocumentMousemove);
function onDocumentMousemove(evt) {
htmlElem.style.setProperty('--x', `${evt.clientX}px`);
htmlElem.style.setProperty('--y', `${evt.clientY}px`);
}
</script>
jsfiddle
In the case of circle - It's working.
jsfiddle
In the case of url - It's not working.
Use the SVG as mask instead.
const htmlElem = document.documentElement;
document.addEventListener('mousemove', onDocumentMousemove);
function onDocumentMousemove(evt) {
htmlElem.style.setProperty('--x', `${evt.clientX - 100}px`);
htmlElem.style.setProperty('--y', `${evt.clientY - 100}px`);
}
html,
body {
height: 100%;
margin: 0;
}
body {
font-family: Monaco;
font-size: 12px;
color: rgba(0, 0, 0, .7);
}
.container {
position: relative;
width: 100%;
--m: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M42 27v-20c0-3.7-3.3-7-7-7s-7 3.3-7 7v21l12 15-7 15.7c14.5 13.9 35 2.8 35-13.7 0-13.3-13.4-21.8-26-18zm6 25c-3.9 0-7-3.1-7-7s3.1-7 7-7 7 3.1 7 7-3.1 7-7 7z" /></svg>' ) ;
-webkit-mask: var(--m) var(--x, 50%) var(--y, 50%) no-repeat;
mask: var(--m) var(--x, 50%) var(--y, 50%) no-repeat;
background-color: blue;
}
img {
max-width:100%;
}
<div class="container">
<img src="https://www.typotheque.com/ssadmin/assets/FontImageGenerator/AaImage29.png">
</div>
In my vuejs2/html app based on external template svg icons
are used, like :
<svg class="icon icon-plus">
<use xlink:href="/img/icons.svg#plus" />
</svg>
<svg class="icon icon-basket">
<use xlink:href="/img/icons.svg#basket" />
</svg>
File icons.svg has 20 KiB in size and I wonder
if there is a way to check which icons are available in this
file? How can I check it? I need to of close, like ”x", but making
icons.svg#close
I failed.
Can I modify color/background color of these icons? I need to modify it with css propoties and set custom colors.
Are there some available resource easy to use to create such svg files?
Thanks!
Most modern svg icon libraries (feather-icons, fontAwesome etc.) provide something like a "cheat sheet" showing all available icons.
Besides, they will most likely provide an api for embedding and styling (like changing icon colors).
If you can't find any documentation, you could try to create an visual overview like so:
let svgIcons = document.querySelector('#svgIcons');
let symbols = svgIcons.querySelectorAll('symbol[id]');
let symbolUseHtml = '';
symbols.forEach(function(el, i){
let id = el.id;
let viewBox = el.getAttribute('viewBox');
let viewBoxAtt = viewBox ? 'viewBox="'+viewBox+'"' : '';
symbolUseHtml +=
'<div class="icon-wrp"><svg class="icon icon-'+id+'" id="icon-'+id+'" '+viewBoxAtt+'><use href="#'+id+'"></svg><p>'+id+'</p></div>';
});
symbolUseHtml+='';
document.body.insertAdjacentHTML('beforeend', symbolUseHtml);
.icon-wrp{
display:inline-block;
font-size:20px;
padding:0.5em;
border:1px solid #ccc
}
#icon-home{
fill: red;
}
#icon-close{
fill: green;
}
<svg id="svgIcons" viewBox="0 0 100 100" style="display:none" xmlns="http://www.w3.org/2000/svg">
<symbol id="home" viewBox="0 0 34 48">
<path d="M33.16,28.12h-5.2v13h-3.44v-16.72l-7.72-8.72l-7.72,8.72v16.72h-3.44v-13h-5.24l16.4-17.4Z" />
</symbol>
<symbol id="close" viewBox="0 0 27 48">
<path d="M26.16,17.92l-10.44,10.44l10.44,10.44l-2.44,2.44l-10.44-10.44l-10.44,10.44l-2.44-2.44l10.44-10.44l-10.44-10.44l2.44-2.44l10.44,10.44l10.44-10.44Z" />
</symbol>
</svg>
Most likely querying for <symbol> elements will do the trick – as #chrwahl recommended.
Unfortunately, there is no standardized concept, how icon libraries are referencing icon assets:
Some might use nested svg elements or elements wrapped in a <def>.
Here is a draft of a visual "icon Inspector" to illustrate the different flavors:
Icon inspector example
let svgIcons = document.querySelector(".svgIcons");
let svgMarkup = document.querySelector("#svgMarkup");
let svgUseCode = document.querySelector("#svgUseCode");
let svgUseCodeID = document.querySelector("#svgUseCodeID");
let svgIconAssets = document.querySelector(".svgIconAssets");
let iconType = "";
let iconHtml = "";
// default example svg
let svgSrc = `<svg id="svgIcons" class="svgIcons" viewBox="0 0 100 100" style="display:none" xmlns="http://www.w3.org/2000/svg">
<symbol id="home" viewBox="0 0 34 48">
<path d="M33.16,28.12h-5.2v13h-3.44v-16.72l-7.72-8.72l-7.72,8.72v16.72h-3.44v-13h-5.24l16.4-17.4Z" />
</symbol>
<symbol id="close" viewBox="0 0 27 48">
<path d="M26.16,17.92l-10.44,10.44l10.44,10.44l-2.44,2.44l-10.44-10.44l-10.44,10.44l-2.44-2.44l10.44-10.44l-10.44-10.44l2.44-2.44l10.44,10.44l10.44-10.44Z" />
</symbol>
</svg>`;
svgMarkup.value = svgSrc;
loadSvg(svgSrc);
let svgSrcSelect = document.querySelectorAll(".trackChange");
svgSrcSelect.forEach(function (el, i) {
el.addEventListener("change", function (e) {
svgSrc = e.target.value;
loadSvg(svgSrc);
});
});
function loadSvg(src) {
// if external src
if (src.indexOf("<svg") == -1) {
fetch(src)
.then((response) => response.text())
.then((data) => {
// add temporary svg to DOM
svgIcons.innerHTML = data;
renderIcons(data);
})
.catch((error) => {
let errorData ='<svg class="icon" viewBox="0 0 100 100"><symbol id="error" viewBox="0 0 100 100" ><text x="50" y="50" width="100" text-anchor="middle" style="font-size:25%; fill:red">Not available</text></symbol></svg>';
svgIcons.innerHTML = errorData;
renderIcons(errorData);
})
}
// if src is svg markup
else {
svgIcons.innerHTML = src;
renderIcons(src);
}
}
function renderIcons(data) {
let svgOuter = svgIcons.querySelector("svg");
let nested = svgOuter.querySelectorAll("svg");
let symbols = svgOuter.querySelectorAll("symbol");
let icons = symbols.length ? symbols : nested;
let nonPathEls = svgIcons.querySelectorAll("polyline, line");
iconType = icons == symbols ? "symbols" : "nested svgs";
iconHtml = "";
icons.forEach(function (el, i) {
let id = el.id ? el.id : "";
let idAtt = id ? ' id="icon-'+id+'" ' : '';
/**
* if an icon doesn't have an id –
* try to fetch the last class name
**/
let classAtt = !id ? el.classList : "";
let classLast = classAtt ? [].slice.call(classAtt).pop() : "";
let viewBox = el.getAttribute("viewBox");
let viewBoxAtt = viewBox ? 'viewBox="' + viewBox + '"' : "";
let iconDef = id ? document.querySelector("#" + id) : "";
let iconAsset = iconDef ? iconDef : el;
let iconUseHtml = "";
/**
* if icon defs contain other elements
* than paths – like polyline or line –
* maybe they're intended to have outlines/strokes
* but no fill (like in feather-icons)
**/
if (iconAsset) {
let children = iconAsset.childNodes;
if (children.length && nonPathEls.length) {
children.forEach(function (sub, i) {
sub.classList.add("icon-inner-stroke");
});
}
let iconInner = iconAsset ? iconAsset.innerHTML : "";
let iconSelector = !id ? "class: " + '<strong>'+classLast+'</strong>' : "id: " + '<strong>'+id+'</strong>';
if (id) {
iconUseHtml =
'<svg class="icon-use icon-use-' +
id +
idAtt+
viewBoxAtt +
">\n" +
'<use href="' +
id +
'" />\n' +
"</svg>\n";
}
let dataUse = iconUseHtml
? 'data-use="' + encodeURIComponent(iconUseHtml) + '" '
: 'data-use=""';
iconHtml +=
'<div class="icon-wrp" ' +
dataUse +
" >" +
'<svg class="icon icon-' +
id + '" '+
idAtt+
viewBoxAtt +
">" +
iconInner +
"</svg>" +
'<p class="icon-label">' +
iconSelector +
"<br>type: " +
iconType +
"</p>" +
"</div>";
//console.log(svgIconAssets)
}
});
svgIconAssets.innerHTML = iconHtml;
let svgIconWrp = svgIconAssets.querySelectorAll(".icon-wrp");
svgIconWrp.forEach(function (el, i) {
el.addEventListener("click", function (e) {
let currentIcon = e.target.closest(".icon-wrp");
let iconCode = '<!-- not selectable by id -->\n'+currentIcon.innerHTML;
let useCode = currentIcon.getAttribute("data-use");
useCode = useCode ? decodeURIComponent(useCode) : iconCode;
svgUseCode.value = useCode;
svgUseCodeID.innerHTML =
"<strong>" + currentIcon.querySelector("svg").id + "</strong>";
});
});
}
body {
margin: 0;
font-family: "Segoe UI";
}
* {
box-sizing: border-box;
font-family: inherit;
}
:root {
--svgHover: red;
}
::-webkit-scrollbar {
width: 0.45em;
border-radius: 0.5em;
background: #ddd;
}
::-webkit-scrollbar-track {
border-radius: 0.5em;
}
::-webkit-scrollbar-thumb {
background: #aaa;
border-radius: 0.5em;
}
.layout {
display: flex;
height: 100vh;
overflow: hidden;
}
.svgIconAssets {
display: flex;
flex-wrap: wrap;
}
.icon-wrp {
width: 10vw;
font-size: 4vw;
margin-bottom: 0.5em;
text-align: center;
}
.icon-wrp * {
text-align: center;
}
.icon-label {
font-size: 12px;
text-align: center;
margin: 0;
}
.icon {
display: inline-block;
height: 1em;
font-size: 1em;
color: #999;
fill: #999;
cursor:pointer;
&:hover {
color: var(--svgHover);
fill: var(--svgHover);
}
}
.icon-inner-stroke {
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
input {
width: 100%;
}
label{
font-size:0.8rem;
line-height: 1.2em;
margin-bottom:0.25rem;
}
.svgDisplay {
width: 75%;
height: 100%;
position: relative;
overflow: auto;
}
.frm-wrp {
padding: 1em;
margin-left: auto;
width: 20%;
background-color: #ccc;
display: flex;
flex-direction: column;
flex: 1;
* {
width: 100%;
}
textarea {
min-height: 5em;
height: 90%;
white-space: pre-wrap;
font-family: monospace;
}
input, textarea{
margin-bottom: 0.5rem;
}
}
<div class="layout">
<div class="svgDisplay">
<div class="svgIcons" style="display:none"></div>
<div class="svgIconAssets"></div>
</div>
<div class="frm-wrp">
<label>Svg URL</label>
<input type="text" id="svgSrcSelect" class="svgSrcSelect trackChange" list="dataSrc" placeholder="select example or paste svg url" />
<datalist id="dataSrc">
<option value="https://unpkg.com/feather-icons#4.28.0/dist/feather-sprite.svg">Feather icons</option>
<option value="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/sprites/regular.svg">FontAwesome (6.0)</option>
<option value="https://cdn.jsdelivr.net/npm/font-awesome-svg-icons#0.1.0/svg-sprite.svg">FontAwesome (0.1)</option>
</datalist>
<label>Svg markup</label>
<textarea id="svgMarkup" class="svgMarkup trackChange" placeholder="enter svg code"></textarea>
<label>Svg use code: <span id="svgUseCodeID"></span></label>
<textarea id="svgUseCode" class="svgUseCode" placeholder="enter svg code"></textarea>
</div>
</div>
This example contains 3 icon library svgs (you can select them in the URL field as datalist options).
Significant differences:
1. Feather icons
icons are defined as symbols
contain different element types like circle, polyline etc. (in addition to <path>)
all icons are styled by stroke attributes (stroke, stroke-width, stroke-linecap)
2. FontAwesome (6.0)
icons are defined as symbols
all assets are solid/closed <path> shapes
all icons are styled by fill attribute
3. FontAwesome (0.1)
icons are grouped in nested svg elements
using class names instead of IDs
(probably supposed to be just a test file)
I'm working on a Next.js project where the menu opens with a click on a burger button. For this I toggle a class on the button, and the styles for this class use styled-components.
When I click on the button it changes like expected, but when I add a CSS transition it's still not animated. I tested the same code in vanilla HTML and it's working well.
Where did I go wrong?
This is my code:
Burger js:
<BurgerStyled
onClick={() => setIsBurgerOpen(!isBurgerOpen)}
className={BurgerOpen}
>
<span />
<span />
<span />
</BurgerStyled>
OnClick behaviour:
const [isBurgerOpen, setIsBurgerOpen] = useState(false);
const BurgerOpen = isBurgerOpen ? "BurgerOpen" : "";
Styled component:
const BurgerStyled = styled.div`
width: 32px;
height: 21px;
position: relative;
span {
position: absolute;
left: 0;
background-color: var(--gray);
width: 100%;
height: 3px;
transition: all 0.3s ease;
&:first-child {
top: 0;
}
&:nth-child(2) {
top: calc(50% - 3px / 2);
}
&:last-child {
bottom: 0;
}
}
&.BurgerOpen span {
&:first-child {
transform: rotate(45deg);
}
&:nth-child(2) {
width: 0;
}
&:last-child {
transform: rotate(-45deg);
}
}
`;
Thanks for your answers!
You can pass the property to your styled component like so:
JSX:
return (
<BurgerStyled
onClick={() => setIsBurgerOpen(!isBurgerOpen)}
open={isBurgerOpen}
>
<span />
<span />
<span />
</BurgerStyled>
);
Style:
const BurgerStyled = styled(({ open, ...restProps }) => <div {...restProps} />)`
${({ open }) => open ? ` styles open here ` : ` styles closed here `};
`;
What you do here: you pass the open prop to your styled component, and then pass all other props to the div. The open prop is now available in your style template string. This prevents the open prop to be passed to the DOM-element, because a <div> DOM-element has no open property, so passing it through to the DOM would result in warnings.
You can try svg and transition to have the desired output. Check my sample code below, it might help you.
import { motion } from './framer-motion'; //install it with npm
const [isBurgerOpen, setIsBurgerOpen] = useState(false);
const HamburgerMenuContainer = styled.div`
display:flex;
`;
const MenuContainer = styled.div`
min-width:300px;
width:100%;
max-width:44%;
height:100%;
background-color: #fff;
box-shadow: -2px 0 2px rgba(15,15,15,0.3);
z-index:90;
position:fixed;
top:0;
right:0;
user-select:none;
padding: 1em 2.5em;
`;
const Button = styled.button`
z-index:99;
cursor:pointer;
`;
const Path = props =>{
<motion.Path fill="transparent" strokeLinecap="round" strokeWidth="3" {...props}/>
}
const transition = {duration: 0.3};
return (
<>
<HamburgerMenuContainer>
<Button onClick={() => setIsBurgerOpen(true)}>
<svg width="23" height="23" viewBox="0 0 23 23">
<Path animate={isBurgerOpen ? "open" : "closed"} initial={false} variants={{
closed: {d: "M 2 2.5 L 20 2.5",stroke:"hsl(0, 0%, 100%)"},
open: {d: "M 3 16.5 L 17 2.5",stroke:"hsl(0, 0%, 18%)"},
}}
transition={transition}
/>
<Path animate={isBurgerOpen ? "open" : "closed"} initial={false} variants={{
closed: {d: "M 2 2.5 L 20 2.5",stroke:"hsl(0, 0%, 100%)"},
open: {d: "M 3 16.5 L 17 2.5",stroke:"hsl(0, 0%, 18%)"},
}}
transition={transition}
/>
<Path animate={isBurgerOpen ? "open" : "closed"} initial={false} variants={{
closed: {d: "M 2 2.5 L 20 2.5",stroke:"hsl(0, 0%, 100%)"},
open: {d: "M 3 16.5 L 17 2.5",stroke:"hsl(0, 0%, 18%)"},
}}
transition={transition}
/>
</svg>
</Button>
</HamburgerMenuContainer>
</>
);
}
I could fix the problem. My Burger component was outside the header, like this:
function Header() {
const Burger = () => {
// Burger component
}
return (
<Burger />
)
}
I just put it like this and it worked:
function Header() {
return (
<BurgerStyled ...>
<span />
<span />
<span />
</BurgerStyled>
// Content header
)
}
Now the animation works, thanks for your answers!
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'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>