Browse Source

Completed zoom-fade animation

Fix new menu button animation
master
morethanwords 4 years ago
parent
commit
6d8193671a
  1. 113
      src/components/horizontalMenu.ts
  2. 85
      src/components/sidebarLeft/index.ts
  3. 123
      src/components/transition.ts
  4. 6
      src/index.hbs
  5. 2
      src/scss/partials/_avatar.scss
  6. 2
      src/scss/partials/_chatBubble.scss
  7. 2
      src/scss/partials/_emojiDropdown.scss
  8. 18
      src/scss/partials/_leftSidebar.scss
  9. 143
      src/scss/style.scss

113
src/components/horizontalMenu.ts

@ -1,117 +1,10 @@ @@ -1,117 +1,10 @@
import { findUpTag, whichChild } from "../lib/utils";
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, width: number, toRight: boolean) {
const elements = [tabContent, prevTabContent];
if(toRight) elements.reverse();
elements[0].style.filter = `brightness(80%)`;
elements[0].style.transform = `translate3d(${-width * .25}px, 0, 0)`;
elements[1].style.transform = `translate3d(${width}px, 0, 0)`;
tabContent.classList.add('active');
void tabContent.offsetWidth; // reflow
tabContent.style.transform = '';
tabContent.style.filter = '';
}
function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, width: number, toRight: boolean) {
const elements = [tabContent, prevTabContent];
if(toRight) elements.reverse();
elements[0].style.transform = `translate3d(${-width}px, 0, 0)`;
elements[1].style.transform = `translate3d(${width}px, 0, 0)`;
tabContent.classList.add('active');
void tabContent.offsetWidth; // reflow
tabContent.style.transform = '';
}
import Transition from "./transition";
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250) {
const hideTimeouts: {[id: number]: number} = {};
//const deferred: (() => void)[] = [];
let transitionEndTimeout: number;
let prevTabContent: HTMLElement = null;
let prevId = -1;
const selectTab = (id: number, animate = true) => {
if(id == prevId) return false;
//console.log('selectTab id:', id);
const p = prevTabContent;
const tabContent = content.children[id] as HTMLElement;
// * means animation isn't needed
if(content.dataset.slider == 'none' || !animate) {
if(p) {
p.classList.remove('active');
}
tabContent.classList.add('active');
prevId = id;
prevTabContent = tabContent;
if(onTransitionEnd) onTransitionEnd();
return;
}
if(prevTabContent) {
prevTabContent.classList.remove('to');
}
const toRight = prevId < id;
if(!tabContent) {
//prevTabContent.classList.remove('active');
} else if(prevId != -1) {
const width = prevTabContent.getBoundingClientRect().width;
if(tabs || content.dataset.slider == 'tabs') {
slideTabs(tabContent, prevTabContent, width, toRight);
} else {
slideNavigation(tabContent, prevTabContent, width, toRight);
}
tabContent.classList.add('to');
} else {
tabContent.classList.add('active');
}
const _prevId = prevId;
if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]);
if(p/* && false */) {
hideTimeouts[_prevId] = window.setTimeout(() => {
p.style.transform = '';
p.style.filter = '';
p.classList.remove('active');
if(tabContent) {
tabContent.classList.remove('to');
}
delete hideTimeouts[_prevId];
}, /* 420 */transitionTime);
if(onTransitionEnd) {
if(transitionEndTimeout) clearTimeout(transitionEndTimeout);
transitionEndTimeout = window.setTimeout(() => {
onTransitionEnd();
transitionEndTimeout = 0;
}, transitionTime);
}
}
prevId = id;
prevTabContent = tabContent;
/* if(p) {
return new Promise((resolve) => {
deferred.push(resolve);
});
} else {
return Promise.resolve();
} */
};
const selectTab = Transition(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
if(tabs) {
const useStripe = !tabs.classList.contains('no-stripe');
@ -173,6 +66,8 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? @@ -173,6 +66,8 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
target.classList.add('active');
selectTab(id);
prevId = id;
});
}

85
src/components/sidebarLeft/index.ts

