diff --git a/__tests__/src/selectors/viewer.test.js b/__tests__/src/selectors/viewer.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..ece25446b81a8393bf996b80cf51eb49e2d0e523
--- /dev/null
+++ b/__tests__/src/selectors/viewer.test.js
@@ -0,0 +1,38 @@
+import {
+  getCurrentCanvasWorld,
+} from '../../../src/state/selectors/viewer';
+
+describe('getCurrentCanvasWorld', () => {
+  it('returns a CanvasWorld', () => {
+    const windowId = 'id';
+    const state = {
+      manifests: {
+        a: {
+          json: {
+            '@context': 'http://iiif.io/api/presentation/3/context.json',
+            id: 'whatever',
+            items: [
+              { '@id': 'a1', type: 'Canvas' },
+              { '@id': 'a2', type: 'Canvas' },
+            ],
+            type: 'Manifest',
+            viewingDirection: 'sideways',
+          },
+        },
+      },
+      windows: {
+        [windowId]: {
+          manifestId: 'a',
+          visibleCanvases: [
+            'a1', 'a2',
+          ],
+        },
+      },
+    };
+
+    const actual = getCurrentCanvasWorld(state, { windowId });
+    expect(actual.canvases.length).toEqual(2);
+    expect(Object.keys(actual.layers).length).toEqual(2);
+    expect(actual.viewingDirection).toEqual('sideways');
+  });
+});
diff --git a/src/containers/AnnotationsOverlay.js b/src/containers/AnnotationsOverlay.js
index 43be6e15931dddb888d88d183ac1805caea27df5..8619925686bfcb71677d7d68e7d65ad71e4d89d6 100644
--- a/src/containers/AnnotationsOverlay.js
+++ b/src/containers/AnnotationsOverlay.js
@@ -4,18 +4,15 @@ import { withTranslation } from 'react-i18next';
 import { withPlugins } from '../extend/withPlugins';
 import { AnnotationsOverlay } from '../components/AnnotationsOverlay';
 import * as actions from '../state/actions';
-import CanvasWorld from '../lib/CanvasWorld';
 import {
   getWindow,
-  getSequenceViewingDirection,
-  getLayersForVisibleCanvases,
-  getVisibleCanvases,
   getSearchAnnotationsForWindow,
   getCompanionWindowsForContent,
   getTheme,
   getConfig,
   getPresentAnnotationsOnSelectedCanvases,
   getSelectedAnnotationId,
+  getCurrentCanvasWorld,
 } from '../state/selectors';
 
 /**
@@ -25,11 +22,7 @@ import {
  */
 const mapStateToProps = (state, { windowId }) => ({
   annotations: getPresentAnnotationsOnSelectedCanvases(state, { windowId }),
-  canvasWorld: new CanvasWorld(
-    getVisibleCanvases(state, { windowId }),
-    getLayersForVisibleCanvases(state, { windowId }),
-    getSequenceViewingDirection(state, { windowId }),
-  ),
+  canvasWorld: getCurrentCanvasWorld(state, { windowId }),
   drawAnnotations: getConfig(state).window.forceDrawAnnotations || getCompanionWindowsForContent(state, { content: 'annotations', windowId }).length > 0,
   drawSearchAnnotations: getConfig(state).window.forceDrawAnnotations || getCompanionWindowsForContent(state, { content: 'search', windowId }).length > 0,
   highlightAllAnnotations: getWindow(state, { windowId }).highlightAllAnnotations,
diff --git a/src/containers/OpenSeadragonViewer.js b/src/containers/OpenSeadragonViewer.js
index 253c10ee6b5a3b902181edca81af6b7b2740f302..bf5d3fef6b091af9709e994c6e6a49d2d206b1f3 100644
--- a/src/containers/OpenSeadragonViewer.js
+++ b/src/containers/OpenSeadragonViewer.js
@@ -6,18 +6,15 @@ import flatten from 'lodash/flatten';
 import { withPlugins } from '../extend/withPlugins';
 import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer';
 import * as actions from '../state/actions';
-import CanvasWorld from '../lib/CanvasWorld';
 import {
   getVisibleCanvasNonTiledResources,
   getCurrentCanvas,
   getCanvasLabel,
-  getSequenceViewingDirection,
-  getLayersForVisibleCanvases,
-  getVisibleCanvases,
   getViewer,
   getConfig,
   getCompanionWindowsForContent,
   selectInfoResponses,
+  getCurrentCanvasWorld,
 } from '../state/selectors';
 
 /**
@@ -26,12 +23,7 @@ import {
  * @private
  */
 const mapStateToProps = (state, { windowId }) => {
-  const canvasWorld = new CanvasWorld(
-    getVisibleCanvases(state, { windowId }),
-    getLayersForVisibleCanvases(state, { windowId }),
-    getSequenceViewingDirection(state, { windowId }),
-  );
-
+  const canvasWorld = getCurrentCanvasWorld(state, { windowId });
   const infoResponses = selectInfoResponses(state);
   const imageServiceIds = flatten(canvasWorld.canvases.map(c => c.imageServiceIds));
 
diff --git a/src/state/selectors/index.js b/src/state/selectors/index.js
index 866141957ffc7875f157663ae457c88428e38c5b..bad2660352779524c7324cf25d3f33f32ec1cef9 100644
--- a/src/state/selectors/index.js
+++ b/src/state/selectors/index.js
@@ -12,3 +12,4 @@ export * from './layers';
 export * from './sequences';
 export * from './auth';
 export * from './utils';
+export * from './viewer';
diff --git a/src/state/selectors/viewer.js b/src/state/selectors/viewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..27b5992553d5d5f2d739f35d2f863c90d46e99d7
--- /dev/null
+++ b/src/state/selectors/viewer.js
@@ -0,0 +1,14 @@
+import { createSelector } from 'reselect';
+import CanvasWorld from '../../lib/CanvasWorld';
+
+import { getVisibleCanvases } from './canvases';
+import { getLayersForVisibleCanvases } from './layers';
+import { getSequenceViewingDirection } from './sequences';
+
+/** Instantiate a manifesto instance */
+export const getCurrentCanvasWorld = createSelector(
+  getVisibleCanvases,
+  getLayersForVisibleCanvases,
+  getSequenceViewingDirection,
+  (canvases, layers, viewingDirection) => new CanvasWorld(canvases, layers, viewingDirection),
+);