HTML
<body ng-app="mdApp">
<section ng-controller="mdController as Ctrl">
<ul>
<li ng-repeat="user in Ctrl.users">
<a ng-click="Ctrl.showProfile($event, $index)">
<div class="imageWrapper">
<img src="{{user.avatar}}" >
</div>
<div class="info">
<h3>{{user.name}}</h3>
<p>{{user.title}}</p>
</div>
</a>
</li>
</ul>
<div class="profile">
<header ng-style="{backgroundColor:Ctrl.user.color}">
<h3>{{Ctrl.user.name}}</h3>
<small>{{Ctrl.user.title}}</small>
</header>
<ul>
<li>
<quote>{{Ctrl.user.description}}</quote>
</li>
<li>
<h4>Address:</h4>
<p>{{Ctrl.user.address}}</p>
</li>
<li>
<h4>Phone:</h4>
<p>{{Ctrl.user.phone}}</p>
</li>
<li>
<h4>Email:</h4>
<p>{{Ctrl.user.email}}</p>
</li>
</ul>
<a class="close-button"
ng-style="{backgroundColor:Ctrl.user.color}"
ng-click="Ctrl.closeProfile($event)">x</a>
</div>
</section>
</body>
SCSS
* {
box-sizing: border-box;
}
body, h3, h4, p, ul {
list-style: none;
margin: 0;
padding: 0;
}
body {
background-color: #232323;
color: #333;
font-family: 'Roboto', sans-serif;
}
a {
background-color: ghostwhite;
cursor: pointer;
display: flex;
flex-direction: row;
padding: 10px;
text-decoration: none;
&:active {
background-color: whitesmoke;
}
}
.floating-item {
display: block;
padding: 10px;
position: fixed;
width: 100%;
z-index: 2;
&, .imageWrapper, .info {
position: absolute;
transition: all 400ms cubic-bezier(0.785, 0.135, 0.150, 0.860);
will-change: left, top;
}
.imageWrapper {
height: 100px;
left: -15px;
top: -15px;
width: 100px;
transform: scale(0.5);
img {
height: 100%;
width: 100%;
}
}
.info {
left: 70px;
right: 0;
}
h3, p {
transition: all 100ms ease-in;
}
&.centered {
.imageWrapper {
left: 50%;
transform: translate3d(-50%, 0, 0) scale(1);
}
h3, p {
opacity: 0;
}
}
}
.imageWrapper {
flex-grow: 0;
flex-shrink: 0;
margin-right: 10px;
overflow: hidden;
img {
border-radius: 50%;
height: 50px;
width: 50px;
}
}
.info {
flex: 1;
}
.profile {
background-color: ghostwhite;
bottom: 0;
left: 0;
opacity: 0;
position: absolute;
right: 0;
top: 0;
transition: all 400ms cubic-bezier(0.785, 0.135, 0.150, 0.860);
transform: translate3d(0,100%,0);
z-index: -1;
&.show {
opacity: 1;
transform: translate3d(0,0,0);
}
.close-button {
bottom: 1em;
border: none;
border-radius: 50%;
box-shadow: 2px 2px 2px rgba(0,0,0,0.3);
color: ghostwhite;
display: block;
font-family: sans-serif;
font-weight: 300;
height: 40px;
position: absolute;
right: 1em;
text-align: center;
width: 40px;
}
header {
background-color: #efefef;
height: 200px;
overflow: hidden;
h3, small {
color: ghostwhite;
left: 0;
position: absolute;
text-align: center;
width: 100%;
}
h3 {
font-weight: 400;
top: 140px;
}
small {
font-weight: 300;
top: 165px;
}
}
li {
opacity: 0;
transition: all 200ms ease-out;
transform: translateY(20px);
&.show {
opacity: 1;
transform: translateY(0);
}
h4 {
border-bottom: 1px solid #ddd;
font-weight: 400;
margin-bottom: .3em;
}
p {
color: #666;
font-weight: 300;
margin: 0 0 1em 0;
}
}
quote {
color: #444;
display: block;
font-weight: 300;
margin-bottom: 1.5em;
padding: 0 0 0 2.2em;
position: relative;
&:before {
color: #666;
content: '"';
font-size: 4.5em;
font-family: 'Passion One', cursive;
left: -.3rem;
position: absolute;
top: -.5rem;
}
}
ul {
display: block;
font-size: 0.9em;
padding: 1.5em;
}
}
section {
background-color: whitesmoke;
border-radius: 15px;
border: 2px solid #999;
box-shadow: 4px 4px 3px rgba(0,0,0,0.3);
height: 480px;
left: 50%;
max-width: 100%;
overflow: hidden;
position: absolute;
top: 50%;
transform: translate(-50%,-50%);
width: 320px;
> ul {
bottom: 0;
left: 0;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
position: absolute;
right: 0;
top: 0;
z-index: 0;
&::-webkit-scrollbar {
display: none;
}
li {
display: block;
border-bottom: 1px solid rgba(0,0,0,0.1);
}
}
}
JAVASCRIPT
(function() {
"use strict";
angular.module("mdApp", []);
// Api
angular.module("mdApp")
.factory("MdApi", MdApi);
function MdApi($http, $q) {
var fn = {},
baseUrl = 'https://gist.githubusercontent.com/ivillamil/e10ca31afcd136a5a7c7/raw/240a27e8c34b5bec7edfa19ee42efad909a85401/demo-users-db.json';
fn.query = query;
function query() {
return $http.get(baseUrl);
}
return fn;
}
// Controller
angular.module("mdApp")
.controller('mdController', mdController);
function mdController(MdApi) {
var vm = this,
$itemFloat,
$itemSelected,
$profile = document.querySelector('.profile'),
$section = $profile.parentNode,
$list = $section.querySelector('ul'),
pos,
contentSpeed = 50;
vm.closeProfile = closeProfile;
vm.showProfile = showProfile;
vm.user = null;
vm.users = [];
MdApi.query()
.then(function(response) {
vm.users = response.data.users;
});
function closeProfile(e) {
e.preventDefault();
var $profile = document.querySelector('.profile'),
$section = $profile.parentNode;
_animateInfo(false)
.then(function() {
setTimeout(function() {
$profile.classList.remove('show');
$itemFloat.style.top = pos + 'px';
$itemFloat.classList.remove('centered');
}, contentSpeed * 2);
setTimeout(function() {
$itemSelected.style.opacity = 1;
}, 500);
setTimeout(function() {
$section.removeChild($itemFloat);
}, 600);
});
}
function showProfile(e, i) {
e.preventDefault();
var $selectedItem = e.target.closest('a'),
$floatingItem = document.createElement('div');
pos = ($selectedItem.offsetTop - $list.scrollTop);
$floatingItem.innerHTML = $selectedItem.innerHTML;
$floatingItem.classList.add('floating-item');
$floatingItem.style.height = $selectedItem.offsetHeight + 'px';
$floatingItem.style.top = pos + 'px';
$floatingItem.style['will-change'] = 'left, top';
$section.appendChild($floatingItem);
$itemFloat = $floatingItem;
$itemSelected = $selectedItem;
$selectedItem.style.opacity = 0;
vm.user = vm.users[i];
setTimeout(function() {
var $imageWrapper = $floatingItem.querySelector('.imageWrapper');
$profile.style.zIndex = 1;
$profile.classList.add('show');
$floatingItem.style.top = '50px';
$floatingItem.classList.add('centered');
}, 50);
setTimeout(function() {
_animateInfo(true);
}, 600);
}
function _animateInfo(show) {
return new Promise(function(resolve, reject) {
var $contentItems = document.querySelectorAll('.profile li'),
i = 0,
length = $contentItems.length,
arr = [];
if (length === 0) return;
for (i = 0; i < length; i++) {
arr.push($contentItems[i]);
}
i = show ? 0 : length;
arr.forEach(function(item) {
var delay = (show ? i++ : i--) * contentSpeed;
setTimeout(function() {
if (show)
item.classList.add('show');
else
item.classList.remove('show');
hasFinished(delay);
}, delay);
});
function hasFinished(d) {
if (d === ((length - 1) * contentSpeed))
resolve();
}
});
}
}
}());