How to use clip-path in an animation? - css

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>

Related

Is there a way to add a variable to the clip-path url?

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>

Firefox 100: CSS 3D Transform rotateY(180deg) and SVG not playing well together

This may be a browser-bug relating to how Firefox Desktop 100.0.2. handles SVG and CSS Transforms.
Or else I'm doing something wrong to bring about this undesired effect.
I have a <button> containing two SVGs, one positioned on top of the other.
The SVG on the bottom is rotated 180 degrees (using transform: rotateY(180deg)) and invisible (using opacity: 0).
When the <button> is clicked, both SVGs rotate and at the halfway point in the rotation, the SVG on the bottom becomes visible and the SVG on the top becomes invisible.
The overall effect is that the <button> image rotates and transforms in the middle of its rotation.
Why is messing about with opacity necessary when we have backface-visibility?
Because iOS Safari doesn't support backface-visibility.
What's the issue?
The issue is that this isn't a clean rotation. The SVG which suddenly becomes visible turns into a blurry mess - like when the helicopter pilot in the original The Matrix is possessed by one of the agents - before settling into its new shape.
Click the button several times below to see this undesired effect in action. (I've slowed the transition down a little, so you have more time to see it in action.)
Working Example:
const audioButton = document.querySelector('.audioButton');
const audioOn = document.querySelector('.audio-on');
const audioOff = document.querySelector('.audio-off');
const toggleAudio = (e) => {
audioOn.classList.toggle('active');
audioOff.classList.toggle('active');
}
audioButton.addEventListener('click', toggleAudio, false);
.audioButton {
position: relative;
width: 52px;
height: 52px;
margin: 0 12px 0 0;
background-image: linear-gradient(hsl(219, 76%, 50%), hsl(219, 80%, 43%) 50%, hsl(219, 81%, 41%) 51%, hsl(219, 88%, 33%));
border: 2px solid hsl(220, 100%, 25%);
border-bottom: 2px solid hsl(220, 100%, 26%);
border-radius: 4px;
cursor: pointer;
}
.audioButton::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0);
}
.audioButton:hover::after {
background-color: rgba(0, 0, 0, 0.15);
}
.audioButtonVector {
position: absolute;
display: block;
top: 10%;
left: 10%;
width: 80%;
height: 80%;
opacity: 0;
pointer-events: none;
transform: rotateY(180deg);
transition: transform 1.2s linear, opacity 0s linear 0.6s;
}
.audio-off {
color: rgb(91, 127, 239);
}
.audio-on {
color: rgb(255, 255, 255);
}
.active {
opacity: 1;
transform: rotateY(0deg);
}
<button class="audioButton" type="button" title="Turn Audio On">
<svg xmlns="http://www.w3.org/2000/svg"
lang="en-GB"
class="audioButtonVector audio-off active"
viewBox="0 0 768 768">
<title>Mute Icon</title>
<defs><style><![CDATA[path {fill: currentColor;}]]></style></defs>
<path d="m 514.64857,746.41567 c -7.23403,-2.33279 -15.45533,-6.93789 -23.16845,-12.97761 -3.14331,-2.46136 -44.6698,-43.2877 -92.2811,-90.7252 l -86.56599,-86.25 h -67.29941 c -47.65888,0 -68.66895,-0.33827 -71.99223,-1.1591 -10.38521,-2.56509 -23.41546,-14.17469 -26.78831,-23.86766 -1.83792,-5.28188 -1.90451,-10.42057 -1.90451,-146.97324 v -141.5 l 2.89186,-6.2618 c 3.2572,-7.05289 8.40912,-12.23957 15.39549,-15.4994 2.59196,-1.20939 4.91571,-2.33594 5.16389,-2.50342 0.24818,-0.16749 -20.72025,-21.55146 -46.5965,-47.51995 -30.937231,-31.04751 -47.695971,-48.58514 -48.940811,-51.21543 -2.8746,-6.0739 -2.65973,-18.56296 0.42151,-24.500003 3.16419,-6.09687 8.64277,-11.63242 14.66456,-14.81707 4.20155,-2.222 6.27753,-2.64426 13.000001,-2.64426 6.74443,0 8.78473,0.4178 13,2.6621 3.65377,1.94535 22.6348,20.316143 70.49714,68.230673 l 65.49713,65.56856 h 31.48475 31.48474 l 89.51812,-89.32184 c 95.1437,-94.935093 95.62848,-95.37672 110.05601,-100.257898 6.53075,-2.209511 8.73786,-2.420265 25.34601,-2.420265 16.8774,0 18.51482,0.162627 22.65421,2.25 3.53889,1.784556 5.02413,3.387752 7.17979,7.75 l 2.71791,5.5 0.0321,248.502843 0.0321,248.50284 65.8925,65.88683 c 46.80175,46.79773 66.57555,67.22255 68.25005,70.49716 3.3554,6.56182 3.366,18.66705 0.022,25.11033 -3.1642,6.09686 -8.6428,11.63242 -14.6646,14.81707 -6.9012,3.64972 -18.7705,3.80022 -25.50001,0.32333 -3.02072,-1.5607 -19.21059,-17.00386 -49.25,-46.97853 l -44.75005,-44.65354 v 56.77796 c 0,62.53583 0.17139,60.72201 -6.27221,66.37956 -4.82954,4.24039 -10.80361,5.3457 -28.52701,5.27798 -11.68868,-0.0447 -15.93014,-0.4526 -20.70078,-1.99102 z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg"
lang="en-GB"
class="audioButtonVector audio-on"
viewBox="0 0 768 768">
<title>Audio Icon</title>
<defs><style><![CDATA[path {fill: currentColor;}]]></style></defs>
<path d="m 470.57734,741.2876 c -7.71475,-2.83606 -11.18777,-4.89195 -18.82603,-11.1443 -2.39807,-1.96296 -43.79807,-42.82236 -92,-90.79868 l -87.63988,-87.22967 -69.5,-0.27033 -69.5,-0.27033 -5.86156,-2.87813 c -8.69995,-4.27183 -19.79481,-15.53374 -22.7789,-23.12187 l -2.35954,-6 v -138 c 0,-131.11094 0.0914,-138.27456 1.83191,-143.5 4.32786,-12.99369 11.24782,-19.42452 26.95392,-25.04871 l 8.21417,-2.94141 66.47695,-0.005 66.47696,-0.005 90.52304,-90.2663 c 95.08695,-94.817141 94.44763,-94.232081 108.57699,-99.360761 5.97505,-2.16883 8.09265,-2.37282 24.63202,-2.37282 16.6968,0 18.4388,0.17171 22.54471,2.2223 2.44735,1.22227 5.35455,3.49301 6.46044,5.04609 4.95602,6.96009 4.73518,-9.62279 4.77327,358.416511 0.0249,240.3642 -0.28452,345.03405 -1.02571,347 -0.58374,1.54831 -2.49624,4.31599 -4.25,6.15042 -5.99091,6.26644 -7.33614,6.60106 -27.68867,6.88738 -18.04936,0.25392 -18.68352,0.19279 -26.03409,-2.50939 z M 600.3594,549.70163 c -5.93421,-2.20326 -10.33035,-6.14828 -13.14459,-11.79572 -5.39826,-10.83291 -3.08865,-16.71172 11.68289,-29.73724 40.36186,-35.59104 59.79177,-80.99428 57.38451,-134.09438 -2.23918,-49.39231 -20.58907,-87.5315 -58.17881,-120.9213 -12.02899,-10.68498 -14.65864,-14.55603 -14.65864,-21.5787 0,-11.90939 13.04063,-24.61151 23.75911,-23.14238 6.3213,0.86643 9.17103,2.59906 20.69094,12.58013 39.46289,34.19135 62.29527,76.84757 69.88539,130.56225 1.92863,13.64871 1.60428,48.41306 -0.58777,63 -5.75409,38.29032 -20.26693,71.24453 -44.36153,100.73141 -8.21433,10.05267 -30.26582,31.0853 -34.9257,33.312 -4.92676,2.35423 -12.81232,2.84138 -17.5458,1.08393 z" />
</svg>
</button>
Nota Bene:
I note that I if I change the transition from:
transition: transform 1.2s linear, opacity 0s linear 0.6s;
to:
transition: transform 1.2s linear, opacity 0.2s linear 0.5s;
the bizarre effect is minimised.
Using magic numbers like this, isn't an ideal compromise, I'll fall back to that if I can't get to the bottom of the issue.
A Significant Improvement:
I recalled some advice about sometimes needing to put different transitions on different elements. The updated version below is a clear improvement in that if you keep the mouse cursor hovered over the button after you click, the rotation is clean.
But if you move the mouse cursor off the button after clicking, the same messy effect is still visible.
Improved Working Example:
const audioButton = document.querySelector('.audioButton');
const audioOn = document.querySelector('.audio-on');
const audioOff = document.querySelector('.audio-off');
const toggleAudio = (e) => {
audioOn.classList.toggle('active');
audioOff.classList.toggle('active');
}
audioButton.addEventListener('click', toggleAudio, false);
.audioButton {
position: relative;
width: 52px;
height: 52px;
margin: 0 12px 0 0;
background-image: linear-gradient(hsl(219, 76%, 50%), hsl(219, 80%, 43%) 50%, hsl(219, 81%, 41%) 51%, hsl(219, 88%, 33%));
border: 2px solid hsl(220, 100%, 25%);
border-bottom: 2px solid hsl(220, 100%, 26%);
border-radius: 4px;
cursor: pointer;
}
.audioButton::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0);
}
.audioButton:hover::after {
background-color: rgba(0, 0, 0, 0.15);
}
.audioButtonVector {
position: absolute;
display: block;
top: 10%;
left: 10%;
width: 80%;
height: 80%;
transform: rotateY(180deg);
pointer-events: none;
transition: transform 0.6s linear;
}
.audioButtonVector.active {
transform: rotateY(0deg);
}
.audioButtonVector path {
opacity: 0;
transition: opacity 0s linear 0.3s;
}
.audioButtonVector.active path {
opacity: 1;
}
.audio-off {
color: rgb(91, 127, 239);
}
.audio-on {
color: rgb(255, 255, 255);
}
<button class="audioButton" type="button" title="Turn Audio On">
<svg xmlns="http://www.w3.org/2000/svg"
lang="en-GB"
class="audioButtonVector audio-off active"
viewBox="0 0 768 768">
<title>Mute Icon</title>
<defs><style><![CDATA[path {fill: currentColor;}]]></style></defs>
<path d="m 514.64857,746.41567 c -7.23403,-2.33279 -15.45533,-6.93789 -23.16845,-12.97761 -3.14331,-2.46136 -44.6698,-43.2877 -92.2811,-90.7252 l -86.56599,-86.25 h -67.29941 c -47.65888,0 -68.66895,-0.33827 -71.99223,-1.1591 -10.38521,-2.56509 -23.41546,-14.17469 -26.78831,-23.86766 -1.83792,-5.28188 -1.90451,-10.42057 -1.90451,-146.97324 v -141.5 l 2.89186,-6.2618 c 3.2572,-7.05289 8.40912,-12.23957 15.39549,-15.4994 2.59196,-1.20939 4.91571,-2.33594 5.16389,-2.50342 0.24818,-0.16749 -20.72025,-21.55146 -46.5965,-47.51995 -30.937231,-31.04751 -47.695971,-48.58514 -48.940811,-51.21543 -2.8746,-6.0739 -2.65973,-18.56296 0.42151,-24.500003 3.16419,-6.09687 8.64277,-11.63242 14.66456,-14.81707 4.20155,-2.222 6.27753,-2.64426 13.000001,-2.64426 6.74443,0 8.78473,0.4178 13,2.6621 3.65377,1.94535 22.6348,20.316143 70.49714,68.230673 l 65.49713,65.56856 h 31.48475 31.48474 l 89.51812,-89.32184 c 95.1437,-94.935093 95.62848,-95.37672 110.05601,-100.257898 6.53075,-2.209511 8.73786,-2.420265 25.34601,-2.420265 16.8774,0 18.51482,0.162627 22.65421,2.25 3.53889,1.784556 5.02413,3.387752 7.17979,7.75 l 2.71791,5.5 0.0321,248.502843 0.0321,248.50284 65.8925,65.88683 c 46.80175,46.79773 66.57555,67.22255 68.25005,70.49716 3.3554,6.56182 3.366,18.66705 0.022,25.11033 -3.1642,6.09686 -8.6428,11.63242 -14.6646,14.81707 -6.9012,3.64972 -18.7705,3.80022 -25.50001,0.32333 -3.02072,-1.5607 -19.21059,-17.00386 -49.25,-46.97853 l -44.75005,-44.65354 v 56.77796 c 0,62.53583 0.17139,60.72201 -6.27221,66.37956 -4.82954,4.24039 -10.80361,5.3457 -28.52701,5.27798 -11.68868,-0.0447 -15.93014,-0.4526 -20.70078,-1.99102 z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg"
lang="en-GB"
class="audioButtonVector audio-on"
viewBox="0 0 768 768">
<title>Audio Icon</title>
<defs><style><![CDATA[path {fill: currentColor;}]]></style></defs>
<path d="m 470.57734,741.2876 c -7.71475,-2.83606 -11.18777,-4.89195 -18.82603,-11.1443 -2.39807,-1.96296 -43.79807,-42.82236 -92,-90.79868 l -87.63988,-87.22967 -69.5,-0.27033 -69.5,-0.27033 -5.86156,-2.87813 c -8.69995,-4.27183 -19.79481,-15.53374 -22.7789,-23.12187 l -2.35954,-6 v -138 c 0,-131.11094 0.0914,-138.27456 1.83191,-143.5 4.32786,-12.99369 11.24782,-19.42452 26.95392,-25.04871 l 8.21417,-2.94141 66.47695,-0.005 66.47696,-0.005 90.52304,-90.2663 c 95.08695,-94.817141 94.44763,-94.232081 108.57699,-99.360761 5.97505,-2.16883 8.09265,-2.37282 24.63202,-2.37282 16.6968,0 18.4388,0.17171 22.54471,2.2223 2.44735,1.22227 5.35455,3.49301 6.46044,5.04609 4.95602,6.96009 4.73518,-9.62279 4.77327,358.416511 0.0249,240.3642 -0.28452,345.03405 -1.02571,347 -0.58374,1.54831 -2.49624,4.31599 -4.25,6.15042 -5.99091,6.26644 -7.33614,6.60106 -27.68867,6.88738 -18.04936,0.25392 -18.68352,0.19279 -26.03409,-2.50939 z M 600.3594,549.70163 c -5.93421,-2.20326 -10.33035,-6.14828 -13.14459,-11.79572 -5.39826,-10.83291 -3.08865,-16.71172 11.68289,-29.73724 40.36186,-35.59104 59.79177,-80.99428 57.38451,-134.09438 -2.23918,-49.39231 -20.58907,-87.5315 -58.17881,-120.9213 -12.02899,-10.68498 -14.65864,-14.55603 -14.65864,-21.5787 0,-11.90939 13.04063,-24.61151 23.75911,-23.14238 6.3213,0.86643 9.17103,2.59906 20.69094,12.58013 39.46289,34.19135 62.29527,76.84757 69.88539,130.56225 1.92863,13.64871 1.60428,48.41306 -0.58777,63 -5.75409,38.29032 -20.26693,71.24453 -44.36153,100.73141 -8.21433,10.05267 -30.26582,31.0853 -34.9257,33.312 -4.92676,2.35423 -12.81232,2.84138 -17.5458,1.08393 z" />
</svg>
</button>
No Further Improvement:
In the past I've seen issues in iOS Safari related to CSS 3D Rotations.
So I've replaced:
transform: rotateY(180deg)
transform: rotateY(0deg)
with:
transform: scale(-1, 1)
transform: scale(1, 1)
The minimal flicker that was previously visible in iOS Safari (I didn't mention this before - it's an unrelated issue) has now gone.
But the CSS update above hasn't made any significant difference to the undesired visual effect in Firefox Desktop.

How to check which icons are available in svg file?

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)

How to scale SVG path to fit the window size?

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!

How to generate a elongated hexagon shape with two circular sides?

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.

Resources