diff --git a/package-lock.json b/package-lock.json
index d000414..c915aae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10181,6 +10181,14 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
     },
+    "react-page-visibility": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/react-page-visibility/-/react-page-visibility-6.3.0.tgz",
+      "integrity": "sha512-0sZH/i3D+TvVk22PV/xobf6mCBGkr64UXFEPQGMndP21E5d9IX43yCeVHGKIoLuXh8tR4/oO4WYk22jwCr7Asg==",
+      "requires": {
+        "prop-types": "^15.7.2"
+      }
+    },
     "react-scripts": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.0.1.tgz",
diff --git a/package.json b/package.json
index fd221fa..cad118d 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
     "react-device-detect": "^1.7.5",
     "react-dom": "^16.8.6",
     "react-hotkeys": "^2.0.0",
+    "react-page-visibility": "^6.3.0",
     "react-scripts": "3.0.1",
     "store": "^2.0.12"
   },
diff --git a/src/components/Visualizer.js b/src/components/Visualizer.js
index 8aeaf66..054a30e 100644
--- a/src/components/Visualizer.js
+++ b/src/components/Visualizer.js
@@ -1,7 +1,13 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import PageVisibility from 'react-page-visibility';
+
+const DELAY = 500;
+
+export default class Visualizer extends React.PureComponent {
+  rafId = null;
+  timerId = null;
 
-export default class Visualizer extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
@@ -10,7 +16,8 @@ export default class Visualizer extends React.Component {
         baseColour: 'rgb(10, 10, 35)',
         translucent: 'rgba(10, 10, 35, 0.6)',
         multiplier: 0.7529
-      }
+      },
+      isTabVisible: true,
     };
   }
 
@@ -19,10 +26,31 @@ export default class Visualizer extends React.Component {
   // of the audio context stuff AFTER the audio has been triggered.
   // We can't see it until
   // then anyway so it makes no difference to desktop.
-  componentWillReceiveProps(nextProps) {
-    if (nextProps.playing && !this.state.eq.context) {
-      this.initiateEQ();
+  componentDidUpdate(prevProps, prevState) {
+    if (prevProps.playing === this.props.playing && prevState.isTabVisible === this.state.isTabVisible) {
+      return
+    }
+
+    // If the player is playing and the tab is being active,
+    // draw the visualization
+    if (this.props.playing && this.state.isTabVisible) {
+      // Create a new audio context if there isn't one available
+      if (!this.state.eq.context) {
+        this.initiateEQ();
+      }
       this.createVisualizer();
+      this.startDrawing();
+    }
+    // If the player is not playing or the tab is running in the background,
+    // stop the animation
+    else {
+      // Workaround for componentWillUnmount to delay the clean up and achieve fadeout animation
+      setTimeout(() => {
+        // Note: Order matters. 
+        // Stop the drawing loop first (using this.rafId), then set the ID to null
+        this.stopDrawing();
+        this.reset();
+      }, DELAY);
     }
   }
 
@@ -47,7 +75,10 @@ export default class Visualizer extends React.Component {
     eq.bands = new Uint8Array(eq.analyser.frequencyBinCount - 32);
 
     this.setState({ eq });
-    this.updateEQBands();
+  }
+
+  reset = () => {
+    this.rafId = null;
   }
 
   /** *
@@ -56,11 +87,11 @@ export default class Visualizer extends React.Component {
    * visualizer is up to date.
    */
   updateEQBands() {
+    const newEQ = this.state.eq;
     // Populate the buffer with the audio source’s current data
-    this.state.eq.analyser.getByteFrequencyData(this.state.eq.bands);
+    newEQ.analyser.getByteFrequencyData(newEQ.bands);
 
-    // Can’t stop, won’t stop
-    requestAnimationFrame(() => this.updateEQBands());
+    this.setState({ eq: { ...newEQ } });
   }
 
   /** *
@@ -77,22 +108,41 @@ export default class Visualizer extends React.Component {
       width: this._canvas.width,
       barWidth: this._canvas.width / this.state.eq.bands.length
     };
+  }
+
+  startDrawing = () => {
+    if (!this.rafId) {
+      this.rafId = window.requestAnimationFrame(this.drawingLoop);
+    }
+  };
+
+  stopDrawing = () => {
+    window.cancelAnimationFrame(this.rafId);
+    clearTimeout(this.timerId);
+  };
 
+  drawingLoop = () => {
+    const haveWaveform = this.state.eq.bands.reduce((a, b) => a + b, 0) !== 0;
+
+    this.updateEQBands();
     this.drawVisualizer();
-  }
+
+    // Because timeupdate events are not triggered at browser speed,
+    // we use requestanimationframe for higher framerates
+    if (haveWaveform) {
+      this.rafId = window.requestAnimationFrame(this.drawingLoop);
+    }
+    // If there is no music or audio in the song, then reduce the FPS
+    else {
+      this.timerId = setTimeout(this.drawingLoop, 250);
+    }
+  };
 
   /** *
    * As a base visualizer, the equalizer bands are drawn using
    * canvas in the window directly above the song into.
    */
   drawVisualizer() {
-    if (this.state.eq.bands.reduce((a, b) => a + b, 0) !== 0)
-      requestAnimationFrame(() => this.drawVisualizer());
-    // Because timeupdate events are not triggered at browser speed,
-    // we use requestanimationframe for higher framerates
-    // eslint-disable-next-line no-inline-comments
-    // If there is no music or audio in the song, then reduce the FPS
-    else setTimeout(() => this.drawVisualizer(), 250);
     // Intial bar x coordinate
     let y,
       x = 0;
@@ -129,11 +179,17 @@ export default class Visualizer extends React.Component {
     this.visualizer.ctx.fill();
   }
 
+  handleVisibilityChange = isTabVisible => {
+    this.setState({ isTabVisible });
+  }
+
   render() {
     return (
-      <div id='visualizer'>
-        <canvas aria-label='visualizer' ref={a => (this._canvas = a)} />
-      </div>
+      <PageVisibility onChange={this.handleVisibilityChange}>
+        <div id='visualizer'>
+          <canvas aria-label='visualizer' ref={a => (this._canvas = a)} />
+        </div>
+      </PageVisibility>
     );
   }
 }