@ -1,29 +1,30 @@ @@ -1,29 +1,30 @@
//import { logger } from "../polyfill";
import appChatsManager from "../../lib/appManagers/appChatsManager";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import { findUpTag, findUpClassName, formatNumber } from "../../lib/utils";
import appImManager from "../../lib/appManagers/appImManager";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import appStateManager from "../../lib/appManagers/appStateManager";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
import $rootScope from "../../lib/rootScope";
import { findUpClassName, findUpTag, formatNumber } from "../../lib/utils";
import AppSearch, { SearchGroup } from "../appSearch";
import AvatarElement from "../avatar";
import { parseMenuButtonsTo } from "../misc";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import { ScrollableX } from "../scrollable";
import AvatarElement from "../avatar";
import AppNewChannelTab from "./tabs/newChannel";
import SearchInput from "../searchInput";
import SidebarSlider from "../slider";
import Transition from "../transition";
import AppAddMembersTab from "./tabs/addMembers";
import AppContactsTab from "./tabs/contacts";
import AppNewGroupTab from "./tabs/newGroup";
import AppSettingsTab from "./tabs/settings";
import AppEditProfileTab from "./tabs/editProfile";
import AppArchivedTab from "./tabs/archivedTab";
import AppChatFoldersTab from "./tabs/chatFolders";
import AppContactsTab from "./tabs/contacts";
import AppEditFolderTab from "./tabs/editFolder";
import AppEditProfileTab from "./tabs/editProfile";
import AppIncludedChatsTab from "./tabs/includedChats";
import SidebarSlider from "../slider";
import SearchInput from "../searchInput";
import appStateManager from "../../lib/appManagers/appStateManager";
import appChatsManager from "../../lib/appManagers/appChatsManager";
import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
import $rootScope from "../../lib/rootScope";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import AppArchivedTab from "./tabs/archivedTab";
import AppNewChannelTab from "./tabs/newChannel";
import AppNewGroupTab from "./tabs/newGroup";
import AppSettingsTab from "./tabs/settings";
AvatarElement;
@ -38,6 +39,30 @@ const editFolderTab = new AppEditFolderTab(); @@ -38,6 +39,30 @@ const editFolderTab = new AppEditFolderTab();
const includedChatsTab = new AppIncludedChatsTab();
const archivedTab = new AppArchivedTab();
/* const Transition = (container: HTMLElement, duration: number, from: HTMLElement, to: HTMLElement) => {
if(to.classList.contains('active')) return Promise.resolve();
container.classList.add('animating');
const backwards = whichChild(to) < whichChild(from);
if(backwards) {
container.classList.add('backwards');
}
from.classList.add('from');
to.classList.add('to');
return new Promise((resolve) => {
setTimeout(() => {
from.classList.remove('from', 'active');
container.classList.remove('animating', 'backwards');
to.classList.replace('to', 'active');
resolve();
}, duration);
});
}; */
export class AppSidebarLeft extends SidebarSlider {
public static SLIDERITEMSIDS = {
archived: 1,
@ -195,12 +220,28 @@ export class AppSidebarLeft extends SidebarSlider { @@ -195,12 +220,28 @@ export class AppSidebarLeft extends SidebarSlider {
});
});
let hideNewBtnMenuTimeout: number;
//const transition = Transition.bind(null, this.searchContainer.parentElement, 150);
const transition = Transition(this.searchContainer.parentElement, 'zoom-fade', 150, (id) => {
if(hideNewBtnMenuTimeout) clearTimeout(hideNewBtnMenuTimeout);
if(id == 0) {
this.globalSearch.reset();
hideNewBtnMenuTimeout = window.setTimeout(() => {
hideNewBtnMenuTimeout = 0;
this.newBtnMenu.classList.remove('is-hidden');
}, 150);
}
});
transition(0);
const onFocus = () => {
this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active');
this.searchContainer.classList.remove('hide');
void this.searchContainer.offsetWidth; // reflow
this.searchContainer.classList.add('active');
this.newBtnMenu.classList.add('is-hidden');
transition(1);
if(firstTime) {
this.searchGroups.people.setActive();
@ -225,13 +266,9 @@ export class AppSidebarLeft extends SidebarSlider { @@ -225,13 +266,9 @@ export class AppSidebarLeft extends SidebarSlider {
//appDialogsManager.chatsArchivedContainer.classList.remove('active');
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.searchContainer.classList.remove('active');
firstTime = true;
setTimeout(() => {
this.searchContainer.classList.add('hide');
this.globalSearch.reset();
}, 150);
transition(0);
});
this.renderRecentSearch();

123
src/components/transition.ts

@ -0,0 +1,123 @@ @@ -0,0 +1,123 @@
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
const width = prevTabContent.getBoundingClientRect().width;
const elements = [tabContent, prevTabContent];
if(toRight) elements.reverse();
elements[0].style.filter = `brightness(80%)`;
elements[0].style.transform = `translate3d(${-width * .25}px, 0, 0)`;
elements[1].style.transform = `translate3d(${width}px, 0, 0)`;
tabContent.classList.add('active');
void tabContent.offsetWidth; // reflow
tabContent.style.transform = '';
tabContent.style.filter = '';
}
function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
const width = prevTabContent.getBoundingClientRect().width;
const elements = [tabContent, prevTabContent];
if(toRight) elements.reverse();
elements[0].style.transform = `translate3d(${-width}px, 0, 0)`;
elements[1].style.transform = `translate3d(${width}px, 0, 0)`;
tabContent.classList.add('active');
void tabContent.offsetWidth; // reflow
tabContent.style.transform = '';
}
const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fade', transitionTime: number, onTransitionEnd?: (id: number) => void) => {
const hideTimeouts: {[id: number]: number} = {};
//const deferred: (() => void)[] = [];
let transitionEndTimeout: number;
let prevTabContent: HTMLElement = null;
let prevId = -1;
const animationFunction = type == 'zoom-fade' ? null : (type == 'tabs' ? slideTabs : slideNavigation);
const selectTab = (id: number, animate = true) => {
if(id == prevId) return false;
//console.log('selectTab id:', id);
const p = prevTabContent;
const tabContent = content.children[id] as HTMLElement;
// * means animation isn't needed
if(content.dataset.slider == 'none' || !animate) {
if(p) {
p.classList.remove('active');
}
tabContent.classList.add('active');
prevId = id;
prevTabContent = tabContent;
if(onTransitionEnd) onTransitionEnd(prevId);
return;
}
if(prevTabContent) {
prevTabContent.classList.remove('to');
prevTabContent.classList.add('from');
}
content.classList.add('animating');
const toRight = prevId < id;
content.classList.toggle('backwards', !toRight);
if(!tabContent) {
//prevTabContent.classList.remove('active');
} else if(prevId != -1) {
if(animationFunction) {
animationFunction(tabContent, prevTabContent, toRight);
}
tabContent.classList.add('to');
} else {
tabContent.classList.add('active');
}
const _prevId = prevId;
if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]);
if(p/* && false */) {
hideTimeouts[_prevId] = window.setTimeout(() => {
p.style.transform = p.style.filter = '';
p.classList.remove('active', 'from');
content.classList.remove('animating', 'backwards');
if(tabContent) {
tabContent.classList.remove('to');
tabContent.classList.add('active');
}
delete hideTimeouts[_prevId];
}, transitionTime);
if(onTransitionEnd) {
if(transitionEndTimeout) clearTimeout(transitionEndTimeout);
transitionEndTimeout = window.setTimeout(() => {
onTransitionEnd(prevId);
transitionEndTimeout = 0;
}, transitionTime);
}
}
prevId = id;
prevTabContent = tabContent;
/* if(p) {
return new Promise((resolve) => {
deferred.push(resolve);
});
} else {
return Promise.resolve();
} */
};
return selectTab;
};
export default Transition;

