Mobile responsive design for complex process card - html

The design is for a website built on Angular . Have been using canvas along with html . canvas is used for building the dotted and solid arrows around circle . html is used for designing circles.
HTML :
<div class="process-card-container">
<div [style.margin-left.px]="canvasDivMargin" class="myCanvasDiv">
<canvas id="myCanvas" width="2000" height="600">
Your browser does not support the HTML5 canvas tag.</canvas>
</div>
<div class="all-cards">
<div class="card-container" *ngFor="let card of data.cards;">
<div (click)='onCTAClick(card.callToActions)'
[ngClass]="{'each-process-card-without-pointer': card.callToActions == undefined, 'each-process-card' : card.callToActions !== undefined }"
[ngStyle]="card.bgStyle">
<div class="each-card-content-container">
<span class="each-card-num">{{card.cardNumber}}</span>
<span class="each-card-title">{{card.cardTitle}}</span>
</div>
</div>
</div>
</div>
</div>
SASS
.process-card-container {
margin-top: 50px;
margin-bottom: 20px;
text-align: center;
}
.myCanvasDiv{
padding-left: 0;
padding-right: 0;
margin-right: auto;
display: block;
}
.card-container {
display: inline-block;
margin-right: 16px;
margin-left: 75px;
}
.all-cards {
margin-left: -8rem;
margin-top: -553px;
}
.each-process-card {
height: 250px;
width: 250px;
border-radius: 50%;
text-align: center;
cursor: pointer;
}
.each-process-card-without-pointer {
height: 250px;
width: 250px;
border-radius: 50%;
text-align: center;
}
.each-card-content-container {
position: relative;
}
.each-card-num {
position: absolute;
color: #ffffff;
font-size: 54px;
top: 30px;
left: 110px;
padding-left: 0;
padding-right: 0;
margin-left: 0;
margin-right: 0;
display: block;
}
.each-card-title {
font-size: 22px;
font-family: "Metropolis", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: bold;
color: white;
letter-spacing: 0.8px;
width: 120px;
position: absolute;
top: 109px;
left: 65px;
height: 20px;
}
The design works fine on one screen but it breaks on screens with other resolutions , also when zooming in /out , the arrows and circles go here and there .
TS file
import { Component, Input, OnInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-process-card',
templateUrl: './process-card.component.html',
styleUrls: ['./process-card.component.scss']
})
export class ProcessCardComponent implements OnInit {
constructor(public router: Router) { }
#Input()
public data;
public arrowColorArr = [];
public dia = 170;
public top_position = 180;
public left_position = 180;
public c;
public ctx;
public canvasDiv;
public canvasDivMargin = 36;
ngOnInit() {
this.arrowColorArr = this.data.cards.map((cardIn) => {
var eachBgColor = cardIn.bgStyle.background.includes("linear-gradient") ? cardIn.bgStyle.background.substring(35, 43) : cardIn.bgStyle.background;
return eachBgColor.replace(/ /g, '')
})
this.c = document.getElementById('myCanvas');
this.ctx = this.c.getContext("2d");
this.ctx.setLineDash([0.5, 15]);
this.ctx.lineWidth = 7;
this.ctx.lineCap = "round";
//1st down
this.ctx.strokeStyle = this.arrowColorArr[1];
this.ctx.beginPath();
this.ctx.arc(this.left_position, this.top_position + 4, this.dia, 0, 1 * Math.PI);
this.ctx.stroke();
//second circle up
this.ctx.strokeStyle = "#0091da";
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 2), this.top_position, this.dia, 3.1, 2 * Math.PI);
this.ctx.stroke();
//3rd circle down
this.ctx.strokeStyle = this.arrowColorArr[3];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 4), this.top_position, this.dia, 0, 1 * Math.PI);
this.ctx.stroke();
//4th circle up
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 6), this.top_position, this.dia, 3.15, 1.98 * Math.PI);
this.ctx.stroke();
this.ctx.lineWidth = 5;
//1st up
this.ctx.setLineDash([0, 0]);
this.ctx.strokeStyle = this.arrowColorArr[0];
this.ctx.beginPath();
this.ctx.arc(this.left_position, this.top_position, this.dia, 3.3, 1.8 * Math.PI);
this.ctx.stroke();
//2nd down
this.ctx.strokeStyle = this.arrowColorArr[1];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 2), this.top_position, this.dia, 0.5, 0.9 * Math.PI);
this.ctx.stroke();
//3rd up
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 4), this.top_position, this.dia, 3.5, 1.8 * Math.PI);
this.ctx.stroke();
//4th down
this.ctx.strokeStyle = this.arrowColorArr[3];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 6), this.top_position, this.dia, 0.1, 0.9 * Math.PI);
this.ctx.stroke()
//arrow mark
this.ctx.strokeStyle = this.arrowColorArr[0];
this.ctx.beginPath();
this.ctx.moveTo(330, 92);
this.ctx.lineTo(327, 79);
this.ctx.lineTo(317, 89);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.strokeStyle = this.arrowColorArr[1];
this.ctx.beginPath();
this.ctx.moveTo(677, 248);
this.ctx.lineTo(678, 262);
this.ctx.lineTo(667, 256);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.moveTo(1011, 90);
this.ctx.lineTo(1007, 77);
this.ctx.lineTo(997, 87);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.strokeStyle = this.arrowColorArr[3];
this.ctx.beginPath();
this.ctx.moveTo(1370, 183);
this.ctx.lineTo(1375, 193);
this.ctx.lineTo(1365, 193);
this.ctx.closePath();
this.ctx.stroke();
}
}

You can get very, very close with just plain HTML and CSS! I've done this using an ordered list, border-radius and border-style, plus a tiny SVG for the pointer arrow. This has a few advantages:
##Edit - see below for a version using SVG for all the decorative elements...
Much simpler code
Uses the browser's layout and rendering engine
Semantically correct HTML and live text
Automatic step numbering
Automatic coloring based on list position
Able to resize/respond as required
Able to easily add/remove steps as required
The downside is mainly that you don't have absolute fine control over the pixel placement, spacing and style of things like the dots and lines. I think this is a pretty reasonable trade-off for the advantages, but it will depend on your situation. For me, the result looks like this:
Here's the code
.disc-list {
counter-reset: step; /* Allows us to show the step number */
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: row; /* Put the discs in a row */
flex-wrap: wrap; /* Allow them to wrap around as required */
}
.disc-list li {
counter-increment: step; /* Increment the list number */
position: relative;
width: 8.5rem;
height: 8.5rem;
text-align: center;
border-radius: 50%; /* Make it a circle */
background: #888;
color: #888;
display: flex;
flex-shrink: 0; /* Don't allow the discs to squash inside the container */
flex-direction: column;
align-items: center;
justify-content: center;
margin: 1.5rem 1.25rem 1.5rem 1.5rem; /* Right margin is the normal width - the border-width */
overflow: visible;
}
/* Setup the rainbow colours on the discs
- #00AFC0
- #00A3E1
- #27579C
- #934FC0
- #C04F8E
- #C04F5D
- #C07E4F
- #C0B54F
*/
.disc-list li:nth-child(1) {
background: #00AFC0;
color: #00AFC0;
}
.disc-list li:nth-child(2) {
background: #00A3E1;
color: #00A3E1;
}
.disc-list li:nth-child(3) {
background: #27579C;
color: #27579C;
}
.disc-list li:nth-child(4) {
background: #934FC0;
color: #934FC0;
}
.disc-list li:nth-child(5) {
background: #C04F8E;
color: #C04F8E;
}
.disc-list li:nth-child(6) {
background: #C04F5D;
color: #C04F5D;
}
.disc-list li:nth-child(7) {
background: #C07E4F;
color: #C07E4F;
}
.disc-list li:nth-child(8) {
background: #C0B54F;
color: #C0B54F;
}
/* The text inside the disc */
.disc-list li span {
color: #FFF;
display: block;
font-weight: bold;
font-size: 0.8rem;
max-width: 5rem;
margin-left: auto;
margin-right: auto;
}
/* The step number */
.disc-list li span::before {
content: counter(step);
display: block;
font-size: 1.65rem;
margin-bottom: 0.1rem;
font-weight: 200;
}
/* The pointer container */
.disc-list li .pointer {
position: absolute;
right: -1.6rem;
width: 1rem;
height: 1rem;
}
/* The arrow-head itself */
.disc-list li .pointer path {
fill: none;
stroke-linejoin: round;
stroke-width: 3px;
stroke: currentcolor; /* This allows the outline to inherit the text color automatically */
}
/* Move the arrow head into position depending if it's an odd/even disc */
.disc-list li:nth-child(even) .pointer {
bottom: 20%;
transform: rotate(22.5deg);
}
.disc-list li:nth-child(odd) .pointer {
top: 20%;
transform: rotate(22.5deg);
}
.disc-list li::before,
.disc-list li::after {
content: '';
position: absolute;
border: 0.25rem solid;
left: -1.5rem; /* Same as the disc-list li margin */
right: -1.5rem;
}
/* The dotted elements */
.disc-list li:nth-child(even)::before {
border-top-style: dotted;
border-left-style: dotted;
border-radius: 50%/100% 100% 0 0;
border-bottom-color: transparent;
top: -1.5rem;
bottom: 50%;
}
.disc-list li:nth-child(odd)::before {
border-bottom-style: dotted;
border-right-style: dotted;
border-top-color: transparent;
border-radius: 50%/0 0 100% 100%;
bottom: -1.5rem;
top: 50%;
}
/* The solid line elements */
.disc-list li:nth-child(odd)::after {
transform-origin: bottom center;
transform: rotate(22.5deg);
border-width: 0.2rem;
border-style: solid;
border-color: transparent;
border-top-color: inherit;
border-left-color: inherit;
border-radius: 50%/100% 100% 0 0;
border-bottom-color: transparent;
top: -1.5rem;
bottom: 50%;
}
.disc-list li:nth-child(even)::after {
transform-origin: top center;
transform: rotate(22.5deg);
border-style: solid;
border-width: 0.2rem;
border-color: transparent;
border-bottom-color: inherit;
border-right-color: inherit;
border-radius: 50%/0 0 100% 100%;
bottom: -1.5rem;
top: 50%;
}
<ol class="disc-list">
<li>
<span>Login to Workday</span>
<svg class="pointer">
<path d="M6.25,1.25L11.25,11.25L1.25,11.25L6.25,1.25Z" />
</svg>
</li>
<li>
<span>Upload to Receipts</span>
<svg class="pointer">
<path d="M6.25,1.25L11.25,11.25L1.25,11.25L6.25,1.25Z" />
</svg>
</li>
<li>
<span>Submit Report</span>
<svg class="pointer">
<path d="M6.25,1.25L11.25,11.25L1.25,11.25L6.25,1.25Z" />
</svg>
</li>
<li>
<span>Get Reimbursed</span>
<svg class="pointer">
<path d="M6.25,1.25L11.25,11.25L1.25,11.25L6.25,1.25Z"/>
</svg>
</li>
<li>
<span>Do a Dance</span>
<svg class="pointer">
<path d="M6.25,1.25L11.25,11.25L1.25,11.25L6.25,1.25Z"/>
</svg>
</li>
<li>
<span>Make Sandwiches</span>
<svg class="pointer">
<path d="M6.25,1.25L11.25,11.25L1.25,11.25L6.25,1.25Z"/>
</svg>
</li>
<li>
<span>Take a nap</span>
<svg class="pointer">
<path d="M6.25,1.25L11.25,11.25L1.25,11.25L6.25,1.25Z"/>
</svg>
</li>
<li>
<span>Go again!</span>
<svg class="pointer">
<path d="M6.25,1.25L11.25,11.25L1.25,11.25L6.25,1.25Z"/>
</svg>
</li>
</ol>
I'm sure you can fine-tune this further, and optimise the CSS to make it neater - this should give a decent starting point, though. Feel free to ask for more detail if required!
SVG version
This version uses inline SVGs for the decorative elements, rather than border styles. This should give a more consistent result and allow you to fine tune things like the dashes. I've re-written this with SASS for brevity, which SO's code snippet doesn't support, so this is on CodePen: https://codepen.io/companionstudio/pen/BaWqBmm
This doesn't change the wrapping/scaling requirement yet, though I would highly recommend a solution based on HTML like this if you need wrapping.

