Skip to content

Instantly share code, notes, and snippets.

@Poordeveloper
Created October 21, 2015 16:26
Show Gist options
  • Save Poordeveloper/ac776e02a307f4ef1c3a to your computer and use it in GitHub Desktop.
Save Poordeveloper/ac776e02a307f4ef1c3a to your computer and use it in GitHub Desktop.
FAB Menu
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<title></title>
<link href="//code.ionicframework.com/nightly/css/ionic.css" rel="stylesheet">
<script src="//code.ionicframework.com/nightly/js/ionic.bundle.js"></script>
<link href='https://fonts.googleapis.com/css?family=RobotoDraft:400,500,700,400italic' rel='stylesheet' type='text/css'>
<script src="https://cdn.rawgit.com/zachsoft/Ionic-Material/master/dist/ionic.material.min.js"></script>
<link href="https://cdn.rawgit.com/zachsoft/Ionic-Material/master/dist/ionic.material.min.css" rel="stylesheet">
</head>
<body ng-app="fabMenu">
<!--<ion-pane>-->
<!--<ion-header-bar class="bar-stable">-->
<!--<h1 class="title">FAB Menu</h1>-->
<!--</ion-header-bar>-->
<!--<ion-content>-->
<div class="card">
<div class="item">
A Material design style menu with fast action buttons. <br>
<ol>
<li>Click to open the menu</li>
<li>Use mouse/touch to spin the buttons (drag on different direction)</li>
</ol>
</div>
</div>
<!--</ion-content>-->
<fab-menu></fab-menu>
<!--</ion-pane>-->
<script type="text/ng-template" id="templates/fab-menu.html">
<div class="fab-menu-overlay" ng-class="{active: fabMenu.active}" ng-click="fabMenuToggle()"></div>
<div class="fab-menu fab-menu-left" ng-class="{active: fabMenu.active}">
<button class="button button-fab fab-menu-button button-energized-900 icon ion-android-menu"
ng-click="fabMenuToggle()"></button>
<ul class="fab-menu-items">
<li>
<button class="button button-fab fab-menu-button-item button-calm-900 icon ion-home"></button>
</li>
<li>
<button class="button button-fab fab-menu-button-item button-positive-900 icon ion-person"></button>
</li>
<li>
<button class="button button-fab fab-menu-button-item button-balanced-900 icon ion-search"></button>
</li>
<li>
<button class="button button-fab fab-menu-button-item button-positive icon ion-person-stalker"></button>
</li>
<li>
<button class="button button-fab fab-menu-button-item button-calm-900 icon ion-map"></button>
</li>
<li>
<button class="button button-fab fab-menu-button-item button-royal-900 icon ion-email"></button>
</li>
<li>
<button class="button button-fab fab-menu-button-item button-assertive icon ion-settings"></button>
</li>
<li>
<button class="button button-fab fab-menu-button-item button-assertive-900 icon ion-log-out"></button>
</li>
</ul>
</div>
</script>
</body>
</html>
angular.module('fabMenu', ['ionic'])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if (window.StatusBar) {
StatusBar.styleDefault();
}
});
})
.directive('fabMenu', function($timeout, $ionicGesture) {
var options = {
baseAngle: 270,
rotationAngle: 30,
distance: 112,
animateInOut: 'all', // can be slide, rotate, all
},
buttons = [],
buttonContainers = [],
buttonsContainer = null,
lastDragTime = 0,
currentX = 0,
currentY = 0,
previousSpeed = 15,
init = function() {
buttons = document.getElementsByClassName('fab-menu-button-item');
buttonContainers = document.querySelectorAll('.fab-menu-items > li');
buttonsContainer = document.getElementsByClassName('fab-menu-items');
for (var i = 0; i < buttonContainers.length; i++) {
var button = buttonContainers.item(i);
var angle = (options.baseAngle + (options.rotationAngle * i));
button.style.transform = "rotate(" + options.baseAngle + "deg) translate(0px) rotate(-" + options.baseAngle + "deg) scale(0)";
button.style.WebkitTransform = "rotate(" + options.baseAngle + "deg) translate(0px) rotate(-" + options.baseAngle + "deg) scale(0)";
button.setAttribute('angle', '' + angle);
}
},
animateButtonsIn = function() {
for (var i = 0; i < buttonContainers.length; i++) {
var button = buttonContainers.item(i);
var angle = button.getAttribute('angle');
button.style.transform = "rotate(" + angle + "deg) translate(" + options.distance + "px) rotate(-" + angle + "deg) scale(1)";
button.style.WebkitTransform = "rotate(" + angle + "deg) translate(" + options.distance + "px) rotate(-" + angle + "deg) scale(1)";
}
},
animateButtonsOut = function() {
for (var i = 0; i < buttonContainers.length; i++) {
var button = buttonContainers.item(i);
var angle = (options.baseAngle + (options.rotationAngle * i));
button.setAttribute('angle', '' + angle);
button.style.transform = "rotate(" + options.baseAngle + "deg) translate(0px) rotate(-" + options.baseAngle + "deg) scale(0)";
button.style.WebkitTransform = "rotate(" + options.baseAngle + "deg) translate(0px) rotate(-" + options.baseAngle + "deg) scale(0)";
}
},
rotateButtons = function(direction, speed) {
// still looking for a better solution to handle the rotation speed
// the direction will be used to define the angle calculation
// max speed value is 25 // can change this :)
// used previousSpeed to reduce the speed diff on each tick
speed = (speed > 15) ? 15 : speed;
speed = (speed + previousSpeed) / 2;
previousSpeed = speed;
var moveAngle = (direction * speed);
// if first item is on top right or last item on bottom left, move no more
if ((parseInt(buttonContainers.item(0).getAttribute('angle')) + moveAngle >= 285 && direction > 0) ||
(parseInt(buttonContainers.item(buttonContainers.length - 1).getAttribute('angle')) + moveAngle <= 345 && direction < 0)
) {
return;
}
for (var i = 0; i < buttonContainers.length; i++) {
var button = buttonContainers.item(i),
angle = parseInt(button.getAttribute('angle'));
angle = angle + moveAngle;
button.setAttribute('angle', '' + angle);
button.style.transform = "rotate(" + angle + "deg) translate(" + options.distance + "px) rotate(-" + angle + "deg) scale(1)";
button.style.WebkitTransform = "rotate(" + angle + "deg) translate(" + options.distance + "px) rotate(-" + angle + "deg) scale(1)";
}
},
endRotateButtons = function() {
for (var i = 0; i < buttonContainers.length; i++) {
var button = buttonContainers.item(i),
angle = parseInt(button.getAttribute('angle')),
diff = angle % options.rotationAngle;
// Round the angle to realign the elements after rotation ends
angle = diff > options.rotationAngle / 2 ? angle + options.rotationAngle - diff : angle - diff;
button.setAttribute('angle', '' + angle);
button.style.transform = "rotate(" + angle + "deg) translate(" + options.distance + "px) rotate(-" + angle + "deg) scale(1)";
button.style.WebkitTransform = "rotate(" + angle + "deg) translate(" + options.distance + "px) rotate(-" + angle + "deg) scale(1)";
}
};
return {
templateUrl: "templates/fab-menu.html",
link: function(scope) {
console.info("fab-menu :: link");
init();
scope.fabMenu = {
active: false
};
var menuItems = angular.element(buttonsContainer);
$ionicGesture.on('touch', function(event) {
console.log('drag starts', event);
lastDragTime = 0;
currentX = event.gesture.deltaX;
currentY = event.gesture.deltaY;
previousSpeed = 0;
}, menuItems)
$ionicGesture.on('release', function(event) {
console.log('drag ends');
endRotateButtons();
}, menuItems);
$ionicGesture.on('drag', function(event) {
if (event.gesture.timeStamp - lastDragTime > 100) {
var direction = 1,
deltaX = event.gesture.deltaX - currentX,
deltaY = event.gesture.deltaY - currentY,
delta = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY));
if ((deltaX <= 0 && deltaY <= 0) || (deltaX <= 0 && Math.abs(deltaX) > Math.abs(deltaY))) {
direction = -1;
} else if ((deltaX >= 0 && deltaY >= 0) || (deltaY <= 0 && Math.abs(deltaX) < Math.abs(deltaY))) {
direction = 1;
}
rotateButtons(direction, delta);
lastDragTime = event.gesture.timeStamp;
currentX = event.gesture.deltaX;
currentY = event.gesture.deltaY;
}
}, menuItems);
scope.fabMenuToggle = function() {
if (scope.fabMenu.active) { // Close Menu
animateButtonsOut();
} else { // Open Menu
animateButtonsIn();
}
scope.fabMenu.active = !scope.fabMenu.active;
}
}
}
})
/*
* Mixins
*/
@mixin display-flex {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
}
@mixin transform($val) {
-webkit-transform: $val;
transform: $val;
}
@mixin translate3d($x, $y, $z) {
@include transform(translate3d($x, $y, $z));
}
@mixin transition($transition...) {
-webkit-transition: $transition;
transition: $transition;
}
body {
background: url(http://rats-funnybone.com/wp-content/uploads/2013/08/aura-background-large-1.jpg) no-repeat;
background-size: 100%;
}
$fab-menu-padding: 20px;
.fab-menu {
@include display-flex();
@include translate3d(0, 0, 0);
@include transform(scale(2));
position: absolute;
top: auto;
right: auto;
left: 0;
bottom: 0;
z-index: 99;
padding: $fab-menu-padding;
&.fab-menu-left {
top: auto;
right: auto;
left: 0;
bottom: 0;
}
&.fab-menu-right {
top: auto;
right: 0;
left: auto;
bottom: 0;
}
.fab-menu-button {
position: absolute;
bottom: $fab-menu-padding;
left: $fab-menu-padding;
z-index: 101;
}
}
.fab-menu-items {
/**
* @see http://hugogiraudel.com/2013/04/02/items-on-circle/
*/
$item-size: 56px; // FAB Button size
//display: none;
z-index: 99;
.fab-menu.active & {
display: block;
}
position: absolute;
width: 168px;
height: 168px;
top: auto;
right: auto;
bottom: 0;
left: 0;
border-radius: 50%;
list-style: none;
> li {
@include transition(all 0.5s ease 0s);
display: block;
position: absolute;
width: $item-size;
height: $item-size;
top: auto;
right: auto;
bottom: $fab-menu-padding;
left: $fab-menu-padding;
}
}
.fab-menu-overlay {
@include transition(all 0.2s ease 0.2s);
position: fixed;
display: none;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
width: 100%;
height: 100%;
z-index: 98;
&.active {
display: block;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment