<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<div class="title">
🐦 <br/> Twitter Responsive Tabs
</div>
<div class="view">
<div class="bio">
<div class="bio--header">
<div class="bio--header-image-wrapper">
<img class="bio--header-image" src="https://pbs.twimg.com/profile_images/780306197610106880/YR1otDu7_400x400.jpg" alt="Lubos' picture" />
</div>
<div class="bio--header-buttons">
<a href="mailto:lmenus@lmen.us" title="Send me an email">Email</a>
<a href="https://twitter.com/lmenus" target="_blank" title="Go to my Twitter profile">Follow</a>
</div>
</div>
<h2>Lubos</h2>
<h3>@lmenus</h3>
<p>Chief Idea Officer. Blog <a href="https://bold.io/@lmenus" target="_blank">bold.io/@lmenus</a></p>
</div>
<div class="tabbar">
<ul class="tabbar--list">
<li class="tabbar--item">Tweets</li>
<li class="tabbar--item">Tweets et réponses</li>
<li class="tabbar--item">Médias</li>
<li class="tabbar--item">J'aime</li>
</ul>
<div class="tabbar--border"><span></span></div>
</div>
<div class="feed">
<h3>«<span class="js-view-name"></span>»</h3>
<p>Sweeet tab view animation 🍬</p>
</div>
</div>
<div class="footer">© 2017 Lubos</div>
$c-bg-bio: #1b2836 !default;
$c-bg-body: #eee !default;
$c-bg-feed: #141d26 !default;
$c-bg-tabbar: #243447 !default;
$c-text-body: #555 !default;
$c-text-feed-secondary: #8899a6 !default;
$c-text-tabbar: #8899a6 !default;
$c-text-tabbar-active: #1da1f2 !default;
$s-bio-border: 8px !default;
$s-bio-image: 120px;
$s-border-h: .2rem !default;
$border-radius: 8px;
$q: 640px !default;
*, *:after, *:before {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(#000, 0);
}
body {
align-items: center;
background-color: $c-bg-body;
color: $c-text-body;
display: flex;
flex-direction: column;
font-family: 'Dancing Script', cursive;
justify-content: center;
min-height: 100vh;
}
a {
color: inherit;
font-weight: 700;
}
.bio {
background-color: $c-bg-bio;
color: #fff;
min-height: 10rem;
padding: 1rem;
padding-top: $s-bio-image * .6;
position: relative;
width: 100%;
> .bio--header {
$margin: 1rem;
align-items: flex-end;
display: flex;
justify-content: space-between;
left: $margin;
min-width: calc(100% - #{$margin * 2});
position: absolute;
top: -$s-bio-image * .4;
> .bio--header-image-wrapper {
background-color: $c-bg-bio;
border: $s-bio-border solid $c-bg-bio;
border-radius: $border-radius;
height: $s-bio-image;
overflow: hidden;
width: $s-bio-image;
> .bio--header-image {
border-radius: $border-radius;
display: block;
height: 100%;
width: 100%;
}
}
> .bio--header-buttons {
display: flex;
position: relative;
top: -$s-bio-border;
> a {
border: 2px solid currentColor;
border-radius: 4px;
color: $c-text-tabbar;
font-size: 1.4em;
font-weight: 600;
margin-left: 8px;
padding: 8px 1em;
text-decoration: none;
transition: border-color .2s, color .2s;
&:hover { color: $c-text-tabbar-active; }
}
}
}
> h2 {
font-size: 1.8rem;
font-weight: 600;
margin: 1rem auto .4rem;
}
> h3 {
color: $c-text-feed-secondary;
font-size: 1.3rem;
font-weight: 600;
margin: .4rem auto;
}
> p {
color: #fff;
font-size: 1.4rem;
font-weight: 500;
line-height: 1.4;
margin: 1rem auto;
> a {
color: $c-text-tabbar-active;
font-weight: inherit;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
.feed {
background-color: $c-bg-feed;
color: #fff;
min-height: 30rem;
padding: 3rem;
text-align: center;
width: 100%;
> h3 {
font-size: 1.8rem;
font-weight: 600;
line-height: 1.2;
margin: 1.2rem auto;
}
> p {
color: $c-text-feed-secondary;
font-size: 1.2rem;
font-weight: 600;
margin: 1.2rem auto;
}
}
.footer {
font-size: 2em;
line-height: 1.6;
text-align: center;
}
.tabbar {
backface-visibility: hidden;
display: block;
margin: 0 auto;
max-width: 100%;
position: relative;
transform: translateZ(0) scale(1);
> .tabbar--list {
border-radius: 2px;
display: flex;
> .tabbar--item {
align-items: center;
background-color: $c-bg-tabbar;
border-bottom: $s-border-h solid transparent;
color: $c-text-tabbar;
cursor: pointer;
display: flex;
font-size: 1.2em;
font-weight: 700;
height: 2em;
justify-content: center;
padding: 1.6em 1.2em;
text-transform: uppercase;
white-space: nowrap;
&:hover,
&.active {
color: $c-text-tabbar-active;
}
}
}
> .tabbar--border {
background-color: $c-text-tabbar-active;
bottom: 0;
display: block;
height: $s-border-h;
left: 0;
position: absolute;
width: 0;
> span {
background-color: $c-text-tabbar-active;
display: block;
height: 100%;
transform-origin: 0 50%;
width: 0;
}
}
}
.title {
font-size: 4em;
line-height: 1.2;
text-align: center;
}
.view {
box-shadow: 15px 15px 3px rgba(#000, .3);
font-family: 'Open Sans', sans-serif;
margin: 5rem auto 1.6rem;
max-width: 100%;
@media screen and (min-width: $q) {
max-width: $q;
}
}
const TABBAR_BORDER_TRANSFORM_EASING = 'ease'
const TABBAR_BORDER_TRANSFORM_SPEED = 400
const TABBAR_ITEM_ACTIVE_CLASSNAME = 'active'
let _ActiveTabBarItemNode = null
let _ActiveTabBarItemIndex = -1
let _TabBarBorderNode = null
let _TabBarListNode = null
let _FeedViewName = null
const addTabBarItemProps = () => {
const items = _TabBarListNode.children
for (let i = 0; i < items.length; i++) {
const item = items[i]
item.style.transition = `color ${TABBAR_BORDER_TRANSFORM_SPEED}ms ${TABBAR_BORDER_TRANSFORM_EASING}`
item.addEventListener('click', () => setTabBarItemActive(i) )
}
}
const getNodeCenter = clientRect => {
return (clientRect.left + clientRect.right) / 2
}
const getTabBarItem = index => {
return index < _TabBarListNode.children.length ? _TabBarListNode.children[index] : false
}
const initTabBar = index => {
const node = document.getElementsByClassName('tabbar')[0]
const feedName = document.getElementsByClassName('js-view-name')[0]
if (_TabBarListNode || _TabBarBorderNode || !node) return
if (feedName) _FeedViewName = feedName
_TabBarListNode = node.children[0]
_TabBarBorderNode = node.children[1]
// This way, we can use integer numbers for scale transform.
_TabBarBorderNode.children[0].style.width = '1px'
// Wait until the border has applied styles.
setTimeout( () => {
addTabBarItemProps()
// Do not animate the first state transition.
setTabBarItemActive(index).then( () => {
setTimeout( () => {
_TabBarBorderNode.style.transition = `transform ${TABBAR_BORDER_TRANSFORM_SPEED}ms ${TABBAR_BORDER_TRANSFORM_EASING}`
_TabBarBorderNode.children[0].style.transition = `transform ${TABBAR_BORDER_TRANSFORM_SPEED}ms ${TABBAR_BORDER_TRANSFORM_EASING}`
window.addEventListener('resize', () => setTabBarItemActive(_ActiveTabBarItemIndex, true) )
}, 0)
})
}, 100)
}
const moveTabBarBorder = (index, noAnimation) => {
return new Promise( (resolve, reject) => {
let activeItemClientRect = _ActiveTabBarItemNode.getBoundingClientRect()
let borderClientRect = _TabBarBorderNode.getBoundingClientRect()
let firstItemClientRect = getTabBarItem(0).getBoundingClientRect()
let tempTabBarBorderNodeStyle,
tempTabBarBorderSpanStyle
activeItemClientRect.centerX = getNodeCenter(activeItemClientRect)
borderClientRect.centerX = getNodeCenter(borderClientRect)
const currentTransformTranslateX = borderClientRect.left - (firstItemClientRect.left < 0 ? 0 : firstItemClientRect.left)
const newTransformTranslateX = currentTransformTranslateX + (activeItemClientRect.centerX - borderClientRect.centerX)
if (noAnimation) {
tempTabBarBorderNodeStyle = _TabBarBorderNode.style.transition
tempTabBarBorderSpanStyle = _TabBarBorderNode.children[0].style.transition
_TabBarBorderNode.style.transition = ''
_TabBarBorderNode.children[0].style.transition = ''
}
setTimeout( () => {
_TabBarBorderNode.style.transform = `translateX(${newTransformTranslateX}px)`
_TabBarBorderNode.children[0].style.transform = `scaleX(${activeItemClientRect.width}) translateX(-50%)`
if (noAnimation) {
setTimeout( () => {
if (tempTabBarBorderNodeStyle) {
_TabBarBorderNode.style.transition = tempTabBarBorderNodeStyle
}
if (tempTabBarBorderSpanStyle) {
_TabBarBorderNode.children[0].style.transition = tempTabBarBorderSpanStyle
}
return resolve()
}, 0)
}
return resolve()
}, 0)
})
}
const setTabBarItemActive = (index, isEventListener) => {
return new Promise( (resolve, reject) => {
if (index === -1) return resolve()
// Event listeners do not change the index.
if (isEventListener) moveTabBarBorder(index, isEventListener).then( () => resolve() )
if (_ActiveTabBarItemNode) {
_ActiveTabBarItemNode.classList.remove(TABBAR_ITEM_ACTIVE_CLASSNAME)
}
_ActiveTabBarItemIndex = index
_ActiveTabBarItemNode = getTabBarItem(index)
_ActiveTabBarItemNode.classList.add(TABBAR_ITEM_ACTIVE_CLASSNAME)
_FeedViewName.innerHTML = _ActiveTabBarItemNode.innerHTML
moveTabBarBorder(index).then( () => resolve() )
})
}
window.addEventListener('load', () => initTabBar(0) )