The canvas draws OK at viewport dimensions 1920 x 1080.
This snippet (which is vanilla JS for demo purposes) draws the canvas as given in the code in the question and then scales it and its position to fit the current viewport.
let arrowColorArr = ["red", "green", "blue", "grey"];
const dia = 170;
const top_position = 180;
const left_position = 180;
function OnInit() {
// for vanilla JS demo only we need to set up the values
this.arrowColorArr = arrowColorArr;
this.dia = dia;
this.top_position = top_position;
this.left_position = left_position;
this.c = document.getElementById('myCanvas');
//ADDED - RESTORE CANVAS TO INITIAL SCALE
c.style.transform = "scale(1)";
this.ctx = this.c.getContext("2d");
this.ctx.clearRect(0, 0, 1920, 600); //ADDED
this.ctx.setLineDash([0.5, 15]);
this.ctx.lineWidth = 7;
this.ctx.lineCap = "round";
//1st down
this.ctx.strokeStyle = this.arrowColorArr[1];
this.ctx.beginPath();
this.ctx.arc(this.left_position, this.top_position + 4, this.dia, 0, 1 * Math.PI);
this.ctx.stroke();
//second circle up
this.ctx.strokeStyle = "#0091da";
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 2), this.top_position, this.dia, 3.1, 2 * Math.PI);
this.ctx.stroke();
//3rd circle down
this.ctx.strokeStyle = this.arrowColorArr[3];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 4), this.top_position, this.dia, 0, 1 * Math.PI);
this.ctx.stroke();
//4th circle up
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 6), this.top_position, this.dia, 3.15, 1.98 * Math.PI);
this.ctx.stroke();
this.ctx.lineWidth = 5;
//1st up
this.ctx.setLineDash([0, 0]);
this.ctx.strokeStyle = this.arrowColorArr[0];
this.ctx.beginPath();
this.ctx.arc(this.left_position, this.top_position, this.dia, 3.3, 1.8 * Math.PI);
this.ctx.stroke();
//2nd down
this.ctx.strokeStyle = this.arrowColorArr[1];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 2), this.top_position, this.dia, 0.5, 0.9 * Math.PI);
this.ctx.stroke();
//3rd up
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 4), this.top_position, this.dia, 3.5, 1.8 * Math.PI);
this.ctx.stroke();
//4th down
this.ctx.strokeStyle = this.arrowColorArr[3];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 6), this.top_position, this.dia, 0.1, 0.9 * Math.PI);
this.ctx.stroke()
//arrow mark
this.ctx.strokeStyle = this.arrowColorArr[0];
this.ctx.beginPath();
this.ctx.moveTo(330, 92);
this.ctx.lineTo(327, 79);
this.ctx.lineTo(317, 89);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.strokeStyle = this.arrowColorArr[1];
this.ctx.beginPath();
this.ctx.moveTo(677, 248);
this.ctx.lineTo(678, 262);
this.ctx.lineTo(667, 256);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.moveTo(1011, 90);
this.ctx.lineTo(1007, 77);
this.ctx.lineTo(997, 87);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.strokeStyle = this.arrowColorArr[3];
this.ctx.beginPath();
this.ctx.moveTo(1370, 183);
this.ctx.lineTo(1375, 193);
this.ctx.lineTo(1365, 193);
this.ctx.closePath();
this.ctx.stroke();
//ADDED SO CANVAS SCALES TO WINDOW WIDTH
const scale = window.innerWidth/1920;// 1920 because it draws the canvas OK at that viewport width
c.style.transform = "scale(" + scale + ")";
c.style.top = this.top_position * scale + "px";
c.style.left = this.left_position * scale + "px";
}
window.onload = OnInit;
window.onresize = OnInit;
* {
margin: 0;
padding: 0;
}
canvas {
position: relative;
transform-origin: 0 0;
}
<canvas id="myCanvas" width="1500" height="600"></canvas><!-- was 2000 wide, reduced so doesn't cause X overflow when scaled -->
With these changes inserted your canvas drawing code therefore becomes:
<canvas id="myCanvas" width="1500" height="600"></canvas><!-- was 2000 wide, reduced so doesn't cause X overflow when scaled -->
TS code:
import { Component, Input, OnInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-process-card',
templateUrl: './process-card.component.html',
styleUrls: ['./process-card.component.scss']
})
export class ProcessCardComponent implements OnInit {
constructor(public router: Router) { }
#Input()
public data;
public arrowColorArr = [];
public dia = 170;
public top_position = 180;
public left_position = 180;
public c;
public ctx;
public canvasDiv;
public canvasDivMargin = 36;
ngOnInit() {
this.arrowColorArr = this.data.cards.map((cardIn) => {
var eachBgColor = cardIn.bgStyle.background.includes("linear-gradient") ? cardIn.bgStyle.background.substring(35, 43) : cardIn.bgStyle.background;
return eachBgColor.replace(/ /g, '')
})
this.c = document.getElementById('myCanvas');
//ADDED - RESTORE CANVAS TO INITIAL SCALE
c.style.transform = "scale(1)";
this.ctx = this.c.getContext("2d");
// ADDED TO CLEAR THE CANVAS EACH TIME (ON A RESIZE E.G.)
this.ctx.clearRect(0, 0, 1500, 600);
this.ctx.setLineDash([0.5, 15]);
this.ctx.lineWidth = 7;
this.ctx.lineCap = "round";
//1st down
this.ctx.strokeStyle = this.arrowColorArr[1];
this.ctx.beginPath();
this.ctx.arc(this.left_position, this.top_position + 4, this.dia, 0, 1 * Math.PI);
this.ctx.stroke();
//second circle up
this.ctx.strokeStyle = "#0091da";
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 2), this.top_position, this.dia, 3.1, 2 * Math.PI);
this.ctx.stroke();
//3rd circle down
this.ctx.strokeStyle = this.arrowColorArr[3];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 4), this.top_position, this.dia, 0, 1 * Math.PI);
this.ctx.stroke();
//4th circle up
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 6), this.top_position, this.dia, 3.15, 1.98 * Math.PI);
this.ctx.stroke();
this.ctx.lineWidth = 5;
//1st up
this.ctx.setLineDash([0, 0]);
this.ctx.strokeStyle = this.arrowColorArr[0];
this.ctx.beginPath();
this.ctx.arc(this.left_position, this.top_position, this.dia, 3.3, 1.8 * Math.PI);
this.ctx.stroke();
//2nd down
this.ctx.strokeStyle = this.arrowColorArr[1];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 2), this.top_position, this.dia, 0.5, 0.9 * Math.PI);
this.ctx.stroke();
//3rd up
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 4), this.top_position, this.dia, 3.5, 1.8 * Math.PI);
this.ctx.stroke();
//4th down
this.ctx.strokeStyle = this.arrowColorArr[3];
this.ctx.beginPath();
this.ctx.arc(this.left_position + (this.dia * 6), this.top_position, this.dia, 0.1, 0.9 * Math.PI);
this.ctx.stroke()
//arrow mark
this.ctx.strokeStyle = this.arrowColorArr[0];
this.ctx.beginPath();
this.ctx.moveTo(330, 92);
this.ctx.lineTo(327, 79);
this.ctx.lineTo(317, 89);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.strokeStyle = this.arrowColorArr[1];
this.ctx.beginPath();
this.ctx.moveTo(677, 248);
this.ctx.lineTo(678, 262);
this.ctx.lineTo(667, 256);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.strokeStyle = this.arrowColorArr[2];
this.ctx.beginPath();
this.ctx.moveTo(1011, 90);
this.ctx.lineTo(1007, 77);
this.ctx.lineTo(997, 87);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.strokeStyle = this.arrowColorArr[3];
this.ctx.beginPath();
this.ctx.moveTo(1370, 183);
this.ctx.lineTo(1375, 193);
this.ctx.lineTo(1365, 193);
this.ctx.closePath();
this.ctx.stroke();
//ADDED SO CANVAS SCALES TO WINDOW WIDTH
const scale = window.innerWidth/1920;// 1920 because it draws the canvas OK at that viewport width
c.style.transform = "scale(" + scale + ")";
c.style.top = this.top_position * scale + "px";
c.style.left = this.left_position * scale + "px";
}
}
You will similarly need to scale the size and positioning of the HTML elements. Let me know if you'd like help with this.
And remember to add whatever is needed to recalculate the canvas on a resize event.

Related

Spinning a wheel of fortune with a predetermined rotation value

