/ / Créer un menu radial en CSS - css, css3, info-bulle, css-Shapes

Création d'un menu radial en CSS - css, css3, infobulle, formes css

Comment créer un menu qui ressemble à ceci ...

Image d'info-bulle

Lien vers PSD

Je ne veux pas utiliser les images PSD. Je préférerais utiliser les icônes de certains paquets comme FontAwesome et avoir les arrière-plans / css générés en CSS.

Vous pouvez trouver une version du menu qui utilise le PSD pour générer des images de l'info-bulle, puis l'utiliser. ici.

Réponses:

978 pour la réponse № 1

Presque trois ans plus tard, j'ai finalement pris le temps de revenir sur cette question et de publier une version améliorée. Vous pouvez toujours voir la réponse originale à la fin pour référence.

Bien que SVG soit le meilleur choix, en particulier aujourd’hui, mon objectif était de ne conserver que le HTML et le CSS, pas de JS, pas de SVG, pas d’image (autre que le fond de l’élément racine).

Captures d'écran

Chrome 43:

Capture d'écran Chrome

Firefox 38:

Firefox capture d'écran

IE 11:

Capture d'écran IE

Code

Le HTML est assez simple. J'utilise la case à cocher pour afficher / masquer le menu.

<input type="checkbox" id="t"/>
<label for="t">✰</label>
<ul>
<li><a href="#">☀</a></li>
<li><a href="#">☃</a></li>
<li><a href="#">☁</a></li>
</ul>

J’utilise Sass pour garder cette logique et faciliter les changements si nécessaire.

$d: 2em; // diameter of central round button
$r: 16em; // radius of menu
$n: 3; // must match number of list items in DOM
$exp: 3em; // menu item height
$tip: .75em; // dimension of tip on middle menu item
$w: .5em; // width of ends
$cover-dim: 2*($r - $exp); // dimension of the link cover
$angle: 15deg; // angle for a menu item
$skew-angle: 90deg - $angle; // how much to skew a menu item to $angle
$scale-factor: cos($skew-angle); // correction factor - see vimeo.com/98137613 from min 15
$off-angle: .125deg; // offset angle so we have a little space between menu items

// don"t show the actual checkbox
input {
transform: translate(-100vw); // move offscreen
visibility: hidden; // avoid paint
}