6
src/index.hbs

@ -209,8 +209,8 @@ @@ -209,8 +209,8 @@
<div class="btn-icon tgico-back rp sidebar-back-button"></div>
</div>
</div>
<div class="sidebar-content">
<div id="chats-container">
<div class="sidebar-content transition zoom-fade">
<div class="transition-item active" id="chats-container">
<div class="folders-tabs-scrollable hide">
<nav class="menu-horizontal" id="folders-tabs">
<ul>
@ -225,7 +225,7 @@ @@ -225,7 +225,7 @@
</div>
</div>
</div>
<div class="sidebar-search hide" id="search-container"></div>
<div class="transition-item sidebar-search" id="search-container"></div>
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-newchat_filled btn-menu-toggle" id="new-menu">
<div class="btn-menu top-left">
<div class="btn-menu-item menu-channel tgico-newchannel rp">New Channel</div>

2
src/scss/partials/_avatar.scss

@ -27,7 +27,7 @@ avatar-element { @@ -27,7 +27,7 @@ avatar-element {
user-select: none;
&.fade-in {
animation: fadeIn .2s ease forwards;
animation: fade-in-opacity .2s ease forwards;
}
}

2
src/scss/partials/_chatBubble.scss

@ -1649,7 +1649,7 @@ poll-element { @@ -1649,7 +1649,7 @@ poll-element {
font-size: 14px;
line-height: 1.4;
opacity: 0;
animation: fadeIn .1s ease forwards;
animation: fade-in-opacity .1s ease forwards;
animation-direction: reverse;
animation-delay: .24s;
text-align: center;

2
src/scss/partials/_emojiDropdown.scss

@ -240,7 +240,7 @@ @@ -240,7 +240,7 @@
}
> img {
animation: fadeIn .2s ease forwards;
animation: fade-in-opacity .2s ease forwards;
object-fit: contain;
}
}

18
src/scss/partials/_leftSidebar.scss

@ -273,31 +273,25 @@ @@ -273,31 +273,25 @@
bottom: 14px;
right: 14px;
transform: translateY(0px);
position: fixed !important;
z-index: 1;
&:not(.is-hidden) {
transform: translateZ(0px);
}
}
}
@include respond-to(not-handhelds) {
html.no-touch &:hover .btn-corner {
transform: translateY(0px);
html.no-touch &:hover .btn-corner:not(.is-hidden) {
transform: translateZ(0px);
}
}
}
}
#search-container {
transition: .15s ease-in-out opacity, .15s ease-in-out transform;
transform: scale(1.1, 1.1);
opacity: 0;
display: flex;
&.active {
transform: scale(1, 1);
transform-origin: center;
opacity: 1;
}
}
.new-channel-container, .new-group-container, .edit-profile-container {

143
src/scss/style.scss

@ -455,7 +455,7 @@ input, textarea { @@ -455,7 +455,7 @@ input, textarea {
z-index: 4;
}
@keyframes fadeIn {
@keyframes fade-in-opacity {
0% {
opacity: 0;
}
@ -465,7 +465,35 @@ input, textarea { @@ -465,7 +465,35 @@ input, textarea {
}
}
@keyframes fadeInFadeOut {
@keyframes fade-out-opacity {
0% {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes fade-in-backwards-opacity {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes fade-out-backwards-opacity {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-in-opacity-fade-out-opacity {
0% {
opacity: 0;
}
@ -493,7 +521,7 @@ input, textarea { @@ -493,7 +521,7 @@ input, textarea {
color: #fff;
font-size: 1rem;
border-radius: $border-radius-medium;
animation: fadeInFadeOut 3s linear forwards;
animation: fade-in-opacity-fade-out-opacity 3s linear forwards;
z-index: 5;
}
@ -1146,7 +1174,7 @@ img.emoji { @@ -1146,7 +1174,7 @@ img.emoji {
height: 100%;
&.fade-in {
animation: fadeIn .2s ease forwards;
animation: fade-in-opacity .2s ease forwards;
}
}
@ -1256,6 +1284,113 @@ img.emoji { @@ -1256,6 +1284,113 @@ img.emoji {
}
}
.transition {
.transition-item {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
animation-fill-mode: forwards!important;
&:not(.active):not(.from):not(.to) {
display: none !important; // Best performance when animating container
//transform: scale(0); // Shortest initial delay
}
}
/*
* zoom-fade
*/
&.zoom-fade {
> .from {
transform-origin: center;
transform: scale(1);
opacity: 1;
}
> .to {
transform-origin: center;
opacity: 0;
// We can omit `transform: scale(1.1);` here because `opacity` is 0.
// We need to for proper position calculation in `InfiniteScroll`.
}
&.animating {
> .from {
animation: fade-out-opacity .15s ease;
}
> .to {
animation: fade-in-opacity .15s ease, zoom-fade-in-move .15s ease;
}
}
}
&.zoom-fade.backwards {
> .from {
transform: scale(1);
}
> .to {
transform: scale(0.95);
}
&.animating {
> .from {
animation: fade-in-backwards-opacity .1s ease, zoom-fade-in-backwards-move .15s ease;
}
> .to {
animation: fade-out-backwards-opacity .15s ease, zoom-fade-out-backwards-move .15s ease;
}
}
}
}
/*
* zoom-fade
*/
@keyframes zoom-fade-in-move {
0% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
@keyframes zoom-fade-in-backwards-move {
0% {
transform: scale(1);
}
100% {
transform: scale(1.1);
}
}
@keyframes zoom-fade-out-backwards-move {
0% {
transform: scale(0.95);
}
100% {
transform: scale(1);
}
}
/* .zoom-fade {
transition: .15s ease-in-out opacity, .15s ease-in-out transform;
transform: scale3d(1.1, 1.1, 1);
opacity: 0;
display: flex;
&.active {
transform: scale3d(1, 1, 1);
transform-origin: center;
opacity: 1;
}
} */
// *:not(input):not(textarea) {
// -webkit-user-select: none; /* disable selection/Copy of UIWebView */
// -webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */

Loading…
Cancel
Save