My goal is to (upon clicking a button) spin a wheel multiple times, then end on a specific rotation value (0...360) while easing out the animation. I can currently 'smoothly' spin to a specific point on the wheel without any full revolutions. My problem is trying to spin the wheel multiple times and then landing on a specific point to make it look more realistic. Is this achievable with the CSS animaton property or anything else?
Here is my code...
const wheelEl = document.getElementById("wheel");
const sliceSize = 360 / 3;
function spinWheel(index) {
// Reset animation
wheelEl.style.transition = "none";
wheelEl.style.transform = "rotate(0deg)";
// Play animation on the next frame
setTimeout(() => {
wheelEl.style.transition = "all ease 1s";
// Target rotation margin
let rotMin = (sliceSize * (index))+(sliceSize/15);
let rotMax = (sliceSize * (index + 1))-(sliceSize/15);
// Target rotation
let rot = Math.floor(Math.random() * (rotMax - rotMin + 1) + rotMin);
wheelEl.style.transform = `rotate(-${rot}deg)`;
}, 0);
}
#container {
position: absolute;
text-align: center;
}
#arrow {
position: absolute;
top: -5px;
left: 50%;
transform: translateX(-50%);
border-top: 10px solid black;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
z-index: 1;
}
#wheel {
width: 100px;
height: 100px;
border-radius: 50%;
background-image: conic-gradient(lightpink 0 120deg, lightblue 0 240deg, lightsalmon 0 360deg);
}
<div id="container">
<div id="arrow"></div>
<div id="wheel"></div>
<br />
<button onclick="spinWheel(2)">Spin wheel</button>
</div>
You can stick to using transform.
This snippet does two things: adds a random (within bounds) number of 360 degrees to the rotation value and sets the easing function to ease-out so that the rotation slows down towards the end only.
So that the effect can be seen, the time of the full rotation is set to 5 seconds but of course alter this to whatever you want. You could for example make that random within some bounds too if desired.
You could also play with the Beziere function that represents ease-out if say you wanted a longer stretch of it seeming to slow down.
const wheelEl = document.getElementById("wheel");
const sliceSize = 360 / 3;
function spinWheel(index) {
// Reset animation
wheelEl.style.transition = "none";
wheelEl.style.transform = "rotate(0deg)";
// Play animation on the next frame
setTimeout(() => {
wheelEl.style.transition = "all ease-out 5s";
// Target rotation margin
const rotMin = (sliceSize * (index)) + (sliceSize / 15);
const rotMax = (sliceSize * (index + 1)) - (sliceSize / 15);
// Target rotation
const fullRots = Math.floor(Math.random() * 5) + 5; // minimum 5 rotations max 9
const rot = (fullRots * 360) + Math.floor(Math.random() * (rotMax - rotMin + 1) + rotMin);
wheelEl.style.transform = `rotate(-${rot}deg)`;
}, 0);
}
#container {
position: absolute;
text-align: center;
}
#arrow {
position: absolute;
top: -5px;
left: 50%;
transform: translateX(-50%);
border-top: 10px solid black;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
z-index: 1;
}
#wheel {
width: 100px;
height: 100px;
border-radius: 50%;
background-image: conic-gradient(lightpink 0 120deg, lightblue 0 240deg, lightsalmon 0 360deg);
}
<div id="container">
<div id="arrow"></div>
<div id="wheel"></div>
<br />
<button onclick="spinWheel(2)">Spin wheel</button>
</div>

Aligning SVG Clip Path on top of image

