diff --git a/.storybook/config.js b/.storybook/config.js
index 1b697b307e6c..d42c60ffe5bf 100644
--- a/.storybook/config.js
+++ b/.storybook/config.js
@@ -72,6 +72,8 @@ addDecorator((storyFn /* , context */) => {
document.body.classList.add('keyboard-mode');
}
+ document.body.classList.add('page-is-visible');
+
return (
diff --git a/stylesheets/_global.scss b/stylesheets/_global.scss
index 9fb0c183c89c..8f518908c85b 100644
--- a/stylesheets/_global.scss
+++ b/stylesheets/_global.scss
@@ -269,12 +269,6 @@ $loading-height: 16px;
border-color: $color-ios-blue-tint $color-ios-blue-tint $color-gray-02
$color-gray-02 !important;
}
-
- @keyframes rotate {
- to {
- transform: rotate(360deg);
- }
- }
}
.x {
@@ -689,3 +683,9 @@ $loading-height: 16px;
.overflow-hidden {
overflow: hidden;
}
+
+@keyframes rotate {
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/stylesheets/_mixins.scss b/stylesheets/_mixins.scss
index 21375975e754..fbec957656c4 100644
--- a/stylesheets/_mixins.scss
+++ b/stylesheets/_mixins.scss
@@ -109,6 +109,14 @@
}
}
+// NOTE: As of this writing, this mixin only works in the main window, because this class
+// is only applied there.
+@mixin only-when-page-is-visible {
+ .page-is-visible & {
+ @content;
+ }
+}
+
// Search results loading
@mixin search-results-loading-pulsating-background {
diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index c579a4a26dad..ea81ab11a737 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -1131,21 +1131,10 @@
.module-message__metadata__status-icon--paused,
.module-message__metadata__status-icon--sending {
- animation: module-message__metadata__status-icon--spinning 4s linear infinite;
-
- @include light-theme {
- @include color-svg('../images/sending.svg', $color-white);
- }
- @include dark-theme {
- @include color-svg('../images/sending.svg', $color-white);
- }
-}
-
-@keyframes module-message__metadata__status-icon--spinning {
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
+ @include only-when-page-is-visible {
+ animation: rotate 4s linear infinite;
}
+ @include color-svg('../images/sending.svg', $color-white);
}
.module-message__metadata__status-icon--sent {
@@ -4129,11 +4118,15 @@ button.module-image__border-overlay:focus {
background-color: $color-gray-60;
}
- animation: typing-animation 1600ms ease infinite;
+ @include only-when-page-is-visible {
+ animation: typing-animation 1600ms ease infinite;
+ }
}
.module-left-pane .module-typing-animation__dot {
- animation-name: typing-animation-bare;
+ @include only-when-page-is-visible {
+ animation-name: typing-animation-bare;
+ }
}
.module-typing-animation__dot--light {
@@ -4649,7 +4642,7 @@ button.module-image__border-overlay:focus {
height: 100%;
width: 100%;
- animation: spinner-arc-animation 1000ms linear infinite;
+ animation: rotate 1000ms linear infinite;
@include light-theme {
@include color-svg('../images/spinner-56.svg', $color-gray-60);
@@ -4659,18 +4652,6 @@ button.module-image__border-overlay:focus {
}
}
-@keyframes spinner-arc-animation {
- 0% {
- transform: rotate(0deg);
- }
- 50% {
- transform: rotate(180deg);
- }
- 100% {
- transform: rotate(360deg);
- }
-}
-
// In these --small and --mini sizes, we're exploding our @color-svg mixin so we don't
// have to duplicate our background colors for the dark/ios/size matrix.
@@ -6489,8 +6470,9 @@ button.module-image__border-overlay:focus {
}
&--sending {
- animation: module-conversation-list__item--contact-or-conversation__contact__message__text__status-icon--spinning
- 4s linear infinite;
+ @include only-when-page-is-visible {
+ animation: rotate 4s linear infinite;
+ }
@include light-theme {
@include color-svg('../images/sending.svg', $color-gray-60);
}
@@ -6664,13 +6646,6 @@ button.module-image__border-overlay:focus {
}
}
-@keyframes module-conversation-list__item--contact-or-conversation__contact__message__text__status-icon--spinning {
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
-}
-
// Module: Left Pane
.module-left-pane {
diff --git a/stylesheets/components/MessageDetail.scss b/stylesheets/components/MessageDetail.scss
index e9e63c5626c9..c33e5e4feb52 100644
--- a/stylesheets/components/MessageDetail.scss
+++ b/stylesheets/components/MessageDetail.scss
@@ -121,19 +121,11 @@
&--Pending:after {
width: 12px;
- animation: module-message-detail__contact-group__header--Pending 4s linear
- infinite;
+ animation: rotate 4s linear infinite;
@include normal-icon('../images/sending.svg');
}
}
-@keyframes module-message-detail__contact-group__header--Pending {
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
-}
-
.module-message-detail__contact {
margin-bottom: 8px;
padding: 8px 0;
diff --git a/ts/components/App.tsx b/ts/components/App.tsx
index 87ea4e23796b..e9f7e9c4a602 100644
--- a/ts/components/App.tsx
+++ b/ts/components/App.tsx
@@ -1,3 +1,6 @@
+// Copyright 2021 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
import React, { useEffect } from 'react';
import classNames from 'classnames';
@@ -6,6 +9,7 @@ import { Inbox } from './Inbox';
import { Install } from './Install';
import { StandaloneRegistration } from './StandaloneRegistration';
import { ThemeType } from '../types/Util';
+import { usePageVisibility } from '../util/hooks';
export type PropsType = {
appView: AppViewType;
@@ -32,7 +36,7 @@ export const App = ({
contents = ;
}
- // This is here so that themes are properly applied to anything that is
+ // This are here so that themes are properly applied to anything that is
// created in a portal and exists outside of the container.
useEffect(() => {
document.body.classList.remove('light-theme');
@@ -46,6 +50,11 @@ export const App = ({
}
}, [theme]);
+ const isPageVisible = usePageVisibility();
+ useEffect(() => {
+ document.body.classList.toggle('page-is-visible', isPageVisible);
+ }, [isPageVisible]);
+
return (