A contacts list based on a video from Google's Material Design spec.
A Pen by Kyle Edwards on CodePen.
| <!-- | |
| Based on the contacts video from Meaningful Transitions | |
| https://www.google.com/design/spec/animation/meaningful-transitions.html# | |
| --> | |
| <!-- Centered wrapper --> | |
| <div class="app-wrapper"> | |
| <!-- Header for the 'app' --> | |
| <header class="app-bar"> | |
| <button id="menuToggle" class="app-bar-button menu-toggle menu-is-closed"><i class="fa fa-bars"></i></button> | |
| <h1 id="appHeadline" class="app-headline">All Contacts</h1> | |
| <button id="sortToggle" class="app-bar-button sort-toggle"><i class="fa fa-sort-alpha-asc"></i></button> | |
| </header> | |
| <!-- END 'app' Header --> | |
| <!-- Table of contacts --> | |
| <div class="app-body"> | |
| <!-- List of contacts, these are built dynamicly by javascript on page load --> | |
| <table id="contactList" class="contact-list"> | |
| <tbody> | |
| </tbody> | |
| </table> | |
| <!-- END List of contacts --> | |
| <!-- Contact Info (Details), hidden by default, visible when a contact is selected --> | |
| <!-- This info is not contact item specific so it is not loaded dynamically --> | |
| <table id="contactInfo" class="contact-info"> | |
| <tbody> | |
| <tr id="contactUsername" class="contact-info-item"><td class="contact-info-icon"><i class="fa fa-comment-o"></i></td><td class="contact-info-detail">joedoe</td></tr> | |
| <tr id="contactEmail" class="contact-info-item"><td class="contact-info-icon"><i class="fa fa-envelope-o"></i></td><td class="contact-info-detail">john.smith@email.com</td></tr> | |
| <tr id="contactHomeNumber" class="contact-info-item"><td class="contact-info-icon"><i class="fa fa-phone"></i></td><td class="contact-info-detail">(555) 987-1234</td></tr> | |
| <tr id="contactWorkNumber" class="contact-info-item"><td class="contact-info-icon"><i class="fa fa-building-o"></i></td><td class="contact-info-detail">(555) 987-1234</td></tr> | |
| <tr id="contactAddress" class="contact-info-item"><td class="contact-info-icon"><i class="fa fa-home"></i></td><td class="contact-info-detail">123 Elm Street</td></tr> | |
| </tbody> | |
| </table> | |
| <!-- End Contact Info (Details) --> | |
| </div> | |
| </div> |
A contacts list based on a video from Google's Material Design spec.
A Pen by Kyle Edwards on CodePen.
| // Utility Function | |
| // Takes any collection with a length property that can be | |
| // emumerated numerically. Each item has it's own callback | |
| function forEach(collection, action, scope) { | |
| for (var i = 0; i < collection.length; i++) { | |
| action.call(scope, collection[i], i); | |
| } | |
| } | |
| // Collection of names and randomly generated colors | |
| // randomColor() is from http://llllll.li/randomColor/ | |
| var contactInformation = [{'firstName': 'Micheal', 'lastName': 'Carswell', 'color': randomColor({luminosity : 'light'})}, | |
| { 'firstName': 'Jed', 'lastName': 'Cherry', 'color': randomColor({luminosity : 'light'})}, | |
| { 'firstName': 'Freddie', 'lastName': 'Crimmins', 'color': randomColor({luminosity : 'light'})}, | |
| { 'firstName': 'Dimple', 'lastName': 'Deloatch', 'color': randomColor({luminosity : 'light'})}, | |
| { 'firstName': 'Tomas', 'lastName': 'Duhn', 'color': randomColor({luminosity : 'light'})}, | |
| { 'firstName': 'Coralee', 'lastName': 'Earheart', 'color': randomColor(), 'color': randomColor({luminosity : 'light'})}, | |
| { 'firstName': 'Solomon', 'lastName': 'Magruder', 'color': randomColor({luminosity : 'light'})}, | |
| { 'firstName': 'Antionette', 'lastName': 'May', 'color': randomColor({luminosity : 'light'})}, | |
| { 'firstName': 'Illa', 'lastName': 'Schwindt', 'color': randomColor({luminosity : 'light'})}, | |
| { 'firstName': 'Jesica', 'lastName': 'Utt', 'color': randomColor({luminosity : 'light'})}]; | |
| // Adds a contact to the contact list using the data above | |
| // Structure | |
| // <tbody> | |
| // <tr class="contact"> | |
| // ... | |
| // </tr> | |
| // | |
| // <tr class="contact"> | |
| // <td class="contact-image"> | |
| // <div data-image-color="#xxxxxx"></div> | |
| // </td> | |
| // <td class="contact-name"> | |
| // <span class="first-name"> | |
| // <span class="last-name"> | |
| // <td> | |
| // </tr> | |
| // | |
| // <tr class="contact"> | |
| // ... | |
| // </tr> | |
| // </tbody> | |
| var contactList = document.querySelector('#contactList tbody'); | |
| function addContact(contactInfo) { | |
| var contact = document.createElement('tr'), | |
| contactImgWrapper = document.createElement('td'), | |
| contactImg = document.createElement('div'), | |
| contactName = document.createElement('td'), | |
| firstName = document.createElement('span'), | |
| lastName = document.createElement('span'); | |
| // Add classes to each element in a contact <tr> | |
| contact.classList.add('contact'); | |
| contactName.classList.add('contact-name'); | |
| firstName.classList.add('first-name'); | |
| lastName.classList.add('last-name'); | |
| contactImgWrapper.classList.add('contact-image'); | |
| // Add the color to the contact-color <td> -> <div> | |
| contactImg.style.backgroundColor = contactInfo['color']; | |
| contactImg.setAttribute('data-image-color', contactInfo['color']); | |
| // Append each element to the contact <tr> | |
| // starting with the inner most element | |
| firstName.appendChild(document.createTextNode(contactInfo['firstName'])); | |
| lastName.appendChild(document.createTextNode(contactInfo['lastName'])); | |
| contactImgWrapper.appendChild(contactImg); | |
| contact.appendChild(contactImgWrapper); | |
| contactName.appendChild(firstName); | |
| contactName.appendChild(document.createTextNode(' ')); | |
| contactName.appendChild(lastName); | |
| contact.appendChild(contactName); | |
| // Append the contact to the contact list | |
| contactList.appendChild(contact); | |
| } | |
| // Use the contactInformation collection to build the contact list | |
| forEach(contactInformation, function(contactInfo) { | |
| addContact(contactInfo); | |
| }); | |
| // Event listener for table cells | |
| // Use CSS directly to bring the selected tab into focus | |
| // Rely on CSS Transitions for the animation | |
| function focusSelectedContact(event) { | |
| var currentTarget = event.currentTarget, | |
| appBody = document.querySelector('.app-body'), | |
| rect = currentTarget.getBoundingClientRect(), | |
| appBarRect = appBody.getBoundingClientRect(), | |
| translate = appBarRect.top - rect.top + 171, | |
| root = document.querySelector('html'), | |
| menuToggleIcon = document.querySelector('#menuToggle i'), | |
| contactInfo = document.querySelector('.contact-info'), | |
| color = currentTarget.querySelector('.contact-image div').getAttribute('data-image-color'); | |
| // Add the initial styles to the selected contact | |
| currentTarget.classList.remove('previously-selected'); | |
| currentTarget.classList.add('selected-contact'); | |
| // Hide the contacts that weren't clicked | |
| root.classList.add('hide-contacts'); | |
| root.classList.remove('show-contacts'); | |
| // Reposition the selected contact table cell | |
| currentTarget.style.webkitTransform = 'translateY(' + translate +'px)'; | |
| currentTarget.style.transform = 'translateY(' + translate +'px)'; | |
| currentTarget.offsetHeight; // Force a redraw so the animation works | |
| // Apply a gradient to the table's background | |
| appBody.style.backgroundImage = 'linear-gradient(' + color + ' 0%, #fff 100%)'; | |
| appBody.style.backgroundPosition = '0 0'; | |
| appBody.style.overflow = 'hidden'; | |
| // Change the menu button icons | |
| menuToggleIcon.classList.remove('fa-bars'); | |
| menuToggleIcon.classList.add('fa-arrow-left'); | |
| contactInfo.classList.add('visible'); | |
| } | |
| // Attach the focusSelectedContact event listener to each table cell | |
| forEach(document.querySelectorAll('.contact'), function(contact) { | |
| contact.addEventListener('click', focusSelectedContact); | |
| }); | |
| // Event listener for the menu button | |
| // Undo all of the css set by focusSelectedContact | |
| function showAllContacts() { | |
| var appBody = document.querySelector('.app-body'), | |
| selectedContact = document.querySelector(".selected-contact"), | |
| menuToggleIcon = document.querySelector('#menuToggle i'), | |
| contactInfo = document.querySelector('.contact-info'); | |
| // Slide the selected contact back into its original position | |
| selectedContact.style.webkitTransform = ''; | |
| selectedContact.style.transform = ''; | |
| selectedContact.offsetHeight; // Force a redraw so the browser doesn't skip the animation | |
| // Remove the gradient on the table's background | |
| appBody.style.backgroundPosition = ''; | |
| appBody.style.overflow = 'auto'; | |
| // Revert the menu button icons | |
| menuToggleIcon.classList.add('fa-bars'); | |
| menuToggleIcon.classList.remove('fa-arrow-left'); | |
| contactInfo.classList.remove('visible'); | |
| // After the selected contact is in position (the transition is complete) | |
| // display the other contacts again | |
| setTimeout(function() { | |
| var root = document.querySelector('html'); | |
| root.classList.add('show-contacts'); | |
| root.classList.remove('hide-contacts'); | |
| selectedContact.classList.remove('selected-contact'); | |
| }, 250); | |
| } | |
| // Attach the showAllContacts event listener to the menu button | |
| document.querySelector('#menuToggle').addEventListener('click', showAllContacts); | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/randomcolor/0.1.1/randomColor.min.js"></script> |
| /* Import the Google Font 'Roboto' */ | |
| @import url(http://fonts.googleapis.com/css?family=Roboto:400,700); | |
| /* Hide scrollbars in Webkit browsers*/ | |
| ::-webkit-scrollbar { | |
| display: none; | |
| } | |
| /* Dimensions and base styles for the containers and headers */ | |
| body { | |
| background-color: #f9f9f9; | |
| font-family: 'Roboto', sans-serif; | |
| -webkit-user-select: none; | |
| -moz-user-select: none; | |
| -ms-user-select: none; | |
| } | |
| .app-wrapper { | |
| background-color: #fff; | |
| font-family: sans-serif; | |
| margin: 50px auto; | |
| overflow: hidden; | |
| position: relative; | |
| width: 400px; | |
| } | |
| .app-bar { | |
| background-color: #f2f2f2; | |
| box-shadow: 0 2px 2px #c6c6c6; | |
| box-sizing: border-box; | |
| padding: 15px 70px; | |
| position: absolute; | |
| width: 100%; | |
| z-index: 3; | |
| transition: background-color 500ms, box-shadow 500ms; | |
| } | |
| /* Declarations that hide the header when a contact is selected */ | |
| .app-headline { | |
| color: #5f5f5f; | |
| cursor: default; | |
| display: inline-block; | |
| font-family: 'Roboto', sans-serif; | |
| font-size: 24px; | |
| font-weight: normal; | |
| margin: 0; | |
| opacity: 1; | |
| transition: opacity 500ms; | |
| } | |
| .app-bar-button { | |
| background: none; | |
| border: none; | |
| color: #b1b1b1; | |
| height: 30px; | |
| outline: none; | |
| padding: 2px 0 3px; | |
| text-shadow: 0 0 transparent; | |
| transition: color 500ms, text-shadow 500ms; | |
| width: 30px; | |
| } | |
| .app-bar-button:hover { | |
| color: #c1c1c1; | |
| } | |
| .app-bar-button:active { | |
| color: #919191; | |
| } | |
| #menuToggle .fa { | |
| font-size: 2.25em; | |
| } | |
| #sortToggle .fa { | |
| font-size: 1.75em; | |
| } | |
| .menu-toggle { | |
| left: 13px; | |
| position: absolute; | |
| top: calc(50% - 13px); | |
| } | |
| .sort-toggle { | |
| position: absolute; | |
| right: 13px; | |
| top: calc(50% - 13px); | |
| } | |
| .app-body { | |
| background-image: linear-gradient(transparent, transparent); | |
| background-position: 0 -300px; | |
| background-repeat: no-repeat; | |
| background-size: 100% 238px; | |
| height: 450px; | |
| -ms-overflow-style: none; | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| padding: 62px 0 0; | |
| transition: background-position 500ms; | |
| } | |
| .app-body::-webkit-scrollbar { | |
| display: none; | |
| } | |
| .contact-list { | |
| display: block; | |
| padding: 2px; | |
| } | |
| .contact-list tbody { | |
| display: -webkit-flex; | |
| display: -ms-flex; | |
| display: flex; | |
| -webkit-flex-wrap: wrap; | |
| -ms-flex-wrap: wrap; | |
| flex-wrap: wrap; | |
| } | |
| /* END Dimensions and base styles for the containers and headers */ | |
| /* Declarations for when the header is set to be hidden */ | |
| .hide-contacts .app-bar { | |
| background-color: transparent; | |
| box-shadow: 0 0 0 transparent; | |
| } | |
| .hide-contacts #menuToggle { | |
| cursor: pointer; | |
| } | |
| .hide-contacts .app-headline { | |
| opacity: 0; | |
| } | |
| .hide-contacts .app-bar-button { | |
| color: #fff; | |
| text-shadow: 1px 1px #c1c1c1; | |
| } | |
| /* END Declarations for when the header is set to be hidden */ | |
| /* Contact table cell styles */ | |
| .contact { | |
| border-bottom: 1px solid #f5f5f5; | |
| cursor: pointer; | |
| position: relative; | |
| display: block; | |
| -webkit-flex: 1 0 100%; | |
| -ms-flex: 1 0 100%; | |
| flex: 1 0 100%; | |
| opacity: 1; | |
| overflow: hidden; | |
| -webkit-transition-property: opacity, top, -webkit-transform; | |
| -webkit-transition-property: opacity, top, transform; | |
| -webkit-transition-duration: 500ms; | |
| transition-property: opacity, top, transform; | |
| transition-duration: 500ms; | |
| -webkit-transform: translateY(0); | |
| transform: translateY(0); | |
| } | |
| .contact-image div { | |
| border-radius: 50%; | |
| display: inline-block; | |
| height: 35px; | |
| margin: 10px 13px; | |
| width: 35px; | |
| vertical-align: middle; | |
| } | |
| .contact-name { | |
| color: #515151; | |
| cursor: inherit; | |
| font-family: 'Roboto', sans-serif; | |
| font-size: 16px; | |
| position: relative; | |
| z-index: 2; | |
| -webkit-transition: -webkit-transform 500ms; | |
| transition: transform 500ms; | |
| } | |
| .contact-name .last-name { | |
| color: #4d4d4d; | |
| font-weight: bold; | |
| } | |
| .selected-contact { | |
| cursor: default; | |
| pointer-events: none; | |
| } | |
| .selected-contact .contact-name { | |
| -webkit-transform: scale(1.2) translateX(9px); | |
| transform: scale(1.2) translateX(9px); | |
| } | |
| .hide-contacts .contact:not(.selected-contact) { | |
| pointer-events: none; | |
| -webkit-transform: translateY(50px); | |
| transform: translateY(50px); | |
| opacity: 0; | |
| } | |
| /* END Contact table cell styles */ | |
| /* Contact info styles, hidden by default*/ | |
| .contact-info { | |
| background-color: #fff; | |
| font-size: 18px; | |
| min-height: 20px; | |
| opacity: 0; | |
| position: absolute; | |
| top: 233px; | |
| -webkit-transform: translateY(275px); | |
| transform: translateY(275px); | |
| -webkit-transition: -webkit-transform 500ms, opacity 1000ms; | |
| -webkit-transition: transform 500ms, opacity 1000ms; | |
| transition: transform 500ms, opacity 1000ms; | |
| width: 100%; | |
| z-index: 1; | |
| } | |
| .contact-info.visible { | |
| opacity: 1; | |
| -webkit-transform: translateY(0); | |
| transform: translateY(0); | |
| } | |
| .contact-info { | |
| color: #484848; | |
| } | |
| .contact-info-item { | |
| border-bottom: 1px solid #f5f5f5; | |
| display: block; | |
| } | |
| .contact-info-icon { | |
| height: 35px; | |
| padding: 10px 13px; | |
| width: 35px; | |
| text-align: center; | |
| } | |
| .contact-info-icon i { | |
| font-size: 1.5em; | |
| vertical-align: middle; | |
| } | |
| .contact-info-detail { | |
| padding-left: 5px; | |
| } | |
| /* END Contact info styles */ |
| <link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.1.0/css/font-awesome.css" rel="stylesheet" /> |