EDIT 1: Quick codepen of my current issue: https://codepen.io/zuffdaddy/pen/QWGewKr
See how the clip path is massive while the image is small? How do align those two?
Original:
Intro:
I'm making an Attack Disc for a submarine simulator game.
The disc has multiple circular rings that all rotate and so far I have that part complete, but there's some overlapping transparent plastic parts that sit on top the rings and rotate as well.
Problem:
The problem is the plastic pieces that rotate on top are complex shapes (image of the shape here). The user needs to be able to click around them to manipulate the rings beneath them but also be able to click on them to rotate them as well.
SVG Clipping paths seem to be the way to go but I cannot get the svg clip path to align with the image.
Please ignore the JS, it's a temporary rotating script I pulled from another question here and will be rewritten towards finalization.
///////////////////////////////
// ------- rotate -------- //
///////////////////////////////
(function() {
var init, rotate, start, stop,
active = false,
angle = 0,
rotation = 0,
startAngle = 0,
center = {
x: 0,
y: 0
},
R2D = 180 / Math.PI,
rot = document.getElementById('attack_disc');
init = function() {
rot.addEventListener("mousedown", start, false);
$(document).bind('mousemove', function(event) {
if (active === true) {
event.preventDefault();
rotate(event);
}
});
$(document).bind('mouseup', function(event) {
event.preventDefault();
stop(event);
});
};
start = function(e) {
e.preventDefault();
var bb = this.getBoundingClientRect(),
t = bb.top,
l = bb.left,
h = bb.height,
w = bb.width,
x, y;
center = {
x: l + (w / 2),
y: t + (h / 2)
};
x = e.clientX - center.x;
y = e.clientY - center.y;
startAngle = R2D * Math.atan2(y, x);
return active = true;
};
rotate = function(e) {
e.preventDefault();
var x = e.clientX - center.x,
y = e.clientY - center.y,
d = R2D * Math.atan2(y, x);
rotation = d - startAngle;
return rot.style.webkitTransform = "rotate(" + (angle + rotation) + "deg)";
};
stop = function() {
angle += rotation;
return active = false;
};
init();
}).call(this);
(function() {
var init, rotate, start, stop,
active = false,
angle = 0,
rotation = 0,
startAngle = 0,
center = {
x: 0,
y: 0
},
R2D = 180 / Math.PI,
rot = document.getElementById('course_disc');
init = function() {
rot.addEventListener("mousedown", start, false);
$(document).bind('mousemove', function(event) {
if (active === true) {
event.preventDefault();
rotate(event);
}
});
$(document).bind('mouseup', function(event) {
event.preventDefault();
stop(event);
});
};
start = function(e) {
e.preventDefault();
var bb = this.getBoundingClientRect(),
t = bb.top,
l = bb.left,
h = bb.height,
w = bb.width,
x, y;
center = {
x: l + (w / 2),
y: t + (h / 2)
};
x = e.clientX - center.x;
y = e.clientY - center.y;
startAngle = R2D * Math.atan2(y, x);
return active = true;
};
rotate = function(e) {
e.preventDefault();
var x = e.clientX - center.x,
y = e.clientY - center.y,
d = R2D * Math.atan2(y, x);
rotation = d - startAngle;
return rot.style.webkitTransform = "rotate(" + (angle + rotation) + "deg)";
};
stop = function() {
angle += rotation;
return active = false;
};
init();
}).call(this);
(function() {
var init, rotate, start, stop,
active = false,
angle = 0,
rotation = 0,
startAngle = 0,
center = {
x: 0,
y: 0
},
R2D = 180 / Math.PI,
rot = document.getElementById('aob_disc');
init = function() {
rot.addEventListener("mousedown", start, false);
$(document).bind('mousemove', function(event) {
if (active === true) {
event.preventDefault();
rotate(event);
}
});
$(document).bind('mouseup', function(event) {
event.preventDefault();
stop(event);
});
};
start = function(e) {
e.preventDefault();
var bb = this.getBoundingClientRect(),
t = bb.top,
l = bb.left,
h = bb.height,
w = bb.width,
x, y;
center = {
x: l + (w / 2),
y: t + (h / 2)
};
x = e.clientX - center.x;
y = e.clientY - center.y;
startAngle = R2D * Math.atan2(y, x);
return active = true;
};
rotate = function(e) {
e.preventDefault();
var x = e.clientX - center.x,
y = e.clientY - center.y,
d = R2D * Math.atan2(y, x);
rotation = d - startAngle;
return rot.style.webkitTransform = "rotate(" + (angle + rotation) + "deg)";
};
stop = function() {
angle += rotation;
return active = false;
};
init();
}).call(this);
(function() {
var init, rotate, start, stop,
active = false,
angle = 0,
rotation = 0,
startAngle = 0,
center = {
x: 0,
y: 0
},
R2D = 180 / Math.PI,
rot = document.getElementById('bearing_lead_disc');
init = function() {
rot.addEventListener("mousedown", start, false);
$(document).bind('mousemove', function(event) {
if (active === true) {
event.preventDefault();
rotate(event);
}
});
$(document).bind('mouseup', function(event) {
event.preventDefault();
stop(event);
});
};
start = function(e) {
e.preventDefault();
var bb = this.getBoundingClientRect(),
t = bb.top,
l = bb.left,
h = bb.height,
w = bb.width,
x, y;
center = {
x: l + (w / 2),
y: t + (h / 2)
};
x = e.clientX - center.x;
y = e.clientY - center.y;
startAngle = R2D * Math.atan2(y, x);
return active = true;
};
rotate = function(e) {
e.preventDefault();
var x = e.clientX - center.x,
y = e.clientY - center.y,
d = R2D * Math.atan2(y, x);
rotation = d - startAngle;
return rot.style.webkitTransform = "rotate(" + (angle + rotation) + "deg)";
};
stop = function() {
angle += rotation;
return active = false;
};
init();
}).call(this);
html {
height: 100%;
overflow: hidden;
}
body {
background: #1c1c1c;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.wrapper {
height: 100%;
max-height: 960px;
width: 100%;
max-width: 960px;
position: relative;
margin: auto;
}
#attack_disc {
background-image: url('https://zuffdaddy.github.io/uboat-attack-disc/images/1.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 30vw;
max-width: 1124px;
height: 30vw;
max-height: 1124px;
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
transform-origin: 50% 50%;
border-radius: 50%;
}
#course_disc {
background-image: url('https://zuffdaddy.github.io/uboat-attack-disc/images/2.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 23vw;
max-width: 868px;
height: 23vw;
max-height: 868px;
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
transform-origin: 50% 50%;
border-radius: 50%;
}
#aob_disc {
background-image: url('https://zuffdaddy.github.io/uboat-attack-disc/images/3.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 15.5vw;
max-width: 592px;
height: 15.5vw;
max-height: 592px;
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
transform-origin: 50% 50%;
border-radius: 50%;
}
#bearing_lead_disc {
background-image: url('https://zuffdaddy.github.io/uboat-attack-disc/images/4.png');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
width: 23.5vw;
max-width: 884px;
height: 23.5vw;
max-height: 884px;
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
clip-path: url('#my-clip-path');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div id="attack_disc"></div>
<div id="course_disc"></div>
<div id="aob_disc"></div>
<div id="bearing_lead_disc">
<svg class="svg">
<clipPath id="my-clip-path" clipPathUnits="objectBoundingBox"><path d="M0.501,0 C0.556,0,0.555,0.04,0.555,0.04 C0.555,0.099,0.984,0.229,0.994,0.232 S1,0.243,0.999,0.251 C0.976,0.284,0.874,0.421,0.656,0.469 C0.656,0.469,0.648,0.47,0.647,0.474 C0.646,0.48,0.572,0.984,0.57,0.992 C0.569,0.996,0.569,0.999,0.561,0.999 C0.522,1,0.516,1,0.502,1 C0.487,1,0.482,1,0.443,0.999 C0.434,0.999,0.434,0.996,0.433,0.992 C0.432,0.984,0.358,0.48,0.357,0.474 C0.356,0.47,0.347,0.469,0.347,0.469 H0.347 C0.129,0.421,0.027,0.284,0.005,0.251 C0,0.243,0,0.236,0.01,0.232 S0.449,0.099,0.449,0.04 C0.449,0.04,0.447,0,0.502,0"></path></clipPath>
</svg>
</div>
</div>
The problem appears to be that you extracted the shape and converted it to objectBoundingBox coordinates relative to itself. So the objectBoundingBox coords are no longer relative to your attack disc image.
What you can do is apply a transform to the clip path to scale it down to where it should be. By trial and error I worked out an appropriate scaling that gets it to match the shape it is supposed to clip.
transform="translate(0.5,1) scale(0.415,0.52) translate(-0.5,-1)"
.svg {
position: absolute;
width: 0;
height: 0;
}
.clipped {
width: 884px;
height: 884px;
background: turquoise url(https://zuffdaddy.github.io/uboat-attack-disc/images/4.png);
background-size: cover;
-webkit-clip-path: url(#my-clip-path);
clip-path: url(#my-clip-path);
}
<svg class="svg">
<clipPath id="my-clip-path" clipPathUnits="objectBoundingBox"><path d="M0.501,0 C0.556,0,0.555,0.04,0.555,0.04 C0.555,0.099,0.984,0.229,0.994,0.232 S1,0.243,0.999,0.251 C0.976,0.284,0.874,0.421,0.656,0.469 C0.656,0.469,0.648,0.47,0.647,0.474 C0.646,0.48,0.572,0.984,0.57,0.992 C0.569,0.996,0.569,0.999,0.561,0.999 C0.522,1,0.516,1,0.502,1 C0.487,1,0.482,1,0.443,0.999 C0.434,0.999,0.434,0.996,0.433,0.992 C0.432,0.984,0.358,0.48,0.357,0.474 C0.356,0.47,0.347,0.469,0.347,0.469 H0.347 C0.129,0.421,0.027,0.284,0.005,0.251 C0,0.243,0,0.236,0.01,0.232 S0.449,0.099,0.449,0.04 C0.449,0.04,0.447,0,0.502,0" transform="translate(0.5,1) scale(0.415,0.52) translate(-0.5,-1)"></path></clipPath>
</svg>
<div class="clipped"></div>
Svg better to be inside div
Svg better to be inside div so you have more control on the shape and it's also can be scale.
First I fix your path 'd' with this site (https://aydos.com/svgedit/)
Second I put the svg inside the div
Third I order the svg with view box to suitable (with color red some transparent) like the background-image link in 'bearing_lead_disc' and use css pointer-events:none for ignore clicking on the div and then we can rotate the circles even we clicked on the shape.
link: (https://codepen.io/omergal/pen/qBqzeQa)
pic:
///////////////////////////////
// ------- rotate -------- //
///////////////////////////////
(function() {
var init, rotate, start, stop,
active = false,
angle = 0,
rotation = 0,
startAngle = 0,
center = {
x: 0,
y: 0
},
R2D = 180 / Math.PI,
rot = document.getElementById('attack_disc');
init = function() {
rot.addEventListener("mousedown", start, false);
$(document).bind('mousemove', function(event) {
if (active === true) {
event.preventDefault();
rotate(event);
}
});
$(document).bind('mouseup', function(event) {
event.preventDefault();
stop(event);
});
};
start = function(e) {
e.preventDefault();
var bb = this.getBoundingClientRect(),
t = bb.top,
l = bb.left,
h = bb.height,
w = bb.width,
x, y;
center = {
x: l + (w / 2),
y: t + (h / 2)
};
x = e.clientX - center.x;
y = e.clientY - center.y;
startAngle = R2D * Math.atan2(y, x);
return active = true;
};
rotate = function(e) {
e.preventDefault();
var x = e.clientX - center.x,
y = e.clientY - center.y,
d = R2D * Math.atan2(y, x);
rotation = d - startAngle;
return rot.style.webkitTransform = "rotate(" + (angle + rotation) + "deg)";
};
stop = function() {
angle += rotation;
return active = false;
};
init();
}).call(this);
(function() {
var init, rotate, start, stop,
active = false,
angle = 0,
rotation = 0,
startAngle = 0,
center = {
x: 0,
y: 0
},
R2D = 180 / Math.PI,
rot = document.getElementById('course_disc');
init = function() {
rot.addEventListener("mousedown", start, false);
$(document).bind('mousemove', function(event) {
if (active === true) {
event.preventDefault();
rotate(event);
}
});
$(document).bind('mouseup', function(event) {
event.preventDefault();
stop(event);
});
};
start = function(e) {
e.preventDefault();
var bb = this.getBoundingClientRect(),
t = bb.top,
l = bb.left,
h = bb.height,
w = bb.width,
x, y;
center = {
x: l + (w / 2),
y: t + (h / 2)
};
x = e.clientX - center.x;
y = e.clientY - center.y;
startAngle = R2D * Math.atan2(y, x);
return active = true;
};
rotate = function(e) {
e.preventDefault();
var x = e.clientX - center.x,
y = e.clientY - center.y,
d = R2D * Math.atan2(y, x);
rotation = d - startAngle;
return rot.style.webkitTransform = "rotate(" + (angle + rotation) + "deg)";
};
stop = function() {
angle += rotation;
return active = false;
};
init();
}).call(this);
(function() {
var init, rotate, start, stop,
active = false,
angle = 0,
rotation = 0,
startAngle = 0,
center = {
x: 0,
y: 0
},
R2D = 180 / Math.PI,
rot = document.getElementById('aob_disc');
init = function() {
rot.addEventListener("mousedown", start, false);
$(document).bind('mousemove', function(event) {
if (active === true) {
event.preventDefault();
rotate(event);
}
});
$(document).bind('mouseup', function(event) {
event.preventDefault();
stop(event);
});
};
start = function(e) {
e.preventDefault();
var bb = this.getBoundingClientRect(),
t = bb.top,
l = bb.left,
h = bb.height,
w = bb.width,
x, y;
center = {
x: l + (w / 2),
y: t + (h / 2)
};
x = e.clientX - center.x;
y = e.clientY - center.y;
startAngle = R2D * Math.atan2(y, x);
return active = true;
};
rotate = function(e) {
e.preventDefault();
var x = e.clientX - center.x,
y = e.clientY - center.y,
d = R2D * Math.atan2(y, x);
rotation = d - startAngle;
return rot.style.webkitTransform = "rotate(" + (angle + rotation) + "deg)";
};
stop = function() {
angle += rotation;
return active = false;
};
init();
}).call(this);
(function() {
var init, rotate, start, stop,
active = false,
angle = 0,
rotation = 0,
startAngle = 0,
center = {
x: 0,
y: 0
},
R2D = 180 / Math.PI,
rot = document.getElementById('bearing_lead_disc');
init = function() {
rot.addEventListener("mousedown", start, false);
$(document).bind('mousemove', function(event) {
if (active === true) {
event.preventDefault();
rotate(event);
}
});
$(document).bind('mouseup', function(event) {
event.preventDefault();
stop(event);
});
};
start = function(e) {
e.preventDefault();
var bb = this.getBoundingClientRect(),
t = bb.top,
l = bb.left,
h = bb.height,
w = bb.width,
x, y;
center = {
x: l + (w / 2),
y: t + (h / 2)
};
x = e.clientX - center.x;
y = e.clientY - center.y;
startAngle = R2D * Math.atan2(y, x);
return active = true;
};
rotate = function(e) {
e.preventDefault();
var x = e.clientX - center.x,
y = e.clientY - center.y,
d = R2D * Math.atan2(y, x);
rotation = d - startAngle;
return rot.style.webkitTransform = "rotate(" + (angle + rotation) + "deg)";
};
stop = function() {
angle += rotation;
return active = false;
};
init();
}).call(this);
html {
height: 100%;
overflow: hidden;
}
body {
background: #1c1c1c;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.wrapper {
height: 100%;
max-height: 960px;
width: 100%;
max-width: 960px;
position: relative;
margin: auto;
}
#attack_disc {
background-image: url('https://zuffdaddy.github.io/uboat-attack-disc/images/1.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 30vw;
max-width: 1124px;
height: 30vw;
max-height: 1124px;
margin: auto;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
transform-origin:50% 50%;
border-radius:50%;
}
#course_disc {
background-image: url('https://zuffdaddy.github.io/uboat-attack-disc/images/2.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 23vw;
max-width: 868px;
height: 23vw;
max-height: 868px;
margin: auto;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
transform-origin:50% 50%;
border-radius:50%;
}
#aob_disc {
background-image: url('https://zuffdaddy.github.io/uboat-attack-disc/images/3.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 15.5vw;
max-width: 592px;
height: 15.5vw;
max-height: 592px;
margin: auto;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
transform-origin:50% 50%;
border-radius:50%;
}
#bearing_lead_disc {
background-image: url('https://zuffdaddy.github.io/uboat-attack-disc/images/4.png');
/* background-color: red; */
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 23.5vw;
max-width: 884px;
height: 23.5vw;
max-height: 884px;
margin: auto;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
clip-path: url('#myPath');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div id="attack_disc"></div>
<div id="course_disc"></div>
<div id="aob_disc"></div>
<div id="bearing_lead_disc" style="pointer-events:none;">
<svg viewBox="0 0 512 512" style="width:100%; height:100%;">
<path style="pointer-events:all;" fill="#ff00004d" d="M255.77 26.76c20 0 19.5 18.5 19.5 18.5 0 27 155.5 86.5 159 88s3.61 5.03 1.78 8.5c-8.07 15.27-45.11 77.9-124.18 99.85 0 0-3.05.81-3.32 2.67-.38 2.6-27.27 233.45-27.88 237.47-.27 1.76-.27 2.84-3.35 2.97-14.18.56-16.1.53-21.37.53s-7.19.03-21.37-.53c-3.08-.12-3.09-1.2-3.35-2.97-.61-4.03-27.5-234.88-27.88-237.47-.27-1.86-3.32-2.69-3.32-2.69-79.01-21.98-116.01-84.57-124.08-99.84-1.83-3.47-1.72-7 1.78-8.5s159-61 159-88c0 0-.5-18.5 19.5-18.5" />
</svg>
</div>
</div>

Creating a Snowflake shape that contains text in shape

I'm trying to create a snowflake on my webpage for the winter season.
The first thing I tried was creating it with SVG:
<h3>Koch Snowflake Frac</h3>
<svg viewBox="-5 -5 110 110" xmlns="http://www.w3.org/2000/svg">
<polyline stroke="cornflowerblue" stroke-width="2" fill="rgba(255,255,255,0.5)" points="55 5,
60 10,
65 10,
65 15,
70 20,
75 20,
80 15,
85 20,
90 20,
85 25,
90 30,
80 30,
75 35,
80 40,
90 40,
85 45,
90 50,
85 50,
80 55,
75 50,
70 50,
65 55,
65 60,
60 60,
55 65,
50 60,
45 60,
45 55,
40 50,
35 50,
30 55,
25 50,
20 50,
25 45,
20 40,
30 40,
35 35,
30 30,
20 30,
25 25,
20 20,
25 20,
30 15,
35 20,
40 20,
45 15,
45 10,
50 10,
55 5" />
<foreignObject x="0" y="0" requiredExtensions="http://www.w3.org/1999/xhtml">
<body xmlns="http://www.w3.org/1999/xhtml">
<p>Here is a paragraph that requires word wrap</p>
</body>
</foreignObject>
</svg>
I could not get the <foreignObject> to work and even if I did it's not supported in IE browsers.
There is no need to support old IE browsers, but I would like support in at least one of them.
Also minor detail at the top, the shape is not closed.
Then I tried creating a snowflake in it with CSS:
.snowflake {
position: absolute;
width: 200px;
display: inline-block;
border-bottom: 10px solid cornflowerblue;
top: 200px;
left: 100px;
background-color: white;
}
.snowflake:before {
position: absolute;
content: "";
display: inline-block;
width: 50px;
border-bottom: 10px solid cornflowerblue;
transform: rotate(45deg);
top: -20px;
}
.snowflake:after {
position: absolute;
content: "";
display: inline-block;
width: 50px;
border-bottom: 10px solid cornflowerblue;
transform: rotate(-45deg);
top: 20px;
}
.smallbranch {
position: absolute;
right: 0px;
display: inline-block;
width: 50px;
border-bottom: 10px solid cornflowerblue;
transform: rotate(45deg);
top: 17px;
box-shadow: -130px -5px 0px 0px cornflowerblue;
}
.smallbranch:before {
position: absolute;
content: "";
display: inline-block;
width: 50px;
border-bottom: 10px solid cornflowerblue;
transform: rotate(90deg);
top: -22px;
left: -22px;
box-shadow: 130px -5px 0px 0px cornflowerblue;
}
.circle {
position: absolute;
left: 50%;
width: 50px;
height: 50px;
border-radius: 50%;
border: 5px solid cornflowerblue;
background-color: white;
transform: translate(-50%, -50%);
}
.circle:before {
position: absolute;
content: "";
display: inline-block;
width: 50px;
border-bottom: 10px solid cornflowerblue;
transform: rotate(90deg);
top: -52px;
left: 20px;
transform: rotate(-45deg);
}
.circle:after {
position: absolute;
content: "";
display: inline-block;
width: 50px;
border-bottom: 10px solid cornflowerblue;
transform: rotate(90deg);
top: 102px;
left: 20px;
transform: rotate(45deg);
}
.branch {
position: absolute;
display: inline-block;
height: 200px;
border-right: 10px solid cornflowerblue;
left: 50%;
top: -100px;
}
<div class="snowflake">
<div class="branch"></div>
<div class="smallbranch"></div>
<div class="circle">Text in here</div>
</div>
This was my best attempt with CSS.
Now here the text is displayed but its not on one line. My idea is to use this in a logo or for a button on the webpage. So I don't think I will need line wrap functionality on the shape, but it would be a plus if it had.
The shape I would like created:
TL;DR
What I would like is a snowflake with text in the middle of the shape.
I'm asking for a solution where the text could be any length and still be inside the shape.
You don't have to create a shape that's exactly the same as what I have tried as long as the shape is a snowflake with text in the center it's ok. I don't know how long the text will be so the shape has to contain the text.
Play with this demo
This is actually a quite interesting question, and coming up with an answer was not easy.
The question asks to make a shape(in this case a snowflake), that would scale to fit the text inside of it. My first advice is to use an image, not try and create the shape with CSS. Images are much easier to make scale, and can have more detail then a CSS shape.
So, lets show how we can accomplish this.
First of all, since you want the element to scale to fit the font, we need to make the element display:inline-block. This will make it only be as wide as it's content, unlike block which would make it as wide as it's parent, and still be able to set the height(which you cannot do with inline).
Next, we need to make the element with a height the same as the width. Luckily, there is a trick in CSS that allows you to do just that. The padding of an element is calculated based on it's width, so if you set the padding-bottom(or padding-top) to 100%, it will have the same width as height.(See this excellent SO answer for further info).
After this, it is just a matter of centering the text inside the snowflake, which may take a little playing with the values to fit your font-family.
If you want the jsfiddle with code:
JSFiddle Demo
Full-Screen JSFiddle Demo
Tested in Chrome, FireFox, IE, and Safari. Minor adjustments may be needed for certain font-family's
.snowflake{
display:inline-block;
background:url("http://i.imgur.com/4M9MH1Q.png") scroll no-repeat center/contain;
}
/*This is for setting the height the same as the width, a 1:1 ratio. more info http://www.mademyday.de/css-height-equals-width-with-pure-css.html#outer_wrap */
.snowflake:after{
content: "";
display: block;
padding-top: 100%;
}
.snowflake span{
display:inline-block;
-webkit-transform: translateY(110%);
-ms-transform: translateY(110%);
transform: translateY(110%);
width:100%;
text-align:center;
padding-top:20%;
}
/*This part is ugly, but it is required to work in chrome or IE, you may have to change the char for different font types*/
.snowflake span:before, .snowflake span:after{
content:"aaa";
visibility:hidden;
opacity:0;
}
Font-size 12pt:
<div class="snowflake" style="font-size:12pt;">
<span>It's Snowing!</span>
</div>
Font-size 24pt:
<div class="snowflake" style="font-size:24pt;">
<span>It's Snowing!</span>
</div>
Font-size 48pt:
<div class="snowflake" style="font-size:48pt;">
<span>It's Snowing!</span>
</div>
EDIT: This solution is prettier, but doesn't work in Chrome or IE
.snowflake{
display:inline-block;
background:url("http://i.imgur.com/4M9MH1Q.png") scroll no-repeat center/contain;
}
/*This is for setting the height the same as the width, a 1:1 ratio. more info http://www.mademyday.de/css-height-equals-width-with-pure-css.html#outer_wrap */
.snowflake:after{
content: "";
display: block;
padding-top: 100%;
}
.snowflake span{
display:inline-block;
transform: translateY(90%);
padding:20%;
}
Font-size 12pt:
<div class="snowflake" style="font-size:12pt;">
<span>It's Snowing!</span>
</div>
Font-size 24pt:
<div class="snowflake" style="font-size:24pt;">
<span>It's Snowing!</span>
</div>
Font-size 48pt:
<div class="snowflake" style="font-size:48pt;">
<span>It's Snowing!</span>
</div>
The main condition for this to work is:
.snowflake must be display:inline-block;
Full-Screen JSFiddle Demo
My solution uses part SVG, part HTML/CSS to create the effect required.
I have used the calc() CSS3 function along with viewport based width/height units which then give it the responsiveness required.
It looks much better when the element is above 200px wide.
calc() Browser Support
Viewport Units Browser Support
The Code
.container {
position: relative;
overflow: auto;
display: block;
width: 50vw;
height: 50vw;
}
.container svg {
width: 100%;
height: 100%;
position: absolute;
}
p {
position: absolute;
z-index: 100;
background: white;
left: 17.5vw;
top: 17.5vw;
margin: 0;
padding: 10px 0;
width: 10vw;
text-align: center;
font-size: 2vw;
border: 20px solid black;
}
#media screen and (min-width: 920px) {
p {
font-size: 3vw;
width: 12.5vw;
}
}
<div class="container">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve" width="50%">
<polygon id="christmas-snowflake-icon" points="441.535,346.644 373.955,307.624 438.697,290.354 431.342,262.782 338.967,287.424
284.535,255.999 339.49,224.271 431.299,249.242 438.787,221.705 374.311,204.168 441.535,165.356 427.266,140.644 359.686,179.66
377.1,114.956 349.545,107.541 324.697,199.861 270.27,231.285 270.27,167.831 337.797,100.809 317.695,80.554 270.27,127.624
270.27,50 241.732,50 241.732,128.036 194.404,80.604 174.203,100.76 241.732,168.438 241.732,231.286 186.779,199.558
162.498,107.565 134.906,114.847 151.957,179.455 84.732,140.644 70.465,165.356 138.045,204.373 73.303,221.645 80.66,249.218
173.035,224.574 227.465,255.999 172.51,287.727 80.701,262.758 73.211,290.293 137.688,307.832 70.465,346.644 84.732,371.356
152.312,332.337 134.898,397.042 162.457,404.459 187.303,312.137 241.732,280.711 241.732,344.169 174.203,411.191
194.307,431.446 241.732,384.376 241.732,462 270.27,462 270.27,383.964 317.598,431.396 337.797,411.24 270.27,343.562
270.27,280.712 325.223,312.439 349.502,404.435 377.094,397.15 360.043,332.545 427.268,371.356 "></polygon>
</svg>
<p>This is some text Now</p>
</div>
CodePen
Using svg i have managed to make an almost responsive solution
.box{
display:inline-block;
position:relative;
width:300px;
height:300px;
}
.box:after{
position:absolute;
content:"Some text";
width:20%;
height:20%;
padding:5%;
font-size:100%;
left:calc(50% - 15%);
top:calc(50% - 19%);
}
<div class="box">
<svg viewBox="0 0 99.999997 100" height="100%" width="100%">
<g transform="translate(0,-952.36216)" id="layer1">
<path id="path4701" d="m 23,959 5,-3 6,11 6,-11 6,0 -9,17 4,7 8,-14 7,13 4,-7 -7,-13 3,-5 7,13 6,-11 4,2 -6,11 12,0 3,5 -18,0 -4,8 16,0 -7,13 8,0.12 7,-13 6,0 -7,13 12,0 -0.12,6 -12,-0 5,11 -3,5 -9,-17 -8,0.12 8,14 -16,0 4,8 18,0 -3,5 -12,-0.06 6,11 -4,3 -6,-11 -7,13 -3,-5 7,-13 -4,-7 -7,14 -8,-14 -4,7 9,17 -6,0.12 -6,-11 -6,11 -4,-3 6,-11 -14,-0.12 3,-6 15,0.12 4,-8 -15,-0.063 7,-14 -8,0 -9,17 -3,-5 6,-11 -12,0 0,-6 12,-0.06 -7,-13 6,-0.12 7,13 8,0.06 -7,-13 15,0.06 -4,-8 -15,-0.06 -3,-5 14,-0.06 z" style="fill:#2ad4ff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
</g>
</svg>
</div>
Try re sizing in the following snippet,it stays almost responsive if width and height are increased equally
.box{
display:inline-block;
position:relative;
width:300px;
height:300px;
resize:both;
border:2px solid;
overflow:auto;
}
.box:after{
position:absolute;
content:"Some text";
width:20%;
height:20%;
padding:5%;
font-size:100%;
left:calc(50% - 15%);
top:calc(50% - 19%);
}
<div class="box">
<svg viewBox="0 0 99.999997 100" height="100%" width="100%">
<g transform="translate(0,-952.36216)" id="layer1">
<path id="path4701" d="m 23,959 5,-3 6,11 6,-11 6,0 -9,17 4,7 8,-14 7,13 4,-7 -7,-13 3,-5 7,13 6,-11 4,2 -6,11 12,0 3,5 -18,0 -4,8 16,0 -7,13 8,0.12 7,-13 6,0 -7,13 12,0 -0.12,6 -12,-0 5,11 -3,5 -9,-17 -8,0.12 8,14 -16,0 4,8 18,0 -3,5 -12,-0.06 6,11 -4,3 -6,-11 -7,13 -3,-5 7,-13 -4,-7 -7,14 -8,-14 -4,7 9,17 -6,0.12 -6,-11 -6,11 -4,-3 6,-11 -14,-0.12 3,-6 15,0.12 4,-8 -15,-0.063 7,-14 -8,0 -9,17 -3,-5 6,-11 -12,0 0,-6 12,-0.06 -7,-13 6,-0.12 7,13 8,0.06 -7,-13 15,0.06 -4,-8 -15,-0.06 -3,-5 14,-0.06 z" style="fill:#2ad4ff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
</g>
</svg>
</div>
Using Canvas:
The snowflake can also be created programmatically using HTML5 Canvas. Doing it programmatically allows for better control over the text and keeps it within the container at all times. I am certain that this method can be converted into its equivalent SVG version but am more comfortable with Canvas.
One disadvantage of using Canvas is that the output is not responsive. It becomes blurred (pixelated) when scaled. The only way to avoid it is to redraw the shape once again whenever there is a change to the text (or) dimensions etc.
Construction
The shape is actually comprised of three parts and they are as follows:
Inner star with 6 spikes (the green colored star)
The lines (red colored)
Outer star with 6 spikes (the blue colored star)
The stars are drawn using the same approach as described in my answer here. Basically we assign an inner and an outer radius to each of them, find points on the inner and outer circle and then connect them alternately to produce the star.
The lines are also drawn using a similar approach except that it has only one radius and the lines are drawn from the points on the (imaginary) inner circle of the green star to specified points on the circle.
Calculation of dimensions: Since the text has to be fit within the shape and the shape must expand to fit the text, the first step that needs to be done is to calculate the width that the text requires. This is done using measureText().width. The value that is obtained is set as the radius of the green star's inner circle (because the text has to remain within it).
Finally, since the outer blue star should look as though it is kind of clipped (not appear in full), height and width lesser than that of the blue star's outer radius is set and a border-radius is assigned to the canvas element.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d"),
spikes = 6,
step = Math.PI / spikes,
rot; /* no. of spikes for the stars */
function drawSnowFlake(content, font) {
ctx.font = font; /* font of the text */
/* get width required to fit text and set radius of each star */
var text = ctx.measureText(content);
innerRadius = (text.width / 2) * 1.15; /* extra factor is to leave gap between text and shape */
outerRadius = text.width;
/* set center point for the individual parts of the shape */
x = leftOffset = outerRadius * 1.5;
y = topOffset = outerRadius * 1.5;
/* canvas height and width should be set same as the largest star for clip */
canvas.height = outerRadius * 3;
canvas.width = outerRadius * 3;
/* default settings */
rot = Math.PI / 2 * 3;
ctx.beginPath();
ctx.lineWidth = 15; /* thickness of the lines */
ctx.strokeStyle = 'blue'; /* color of the lines */
ctx.font = font; /* font of the text */
/* create the inner star */
ctx.moveTo(leftOffset, topOffset - outerRadius)
for (i = 0; i < spikes; i++) {
x = leftOffset + Math.cos(rot) * outerRadius;
y = topOffset + Math.sin(rot) * outerRadius;
ctx.lineTo(x, y);
rot += step;
x = leftOffset + Math.cos(rot) * innerRadius;
y = topOffset + Math.sin(rot) * innerRadius;
ctx.lineTo(x, y);
rot += step;
}
ctx.lineTo(leftOffset, topOffset - outerRadius);
ctx.closePath();
ctx.stroke();
ctx.restore();
/* draws the outer star */
ctx.beginPath();
ctx.moveTo(leftOffset, topOffset - (outerRadius * 1.725));
for (i = 0; i < spikes; i++) {
x = leftOffset + Math.cos(rot) * (outerRadius * 1.725);
y = topOffset + Math.sin(rot) * (outerRadius * 1.725);
ctx.lineTo(x, y)
rot += step
x = leftOffset + Math.cos(rot) * (outerRadius * 1.15);
y = topOffset + Math.sin(rot) * (outerRadius * 1.15);
ctx.lineTo(x, y);
rot += step;
}
ctx.lineTo(leftOffset, topOffset - (outerRadius * 1.725));
ctx.closePath();
ctx.stroke();
ctx.restore();
/* draws the lines from the stars */
rot = Math.PI / 3;
ctx.beginPath();
for (i = 0; i < spikes; i++) {
x = leftOffset + Math.cos(rot) * innerRadius;
y = topOffset + Math.sin(rot) * innerRadius;
x2 = leftOffset + Math.cos(rot) * (outerRadius * 1.5);
y2 = topOffset + Math.sin(rot) * (outerRadius * 1.5);
ctx.moveTo(x, y);
ctx.lineTo(x2, y2);
rot += 2 * Math.PI / spikes;
}
ctx.closePath();
ctx.stroke();
/* add text and position it */
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillText(content, leftOffset, topOffset);
}
document.getElementById('draw').addEventListener('click', function() {
var input = document.getElementById('content').value;
var fontsize = document.getElementById('fontsize').value;
var fontname = document.getElementById('fontname').value;
drawSnowFlake(input, fontsize + "px " + fontname);
});
drawSnowFlake("TEXT HERE", "16px Arial");
div {
margin-bottom: 10px;
}
canvas {
border-radius: 50%;
}
<div class='controls'>
<input id='content' type='textbox' value='TEXT HERE' />
<select id='fontsize'>
<option value='10'>10px</option>
<option value='12'>12px</option>
<option value='14'>14px</option>
<option value='16' selected>16px</option>
</select>
<select id='fontname'>
<option value='Arial' selected>Arial</option>
<option value='Verdana'>Verdana</option>
<option value='Courier'>Courier</option>
</select>
<button id='draw'>Draw</button>
</div>
<canvas id='canvas'></canvas>
With Text Wrap and Bevelled Edges:
Here is a more complex demo which also supports wrapping of the text into multiple lines if content exceeds a certain width. The below is what this snippet will do:
If the size required for the text is less than max-width, shrink the shape
If the size required for the text (height or width) is more than max-width, split the text into multiple lines, expand the shape to fit the text in center
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d"),
spikes = 6,
/* no. of spikes for the stars */
step = Math.PI / spikes,
rot, maxWidth = 100,
lineHeight = 20;
function drawSnowFlake(content, font) {
ctx.font = font; /* font of the text */
/* get width & height required to fit text and set radius of each star */
var text = ctx.measureText(content);
var width = text.width;
var height = splitText(ctx, content, maxWidth, lineHeight);
/* decide which among height & width should be used for radius */
if (width < maxWidth && height < maxWidth) {
innerRadius = (width / 2) * 1.15; /* extra factor is to leave gap between text and shape */
outerRadius = width;
} else if (width > maxWidth && height < maxWidth) {
innerRadius = (maxWidth / 2) * 1.15; /* extra factor is to leave gap between text and shape */
outerRadius = maxWidth;
} else {
innerRadius = (height / 2) * 1.15; /* extra factor is to leave gap between text and shape */
outerRadius = height;
}
/* set center point for the individual parts of the shape */
x = leftOffset = outerRadius * 1.5;
y = topOffset = outerRadius * 1.5;
/* canvas height and width should be set same as the largest star for clip */
canvas.height = outerRadius * 3;
canvas.width = outerRadius * 3;
/* default settings */
rot = Math.PI / 2 * 3;
ctx.beginPath();
ctx.lineWidth = 15; /* thickness of the lines */
ctx.lineJoin = 'bevel';
ctx.strokeStyle = 'blue'; /* color of the lines */
ctx.font = font; /* font of the text */
/* create the inner star */
ctx.moveTo(leftOffset, topOffset - outerRadius)
for (i = 0; i < spikes; i++) {
x = leftOffset + Math.cos(rot) * outerRadius;
y = topOffset + Math.sin(rot) * outerRadius;
ctx.lineTo(x, y);
rot += step;
x = leftOffset + Math.cos(rot) * innerRadius;
y = topOffset + Math.sin(rot) * innerRadius;
ctx.lineTo(x, y);
rot += step;
}
ctx.lineTo(leftOffset, topOffset - outerRadius);
ctx.closePath();
ctx.stroke();
ctx.restore();
/* draws the outer star */
ctx.beginPath();
ctx.moveTo(leftOffset, topOffset - (outerRadius * 1.725));
for (i = 0; i < spikes; i++) {
x = leftOffset + Math.cos(rot) * (outerRadius * 1.725);
y = topOffset + Math.sin(rot) * (outerRadius * 1.725);
ctx.lineTo(x, y)
rot += step
x = leftOffset + Math.cos(rot) * (outerRadius * 1.15);
y = topOffset + Math.sin(rot) * (outerRadius * 1.15);
ctx.lineTo(x, y);
rot += step;
}
ctx.lineTo(leftOffset, topOffset - (outerRadius * 1.725));
ctx.closePath();
ctx.stroke();
ctx.restore();
/* draws the lines from the stars */
rot = Math.PI / 3;
ctx.beginPath();
for (i = 0; i < spikes; i++) {
x = leftOffset + Math.cos(rot) * innerRadius;
y = topOffset + Math.sin(rot) * innerRadius;
x2 = leftOffset + Math.cos(rot) * (outerRadius * 1.5);
y2 = topOffset + Math.sin(rot) * (outerRadius * 1.5);
ctx.moveTo(x, y);
ctx.lineTo(x2, y2);
rot += 2 * Math.PI / spikes;
}
ctx.closePath();
ctx.stroke();
/* add text and position it */
wrapText(ctx, content, leftOffset, (topOffset - ((height - lineHeight) / 2)), maxWidth, lineHeight);
}
document.getElementById('draw').addEventListener('click', function() {
var input = document.getElementById('content').value;
var fontsize = document.getElementById('fontsize').value;
var fontname = document.getElementById('fontname').value;
drawSnowFlake(input, fontsize + "px " + fontname);
});
/* split text into lines based on width and calculate required height */
function splitText(context, text, maxWidth, lineHeight) {
var words = text.split(' ');
var line = '',
height = lineHeight;
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
line = words[n] + ' ';
height += lineHeight;
} else {
line = testLine;
}
}
return height;
}
/* source from http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/ */
function wrapText(context, text, x, y, maxWidth, lineHeight) {
var words = text.split(' ');
var line = '';
context.textBaseline = "middle";
context.textAlign = "center";
var topOffset = x;
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
context.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
} else {
line = testLine;
}
}
context.fillText(line, x, y);
}
drawSnowFlake('CSS SHAPES, SVG, CANVAS DESIGNS ROCK', "16px Arial");
div {
margin-bottom: 10px;
}
canvas {
border-radius: 50%;
}
<div class='controls'>
<input id='content' type='textbox' value='CSS SHAPES, SVG, CANVAS DESIGNS ROCK' />
<select id='fontsize'>
<option value='10'>10px</option>
<option value='12'>12px</option>
<option value='14'>14px</option>
<option value='16' selected>16px</option>
</select>
<select id='fontname'>
<option value='Arial' selected>Arial</option>
<option value='Verdana'>Verdana</option>
<option value='Courier'>Courier</option>
</select>
<button id='draw'>Draw</button>
</div>
<canvas id='canvas'></canvas>
The OP wanted the snowflake to scale to fit the size of the text. Most of the other answers are doing it the other way around.
Here's the simplest solution I could come up width. It is mostly just HTML and SVG with a tiny snippet of JS. This is required because I don't believe it is possible to have an SVG scale to fit a parent that doesn't have an explicit size.
Change the text content or the CSS style of the inner <div> to your heart's content and the SVG snowflake will scale to match.
You could almost certainly do away with the JS if you were happy to use a bitmap background image instead of an SVG.
$().ready(function() {
divWidth = $(".snowflake div").outerWidth();
$(".snowflake svg").css("width", divWidth + "px");
});
.snowflake
{
display: inline-block;
position: relative;
}
.snowflake svg
{
position: absolute;
fill: #eef;
}
.snowflake DIV
{
position: absolute;
padding: 100px;
font: 30pt sans-serif;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="snowflake">
<svg viewBox="2 0 32 36">
<path d="M33.212,26.16l-3.054-1.764l1.84-1.062c0.238-0.139,0.32-0.441,0.184-0.684c-0.14-0.238-0.445-0.322-0.684-0.183 L29.16,23.82l-2.32-1.34l4.729-2.73c0.239-0.139,0.321-0.441,0.184-0.684c-0.139-0.238-0.445-0.322-0.684-0.183l-5.23,3.019 l-3.619-2.09l4.352-1.918l-4.354-1.919l3.619-2.091l5.231,3.021c0.079,0.047,0.165,0.067,0.25,0.067 c0.174,0,0.342-0.091,0.435-0.25c0.139-0.239,0.057-0.546-0.184-0.683l-4.731-2.732l2.32-1.34L31.5,13.32 c0.079,0.046,0.165,0.066,0.25,0.066c0.173,0,0.341-0.09,0.435-0.25c0.138-0.238,0.056-0.545-0.184-0.682L30.16,11.39l3.052-1.762 c0.239-0.139,0.32-0.443,0.184-0.684c-0.14-0.238-0.446-0.322-0.684-0.184l-3.051,1.763L29.66,8.401c0-0.275-0.225-0.5-0.5-0.5 c-0.276,0-0.5,0.225-0.5,0.5l0.001,2.699l-2.32,1.34l0.001-5.46c0-0.276-0.224-0.5-0.5-0.5c-0.275,0-0.5,0.224-0.5,0.5 l-0.001,6.037l-3.619,2.09l0.515-4.728l-3.838,2.81V9.008l5.229-3.021c0.238-0.138,0.32-0.443,0.184-0.684 c-0.14-0.238-0.445-0.321-0.684-0.182l-4.729,2.73V5.173l2.339-1.352c0.239-0.138,0.321-0.443,0.184-0.684 c-0.14-0.238-0.445-0.322-0.684-0.182L18.399,4.02V0.5c0-0.276-0.224-0.5-0.5-0.5s-0.5,0.224-0.5,0.5v3.523L15.56,2.961 c-0.24-0.141-0.545-0.057-0.683,0.184c-0.138,0.239-0.056,0.545,0.183,0.684l2.339,1.352v2.678l-4.729-2.73 c-0.24-0.14-0.545-0.057-0.683,0.184c-0.138,0.239-0.056,0.545,0.183,0.684l5.229,3.02v4.184l-3.837-2.811l0.514,4.729 l-3.621-2.092V6.989c0-0.276-0.224-0.5-0.5-0.5c-0.276,0-0.5,0.224-0.5,0.5v5.462l-2.318-1.34L7.136,8.41 c0-0.277-0.224-0.5-0.5-0.5c-0.276,0-0.5,0.223-0.5,0.5l0.001,2.125L3.084,8.771C2.845,8.63,2.539,8.714,2.401,8.955 C2.263,9.194,2.345,9.5,2.584,9.638L5.636,11.4l-1.839,1.062c-0.239,0.139-0.321,0.443-0.183,0.684 c0.093,0.16,0.261,0.25,0.434,0.25c0.085,0,0.171-0.021,0.25-0.066l2.339-1.351l2.319,1.339l-4.729,2.73 c-0.239,0.139-0.321,0.443-0.183,0.684c0.093,0.16,0.261,0.25,0.434,0.25c0.085,0,0.171-0.021,0.25-0.066l5.23-3.021l3.622,2.091 l-4.352,1.919l4.351,1.919l-3.621,2.09l-5.231-3.018c-0.241-0.138-0.545-0.058-0.683,0.184c-0.138,0.24-0.056,0.545,0.183,0.686 l4.731,2.729l-2.321,1.34l-2.338-1.352c-0.239-0.142-0.545-0.058-0.683,0.184c-0.138,0.238-0.056,0.545,0.183,0.684l1.838,1.062 l-3.05,1.76c-0.239,0.139-0.321,0.443-0.183,0.684c0.093,0.16,0.261,0.25,0.434,0.25c0.085,0,0.171-0.021,0.25-0.065l3.051-1.763 L6.14,27.4c0,0.276,0.224,0.5,0.5,0.5l0,0c0.276,0,0.5-0.225,0.5-0.5l-0.001-2.701l2.322-1.34l-0.002,5.463 c0,0.277,0.224,0.5,0.5,0.5s0.5-0.223,0.5-0.5l0.002-6.041l3.619-2.09l-0.514,4.729l3.837-2.81v4.183l-5.228,3.021 c-0.239,0.139-0.321,0.442-0.183,0.684c0.138,0.236,0.444,0.318,0.683,0.184l4.728-2.73v2.679l-2.339,1.353 c-0.239,0.139-0.321,0.442-0.183,0.684c0.138,0.236,0.444,0.32,0.683,0.184l1.839-1.062V35.3c0,0.274,0.224,0.5,0.5,0.5 s0.5-0.226,0.5-0.5v-3.524l1.841,1.062c0.079,0.046,0.165,0.066,0.25,0.066c0.174,0,0.342-0.09,0.435-0.25 c0.139-0.239,0.057-0.545-0.184-0.684l-2.341-1.354v-2.678l4.729,2.73c0.079,0.046,0.165,0.066,0.25,0.066 c0.174,0,0.342-0.09,0.435-0.25c0.139-0.239,0.057-0.545-0.184-0.684l-5.229-3.021V22.6l3.838,2.811l-0.514-4.729l3.62,2.09v6.039 c0,0.276,0.224,0.5,0.5,0.5c0.275,0,0.5-0.224,0.5-0.5V23.35l2.318,1.34l0.001,2.699c0,0.275,0.225,0.5,0.5,0.5s0.5-0.225,0.5-0.5 l-0.001-2.123l3.053,1.764c0.079,0.045,0.165,0.066,0.25,0.066c0.174,0,0.342-0.09,0.435-0.25 C33.536,26.604,33.454,26.296,33.212,26.16z M20.997,23.259l-2.6-1.901l-0.499-0.363l-0.501,0.365l-2.598,1.9l0.348-3.201 l0.067-0.615l-0.567-0.25l-2.945-1.299l2.946-1.299l0.566-0.25l-0.067-0.616l-0.348-3.2l2.598,1.901l0.5,0.364l0.5-0.365l2.6-1.901 l-0.349,3.201l-0.066,0.616l0.564,0.249l2.946,1.3l-2.944,1.299l-0.566,0.25l0.066,0.615L20.997,23.259z"/>
</svg>
<div>Here is some text. It's even wrapped.</div>
</div>

Pulsating color change on svg image

I came across this awesome design today: http://codepen.io/tmrDevelops/pen/jPLpLJ
I really like the pulsating color change. On my website I have an .svg logo that I would like to change color like that when I hover over the image. I would like the actual svg to change color, not the background or anything. I really don't need the on-click animation.
Is this possible with css? I know I have the source right there in the codepen but this is a little above my skills.
Note: It doesn't even have to be based on this code. As long as it looks sort of the same it's ok.
HTML:
<canvas id="canv"></canvas>
<h4>Click For Random Squiggle</h4>
CSS:
#import url(http://fonts.googleapis.com/css?family=Poiret+One);
body {
width: 100%;
margin: 0;
overflow: hidden;
cursor: pointer;
background: hsla(0, 0%, 10%, 1);
font-family: 'Poiret One', serif;
}
h4 {
width: 100%;
position: absolute;
text-align: center;
bottom: 0;
left: 0;
color: hsla(255, 255%, 255%, .5);
font-size: 2em;
letter-spacing: 15px;
user-select: none;
}
JavaScript:
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var max = 50;
var rad = 200;
var c, $, inc, p;
var c = document.getElementById('canv');
var $ = c.getContext('2d');
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;
var u = 0;
var go = function() {
upd();
draw();
}
var upd = function() {
for (var i = 0; i < max; i++) {
if (i % 2 == 0) {
p[i].upd(inc);
} else {
p[i].upd(0);
}
}
}
var draw = function() {
u -= .5;
$.clearRect(0, 0, c.width, c.height);
$.fillStyle = 'hsla(0,0%,10%,1)';
$.fillRect(0, 0, w, h);
var xp1 = (p[0].x + p[max - 1].x) / 2;
var yp1 = (p[0].y + p[max - 1].y) / 2;
/*
first beginPath() set is a shadow mimic -
a black underlay, which is not necessary but
the canvas shadow attr kills the springiness
in FF :/ so using this instead for a lil depth.
*/
$.beginPath();
$.strokeStyle = 'hsla(0,0%,5%,.35)';
$.lineWidth = 26;
$.moveTo(xp1, yp1);
for (var i = 0; i < max - 1; i++) {
var xp = (p[i].x + p[i + 1].x) / 2;
var yp = (p[i].y + p[i + 1].y) / 2;
$.quadraticCurveTo(p[i].x - 2, p[i].y + 2, xp, yp);
}
$.quadraticCurveTo(p[i].x, p[i].y, xp1, yp1);
$.closePath();
$.stroke();
//this one is the actual color circle.
$.beginPath();
$.strokeStyle = 'hsla(' + u + ',100%, 50%,1)';
$.lineWidth = 20;
$.moveTo(xp1, yp1);
for (var i = 0; i < max - 1; i++) {
var xp = (p[i].x + p[i + 1].x) / 2;
var yp = (p[i].y + p[i + 1].y) / 2;
$.quadraticCurveTo(p[i].x, p[i].y, xp, yp);
}
$.quadraticCurveTo(p[i].x, p[i].y, xp1, yp1);
$.closePath();
$.stroke();
}
var set = function() {
var rot = 360 / max;
p = [];
for (var i = 0; i < max; i++) {
var pt = new Pt(w / 2, h / 2);
pt.radii = rot * i;
pt.rad = rad;
pt.ready();
p.push(pt);
}
}
window.addEventListener('mousedown', function() {
var rnd = (Math.random() * 410) + 10;
inc = (inc == 0) ? rnd : 0;
}, false);
var ready = function(e) {
inc = 0;
set();
run();
}
var run = function() {
window.requestAnimFrame(run);
go();
}
var Pt = function(midX, midY) {
this.acc = 5;
this.chng = 1.35;
this.midX = midX;
this.midY = midY;
this.vert = 0;
this.x, this.y, this.rad, this.radii, this.dia;
this.ready = function() {
this.dia = this.rad;
this.XY();
}
this.upd = function(inc) {
var r = this.dia + inc;
this.rad = this.getRad(r, this.rad);
this.XY();
}
this.XY = function() {
var cos = Math.cos(this.radii * (Math.PI / 180)) * this.rad;
var sin = Math.sin(this.radii * (Math.PI / 180)) * this.rad;
this.x = cos + this.midX;
this.y = sin + this.midY;
}
this.getRad = function(mv, cur) {
this.vert = (this.vert + ((mv - cur) / this.acc)) / this.chng;
return this.vert + cur;
}
}
window.addEventListener('resize', function() {
c.width = w = window.innerWidth;
c.height = h = window.innerHeight;
set();
}, false);
ready();
Like this?
.hue {
fill: red;
}
.hue:hover {
animation: pulse 10s infinite;
-webkit-animation: pulse 10s infinite;
}
#keyframes pulse {
0% { fill: #ff0000 }
17% { fill: #ffff00 }
33% { fill: #00ff00 }
50% { fill: #00ffff }
67% { fill: #0000ff }
83% { fill: #ff00ff }
100% { fill: #ff0000 }
}
#-webkit-keyframes pulse {
0% { fill: #ff0000 }
17% { fill: #ffff00 }
33% { fill: #00ff00 }
50% { fill: #00ffff }
67% { fill: #0000ff }
83% { fill: #ff00ff }
100% { fill: #ff0000 }
}
<svg>
<circle cx="150" cy="75" r="70" class="hue"/>
</svg>
This should work in Firefox and Chrome.

CSS - how to create circle pie canvas like this?

I really like this element,
but how to create it? I am not sure what's the correct designation of the element...
Thank you very much.
This effect can be achieved by layering a couple arc()s:
// bright blue full circle
d.beginPath();
d.arc(50, 50, 50, 0, 2 * Math.PI, false);
d.fillStyle = "#aaeeff";
d.fill();
// dark blue percentage circle
d.beginPath();
d.moveTo(50, 50);
d.arc(50, 50, 50, -0.5 * Math.PI, 0.78 * 2 * Math.PI - 0.5 * Math.PI, false);
d.fillStyle = "#00aaff";
d.fill();
// white inner filler
d.beginPath();
d.moveTo(50, 50);
d.arc(50, 50, 25, 0, 2 * Math.PI, false);
d.fillStyle = "#ffffff";
d.fill();
and finally rendering the text:
d.moveTo(50, 50);
d.fillStyle = "#606060";
d.font = "12pt sans-serif";
d.fillText("78%", 36, 56);
Fiddle: http://jsfiddle.net/j6NVg/
Instead of using the <canvas> element, I have chosen to construct the pie chart relying on CSS and JS entirely. The HTML markup is as follow:
<div class="pie" data-percent="78">
<div class="left">
<span></span>
</div>
<div class="right">
<span></span>
</div>
</div>
The CSS is as follow. The trick is to split the circle into two halves (the nested .left and .right elements). The halves will have their overflowing content hidden, and contain nested <span> that we will manipulate with JS for rotation later. Add vendor prefixes when appropriate :)
.pie {
background-color: #eee;
border-radius: 50%;
width: 200px;
height: 200px;
overflow: hidden;
position: relative;
}
.pie > div {
float: left;
width: 50%;
height: 100%;
position: relative;
overflow: hidden;
}
.pie span {
background-color: #4a7298;
display: block;
width: 100%;
height: 100%;
}
.pie .left span {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
-webkit-transform-origin: 100% 50%;
transform-origin: 100% 50%;
}
.pie .right span {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
-webkit-transform-origin: 0% 50%;
transform-origin: 0% 50%;
}
.pie:before,
.pie:after {
border-radius: 50%;
display: block;
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
}
.pie:before {
background-color: #fff;
content: "";
width: 75%;
height: 75%;
z-index: 100;
}
.pie:after {
content: attr(data-percent) "%";
z-index: 200;
text-align: center;
}
I have used the following with jQuery:
$(function() {
$(".pie").each(function() {
var percent = $(this).data("percent").slice(0,-1), // Removes '%'
$left = $(this).find(".left span"),
$right = $(this).find(".right span"),
deg;
if(percent<=50) {
// Hide left
$left.hide();
// Adjust right
deg = 180 - (percent/100*360)
$right.css({
"transform": "rotateZ(-"+deg+"deg)"
});
} else {
// Adjust left
deg = 180 - ((percent-50)/100*360)
$left.css({
"transform": "rotateZ(-"+deg+"deg)"
});
}
});
});
Here is the fiddle: http://jsfiddle.net/Aw5Rf/7/
Check the below links for more info (not an exact one.But you can get some idea).
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Canvas Test</title>
</head>
<body>
<section>
<div>
<canvas id="canvas" width="400" height="300">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>
<script type="text/javascript">
var myColor = ["#ECD078","#D95B43","#C02942","#542437","#53777A"];
var myData = [10,30,20,60,40];
function getTotal(){
var myTotal = 0;
for (var j = 0; j < myData.length; j++) {
myTotal += (typeof myData[j] == 'number') ? myData[j] : 0;
}
return myTotal;
}
function plotData() {
var canvas;
var ctx;
var lastend = 0;
var myTotal = getTotal();
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < myData.length; i++) {
ctx.fillStyle = myColor[i];
ctx.beginPath();
ctx.moveTo(200,150);
ctx.arc(200,150,150,lastend,lastend+
(Math.PI*2*(myData[i]/myTotal)),false);
ctx.lineTo(200,150);
ctx.fill();
lastend += Math.PI*2*(myData[i]/myTotal);
}
}
plotData();
</script>
</section>
</body>
</html>
For more info :Graphing Data in the HTML5 Canvas Element Simple Pie Charts
Another Link : Pure CSS3 Pie Charts effect
This is an online demo: http://jsbin.com/uFaSOwO/1/
First of all what you need can be done exactly using jQuery knob plugin. Still interested in a CSS Solution, than here's what I have done
<div class="load_me"></div>
.load_me {
margin: 100px;
height: 50px;
width: 50px;
border: 5px solid #f00;
border-radius: 50%;
border-top-color: transparent;
}
Demo
Animating the Knob Credits
If you want to prevent the mouse alteration, you can simply add readOnly
$this.knob({
readOnly: true
});
Demo
FIDDLE with ANIMATION
Here's my approach:
var ctx = canvas.getContext('2d');
/*
* in canvas, 0 degrees angle is on the right edge of a circle,
* while we want to start at the top edge of the circle.
* We'll use this variable to compensate the difference.
*/
var relativeAngle = 270;
function drawCanvas() {
ctx.clearRect(0, 0, 90, 90);
//light blue circle
ctx.lineWidth = 20;
ctx.strokeStyle = '#D8E8F7';
ctx.beginPath();
ctx.arc(45, 45, 35, 0, 2*Math.PI);
ctx.stroke();
//dark blue circle
ctx.strokeStyle = '#66ADF4';
ctx.beginPath();
//notice the angle conversion from degrees to radians in the 5th argument
ctx.arc(45, 45, 35, 1.5*Math.PI, ((angle + relativeAngle) / 180) * Math.PI);
ctx.stroke();
//text
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillStyle = '#666';
ctx.font = 'bold 14px serif';
// angle conversion to percentage value
ctx.fillText(parseInt(100 * angle / 360).toString() + '%', 45, 45);
}
var angle;
function timeout() {
angle = parseInt(360 * percent / 100);
drawCanvas();
if (angle > 360) {
document.getElementById('run').disabled = false;
percent = 0;
return;
}
percent++;
setTimeout(timeout, 10);
};
var percent = 0;
/* START the ANIMATION */
timeout();
At the bottom of the code you'll find a self evaluating function timeout which calls the drawCanvas function every 10 miliseconds and increments the blue circle angle. I hope everything is clear here. If not, feel free to ask!
Enjoy it!