// change state of menu to revealed on checking the checkbox
input:checked ~ ul {
transform: scale(1);
opacity: .999;
// ease out back from easings.net/#easeOutBack
transition: .5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

// position everything absolutely such that their left bottom corner
// is in the middle of the screen
label, ul, li {
position: absolute;
left: 50%; bottom: 50%;
}

// visual candy styles
label, a {
color: #858596;
font: 700 1em/ #{$d} sans-serif;
text-align: center;
text-shadow: 0 1px 1px #6c6f7e;
cursor: pointer;
}

label {
z-index: 2; // place it above the menu which has z-index: 1
margin: -$d/2; // position correction such that it"s right in the middle
width: $d; height: $d;
border-radius: 50%;
box-shadow: 0 0 1px 1px white,
0 .125em .25em #876366,
0 .125em .5em #876366;
background: radial-gradient(#d4c7c5, #e5e1dd);
}

ul {
z-index: 1;
margin: -$r + $exp + 1.5*$d 0; // position correction
padding: 0;
list-style: none;
transform-origin: 50% (-$r + $exp);
transform: scale(.001); // initial state: scaled down to invisible
will-change: transform; // better perf on transitioning transform
opacity: .001; // initial state: transparent
filter: drop-shadow(0 .125em .25em #847c77)
drop-shadow(0 .125em .5em #847c77);
// ease in back, also from easings.net
transition: .5s cubic-bezier(0.6, -0.28, 0.735, 0.045);

// menu ends
&:before, &:after {
position: absolute;
margin: -$exp (-$w/2);
width: $w; height: $exp;
transform-origin: 50% 100%;
background: linear-gradient(#ddd, #c9c4bf);
content: "";
}

&:before {
border-radius: $w 0 0 $w;
transform: rotate(-.5*$n*$angle)
translate(-$w/2, -$r + $exp);
box-shadow: inset 1px 0 1px #eee;
}
&:after {
border-radius: 0 $w $w 0;
transform: rotate(.5*$n*$angle)
translate($w/2, -$r + $exp);
box-shadow: inset -1px 0 1px #eee;
}
}

li {
overflow: hidden;
width: $r; height: $r;
transform-origin: 0 100%;

@for $i from 0 to $n {
&:nth-child(#{$i + 1}) {
$curr-angle: $i*$angle +
($i + .5)*$off-angle -
.5*$n*($angle + $off-angle);

// make each list item a rhombus rotated around its bottom left corner
// see explanation from minute 33:10 youtube.com/watch?v=ehjoh_MmE9A
transform: rotate($curr-angle)
skewY(-$skew-angle)
scaleX($scale-factor);

// add tip for the item n the middle, just a rotated square
@if $i == ($n - 1)/2 {
a:after {
position: absolute;
top: $exp; left: 50%;
margin: -$tip/2;
width: $tip; height: $tip;
transform: rotate(45deg);
box-shadow:
inset -1px -1px 1px #eee;
background: linear-gradient(-45deg,
#bbb, #c9c4bf 50%);
content: "";
}
}
}
}

a, &:before {
margin: 0 (-$r);
width: 2*$r; height: 2*$r;
border-radius: 50%;
}

&:before, &:after {
position: absolute;
border-radius: 50%;
// undo distorting transforms from menu item (parent li)
transform: scaleX(1/$scale-factor)
skewY($skew-angle);
content: "";
}

// actual background of the arched menu items
&:before {
box-shadow:
inset 0 0 1px 1px #fff,
inset 0 0 $exp #ebe7e2,
inset 0 0 1px ($exp - .0625em) #c9c4bf,
inset 0 0 0 $exp #dcdcdc;
}

// cover to prevent click action in between the star and menu items
&:after {
top: 100%; left: 0;
margin: -$cover-dim/2;
width: $cover-dim; height: $cover-dim;
border-radius: 50%;
}
}

a {
display: block;
// undo distorting transforms from menu item and rotate into right position
transform: scaleX(1/$scale-factor)
skewY($skew-angle)
rotate($angle/2);
line-height: $exp;
text-align: center;
text-decoration: none;
}

html {
overflow: hidden;
background: url(http://i.imgur.com/AeFfmwL.jpg);
}

input {
/* move offscreen */
-webkit-transform: translate(-100vw);
-ms-transform: translate(-100vw);
transform: translate(-100vw);
/* avoid paint */
visibility: hidden;
}

input:checked ~ ul {
-webkit-transform: scale(1);
-ms-transform: scale(1);
transform: scale(1);
opacity: .999;
/* ease out back from easings.net */
-webkit-transition: 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition: 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

label, ul, li {
position: absolute;
left: 50%;
bottom: 50%;
}

label, a {
color: #858596;
font: 700 1em/ 2em sans-serif;
text-align: center;
text-shadow: 0 1px 1px #6c6f7e;
cursor: pointer;
}

label {
z-index: 2;
margin: -1em;
width: 2em;
height: 2em;
border-radius: 50%;
box-shadow: 0 0 1px 1px white,  0 .125em .25em #876366,  0 .125em .5em #876366;
background: #d3d3d3;
background: -webkit-radial-gradient(#d4c7c5, #e5e1dd);
background: radial-gradient(#d4c7c5, #e5e1dd);
}

ul {
z-index: 1;
margin: -10em 0;
padding: 0;
list-style: none;
-webkit-transform-origin: 50% -13em;
-ms-transform-origin: 50% -13em;
transform-origin: 50% -13em;
-webkit-transform: scale(0.001);
-ms-transform: scale(0.001);
transform: scale(0.001);
/* for improved perf on transitioning transform
* https://twitter.com/paul_irish/status/608492121734193152
*/
will-change: transform;
opacity: .001;
-webkit-filter: drop-shadow(0 0.125em 0.25em #847c77);
filter: drop-shadow(0 0.125em 0.25em #847c77);
-webkit-transition: 0.5s cubic-bezier(0.6, -0.28, 0.735, 0.045);
transition: 0.5s cubic-bezier(0.6, -0.28, 0.735, 0.045);
}
ul:before, ul:after {
position: absolute;
margin: -3em -0.25em;
width: 0.5em;
height: 3em;
-webkit-transform-origin: 50% 100%;
-ms-transform-origin: 50% 100%;
transform-origin: 50% 100%;
background: #d3d3d3;
background: -webkit-linear-gradient(#ddd, #c9c4bf);
background: linear-gradient(#ddd, #c9c4bf);
content: "";
}
ul:before {
border-radius: 0.5em 0 0 0.5em;
-webkit-transform: rotate(-22.5deg) translate(-0.25em, -13em);
-ms-transform: rotate(-22.5deg) translate(-0.25em, -13em);
transform: rotate(-22.5deg) translate(-0.25em, -13em);
box-shadow: inset 1px 0 1px #eee;
}
ul:after {
border-radius: 0 0.5em 0.5em 0;
-webkit-transform: rotate(22.5deg) translate(0.25em, -13em);
-ms-transform: rotate(22.5deg) translate(0.25em, -13em);
transform: rotate(22.5deg) translate(0.25em, -13em);
box-shadow: inset -1px 0 1px #eee;
}

li {
overflow: hidden;
width: 16em;
height: 16em;
-webkit-transform-origin: 0 100%;
-ms-transform-origin: 0 100%;
transform-origin: 0 100%;
}
li:nth-child(1) {
-webkit-transform: rotate(-22.625deg) skewY(-75deg) scaleX(0.25882);
-ms-transform: rotate(-22.625deg) skewY(-75deg) scaleX(0.25882);
transform: rotate(-22.625deg) skewY(-75deg) scaleX(0.25882);
}
li:nth-child(2) {
-webkit-transform: rotate(-7.5deg) skewY(-75deg) scaleX(0.25882);
-ms-transform: rotate(-7.5deg) skewY(-75deg) scaleX(0.25882);
transform: rotate(-7.5deg) skewY(-75deg) scaleX(0.25882);
}
li:nth-child(2) a:after {
position: absolute;
top: 3em;
left: 50%;
margin: -0.375em;
width: 0.75em;
height: 0.75em;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
box-shadow: inset -1px -1px 1px #eee;
background: -webkit-linear-gradient(135deg, #bbb, #c9c4bf 50%);
background: linear-gradient(-45deg, #bbb, #c9c4bf 50%);
content: "";
}
li:nth-child(3) {
-webkit-transform: rotate(7.625deg) skewY(-75deg) scaleX(0.25882);
-ms-transform: rotate(7.625deg) skewY(-75deg) scaleX(0.25882);
transform: rotate(7.625deg) skewY(-75deg) scaleX(0.25882);
}
li a, li:before {
margin: 0 -16em;
width: 32em;
height: 32em;
border-radius: 50%;
}
li:before, li:after {
position: absolute;
border-radius: 50%;
-webkit-transform: scaleX(3.8637) skewY(75deg);
-ms-transform: scaleX(3.8637) skewY(75deg);
transform: scaleX(3.8637) skewY(75deg);
content: "";
}
li:before {
box-shadow: inset 0 0 1px 1px #fff, inset 0 0 3em #ebe7e2, inset 0 0 1px 2.9375em #c9c4bf, inset 0 0 0 3em #dcdcdc;
}
li:after {
top: 100%;
left: 0;
margin: -13em;
width: 26em;
height: 26em;
border-radius: 50%;
}

a {
display: block;
-webkit-transform: scaleX(3.8637) skewY(75deg) rotate(7.5deg);
-ms-transform: scaleX(3.8637) skewY(75deg) rotate(7.5deg);
transform: scaleX(3.8637) skewY(75deg) rotate(7.5deg);
line-height: 3em;
text-align: center;
text-decoration: none;
}
<input type="checkbox" id="t"/>
<label for="t">✰</label>
<ul>
<li><a href="#">☀</a></li>
<li><a href="#">☃</a></li>
<li><a href="#">☁</a></li>
</ul>


Réponse originale

Ma tentative de faire quelque chose du genre avec du CSS pur:

démo

(cliquez sur l'étoile)

Fonctionne sous Chrome, Firefox (effet de flou un peu bizarre en vol stationnaire), Opera (les extrémités sont plus petites) et Safari (les extrémités sont plus petites).

* { margin: 0; padding: 0; }
body {
overflow: hidden;
}
/* generic styles for button & circular menu */
.ctrl {
position: absolute;
top: 70%; left: 50%;
font: 1.5em/1.13 Verdana, sans-serif;
transition: .5s;
}
/* generic link styles */
a.ctrl, .ctrl a {
display: block;
opacity: .56;
background: #c9c9c9;
color: #7a8092;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px dimgrey;
}
a.ctrl:hover, .ctrl a:hover, a.ctrl:focus, .ctrl a:focus { opacity: 1; }
a.ctrl:focus, .ctrl a:focus { outline: none; }
.button {
z-index: 2;
margin: -.625em;
width: 1.25em; height: 1.25em;
border-radius: 50%;
box-shadow: 0 0 3px 1px white;
}
/* circular menu */
.tip {
z-index: 1;
/**outline: dotted 1px white;/**/
margin: -5em;
width: 10em; height: 10em;
transform: scale(.001);
list-style: none;
opacity: 0;
}
/* the ends of the menu */
.tip:before, .tip:after {
position: absolute;
top: 34.3%;
width: .5em; height: 14%;
opacity: .56;
background: #c9c9c9;
content: "";
}
.tip:before {
left: 5.4%;
border-radius: .25em 0 0 .25em;
box-shadow: -1px 0 1px dimgrey, inset 1px 0 1px white, inset -1px 0 1px grey,
inset 0 1px 1px white, inset 0 -1px 1px white;
transform: rotate(-75deg);
}
.tip:after {
right: 5.4%;
border-radius: 0 .25em .25em 0;
box-shadow: 1px 0 1px dimgrey, inset -1px 0 1px white, inset 1px 0 1px grey,
inset 0 1px 1px white, inset 0 -1px 1px white;
transform: rotate(75deg);
}
/* make the menu appear on click */
.button:focus + .tip {
transform: scale(1);
opacity: 1;
}
/* slices of the circular menu */
.slice {
overflow: hidden;
position: absolute;
/**outline: dotted 1px yellow;/**/
width: 50%; height: 50%;
transform-origin: 100% 100%;
}
/*
* rotate each slice at the right angle = (A/2)° + (k - (n+1)/2)*A°
* where A is the angle of 1 slice (30° in this case)
* k is the number of the slice (in {1,2,3,4,5} here)
* and n is the number of slices (5 in this case)
* formula works for odd number of slices (n odd)
* for even number of slices (n even) the rotation angle is (k - n/2)*A°
*
* after rotating, skew on Y by 90°-A°; here A° = the angle for 1 slice = 30°
*/
.slice:first-child { transform: rotate(-45deg) skewY(60deg); }
.slice:nth-child(2) { transform: rotate(-15deg) skewY(60deg); }
.slice:nth-child(3) { transform: rotate(15deg) skewY(60deg); }
.slice:nth-child(4) { transform: rotate(45deg) skewY(60deg); }
.slice:last-child { transform: rotate(75deg) skewY(60deg); }
/* covers for the inner part of the links so there"s no hover trigger between
star button & menu links; give them a red background to see them */
.slice:after {
position: absolute;
top: 32%; left: 32%;
width: 136%; height: 136%;
border-radius: 50%;
/* "unskew" = skew by minus the same angle by which parent was skewed */
transform: skewY(-60deg);
content: "";
}
/* menu links */
.slice a {
width: 200%; height: 200%;
border-radius: 50%;
box-shadow: 0 0 3px dimgrey, inset 0 0 4px white;
/* "unskew" & rotate by -A°/2 */
transform: skewY(-60deg) rotate(-15deg);
background: /* lateral separators */
linear-gradient(75deg,
transparent 50%, grey 50%, transparent 54%) no-repeat 36.5% 0,
linear-gradient(-75deg,
transparent 50%, grey 50%, transparent 54%) no-repeat 63.5% 0,
/* make sure inner part is transparent */
radial-gradient(rgba(127,127,127,0) 49%,
rgba(255,255,255,.7) 51%, #c9c9c9 52%);
background-size: 15% 15%, 15% 15%, cover;
line-height: 1.4;
}
/* arrow for middle link */
.slice:nth-child(3) a:after {
position: absolute;
top: 13%; left: 50%;
margin: -.25em;
width: .5em; height: .5em;
box-shadow: 2px 2px 2px white;
transform: rotate(45deg);
background: linear-gradient(-45deg, #c9c9c9 50%, transparent 50%);
content: "";
}
<a class="button ctrl" href="#" tabindex="1">★</a>
<ul class="tip ctrl">
<li class="slice"><a href="#">✦</a></li>
<li class="slice"><a href="#">✿</a></li>
<li class="slice"><a href="#">✵</a></li>
<li class="slice"><a href="#">✪</a></li>
<li class="slice"><a href="#">☀</a></li>
</ul>


62 pour la réponse № 2

La réponse d’Ana est un coup de pied! C’est du sérieux CSS-fu.

Ma solution peut ne pas être tout à fait ce que vous espérezcar, mais c’est une autre solution possible. Je travaille actuellement sur une interface de boussole qui a un style similaire de boutons en forme d’arc. J'ai décidé de le développer en utilisant Raphaël et SVG.

J'ai créé une forme d'arc dans Illustrator, exporté le fichier SVG correspondant, saisi la définition du chemin d'accès pour l'arc à partir du fichier SVG exporté et utilisé Raphael pour créer mon interface avec ce dernier.

En voici un JSFiddle.

Voici le JavaScript:

var arc = {
fill: "#333",
stroke: "#333",
path: "M53.286,44.333L69.081,7.904C48.084-1.199,23.615-2.294,0.648,6.78l14.59,36.928C28.008,38.662,41.612,39.27,53.286,44.333z"
};

var paper = Raphael(document.getElementById("notepad"), 500, 500);

var arcDegrees = 45;
var centerX = 210;
var centerY = 210;
var compassRadius = 68;
var currentlyActive = 45;
var directions = [
{label:"N", degrees:0, rotatedDegrees:270},
{label:"NE", degrees:45, rotatedDegrees:315},
{label:"E", degrees:90, rotatedDegrees:0},
{label:"SE", degrees:135, rotatedDegrees:45},
{label:"S", degrees:180, rotatedDegrees:90},
{label:"SW", degrees:225, rotatedDegrees:135},
{label:"W", degrees:270, rotatedDegrees:180},
{label:"NW", degrees:315, rotatedDegrees:225}
];

function arcClicked()
{
var label = $(this).data("direction-label");
$("#activeArc").attr("id", null);
$(this).attr("id", "activeArc");
}

for (i = 0; i < 360; i += arcDegrees) {
var direction = _.find(directions, function(d) { return d.rotatedDegrees == i; });
var radians = i * (Math.PI / 180);
var x = centerX + Math.cos(radians) * compassRadius;
var y = centerY + Math.sin(radians) * compassRadius;

var newArc = paper.path(arc.path);
// newArc.translate(x, y);
// newArc.rotate(i + 89);
newArc.transform("T" + x + "," + y + "r" + (i + 89));

if (direction.degrees == currentlyActive) {
$(newArc.node).attr("id", "activeArc");
}

$(newArc.node)
.attr("class", "arc")
.data("direction-label", direction.label)
.on("click", arcClicked);
}

Voici le CSS associé:

#notepad {
background: #f7f7f7;
width: 500px;
height: 500px;
}

.arc {
fill: #999;
stroke: #888;
cursor: pointer;
}

.arc:hover {
fill: #777;
stroke: #666;
}

#activeArc {
fill: #F18B21 !important;
stroke: #b86a19 !important;
}

16 pour la réponse № 3

Un autre très bon moyen serait d'utiliser JavaScript pour le positionnement.

DEMO + TUTORIAL sur la création d'un menu radial animé

L’un des avantages de cette méthode est que vous pouvez utiliser n’importe quel nombre d’éléments et il continuera de les positionner de manière radiale, sans avoir à changer votre code CSS.

Le JavaScript en question est:

var items = document.querySelectorAll(".circle a");

for(var i = 0, l = items.length; i < l; i++) {
items[i].style.left = (50 - 35*Math.cos(-0.5 * Math.PI - 2*(1/l)*i*Math.PI)).toFixed(4) + "%";

items[i].style.top = (50 + 35*Math.sin(-0.5 * Math.PI - 2*(1/l)*i*Math.PI)).toFixed(4) + "%";
}

document.querySelector(".menu-button").onclick = function(e) {
e.preventDefault(); document.querySelector(".circle").classList.toggle("open");
}