diff --git a/__tests__/src/lib/CanvasGroupings.test.js b/__tests__/src/lib/CanvasGroupings.test.js
index 1f222d299a75fe337e4315ac703eca70461ba370..2732be7b629a0af002c1a7535e6a2cc1b775539a 100644
--- a/__tests__/src/lib/CanvasGroupings.test.js
+++ b/__tests__/src/lib/CanvasGroupings.test.js
@@ -35,6 +35,16 @@ describe('CanvasGroupings', () => {
         expect(subject.groupings()[1]).toEqual([1, 2]);
       });
     });
+    describe('scroll', () => {
+      let subject;
+      beforeEach(() => {
+        subject = new CanvasGroupings([0, 1, 2, 3], 'scroll');
+      });
+      it('creates an array of all the canvases', () => {
+        expect(subject.groupings().length).toEqual(1);
+        expect(subject.groupings()[0]).toEqual([0, 1, 2, 3]);
+      });
+    });
   });
   describe('getCanvases', () => {
     describe('single', () => {
@@ -58,5 +68,11 @@ describe('CanvasGroupings', () => {
         expect(subject.getCanvases(2)).toEqual([2]);
       });
     });
+    describe('scroll', () => {
+      it('selects by index', () => {
+        const subject = new CanvasGroupings([0, 1, 2, 3], 'scroll');
+        expect(subject.getCanvases(0)).toEqual([0, 1, 2, 3]);
+      });
+    });
   });
 });
diff --git a/__tests__/src/lib/CanvasWorld.test.js b/__tests__/src/lib/CanvasWorld.test.js
index f30d00ed0184f123f07f70494df83a98afae7768..d010df137176bd2cd6aaa26e7988842ad1f08179 100644
--- a/__tests__/src/lib/CanvasWorld.test.js
+++ b/__tests__/src/lib/CanvasWorld.test.js
@@ -29,6 +29,14 @@ describe('CanvasWorld', () => {
       expect(new CanvasWorld(canvasSubset, null, 'right-to-left').contentResourceToWorldCoordinates({ id: 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410/full/full/0/default.jpg' }))
         .toEqual([0, 0, 2848, 4288]);
     });
+    it('supports TTB orientations', () => {
+      expect(new CanvasWorld(canvasSubset, null, 'top-to-bottom').contentResourceToWorldCoordinates({ id: 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410/full/full/0/default.jpg' }))
+        .toEqual([0, 1936, 2848, 4288]);
+    });
+    it('supports BTT orientations', () => {
+      expect(new CanvasWorld(canvasSubset, null, 'bottom-to-top').contentResourceToWorldCoordinates({ id: 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410/full/full/0/default.jpg' }))
+        .toEqual([0, 0, 2848, 4288]);
+    });
     it('when placed by a fragment contains the offset', () => {
       const subject = new CanvasWorld(
         [Utils.parseManifest(fragmentFixture).getSequences()[0].getCanvases()[0]],
diff --git a/src/lib/CanvasGroupings.js b/src/lib/CanvasGroupings.js
index ab348f30740da53a7985fb45b9873373b3713a82..690d72a1ec35bdfae2722b1bc6ded86a7b9e2e05 100644
--- a/src/lib/CanvasGroupings.js
+++ b/src/lib/CanvasGroupings.js
@@ -29,6 +29,9 @@ export default class CanvasGroupings {
     if (this._groupings) { // eslint-disable-line no-underscore-dangle
       return this._groupings; // eslint-disable-line no-underscore-dangle
     }
+    if (this.viewType === 'continuous') {
+      return [this.canvases];
+    }
     if (this.viewType !== 'book') {
       return this.canvases.map(canvas => [canvas]);
     }
diff --git a/src/lib/CanvasWorld.js b/src/lib/CanvasWorld.js
index 535f9fac351fdf387eae6e03d0fd079f4df845a4..8b8356254a484f6f35f3ee3a117ba8097e65a6d4 100644
--- a/src/lib/CanvasWorld.js
+++ b/src/lib/CanvasWorld.js
@@ -13,6 +13,7 @@ export default class CanvasWorld {
     this.canvases = canvases.map(c => new MiradorCanvas(c));
     this.layers = layers;
     this.viewingDirection = viewingDirection;
+    this._canvasDimensions = null; // eslint-disable-line no-underscore-dangle
   }
 
   /** */
@@ -20,65 +21,113 @@ export default class CanvasWorld {
     return this.canvases.map(canvas => canvas.id);
   }
 
+  /** */
+  get canvasDimensions() {
+    if (this._canvasDimensions) { // eslint-disable-line no-underscore-dangle
+      return this._canvasDimensions; // eslint-disable-line no-underscore-dangle
+    }
+
+    const [dirX, dirY] = this.canvasDirection;
+    const scale = dirY === 0
+      ? Math.min(...this.canvases.map(c => c.getHeight()))
+      : Math.min(...this.canvases.map(c => c.getWidth()));
+    let incX = 0;
+    let incY = 0;
+
+    const canvasDims = this.canvases.reduce((acc, canvas) => {
+      let canvasHeight;
+      let canvasWidth;
+
+      if (dirY === 0) {
+        // constant height
+        canvasHeight = scale;
+        canvasWidth = Math.floor(scale * canvas.aspectRatio);
+      } else {
+        // constant width
+        canvasWidth = scale;
+        canvasHeight = Math.floor(scale * (1 / canvas.aspectRatio));
+      }
+      acc.push({
+        canvas,
+        height: canvasHeight,
+        width: canvasWidth,
+        x: incX,
+        y: incY,
+      });
+
+      incX += dirX * canvasWidth;
+      incY += dirY * canvasHeight;
+      return acc;
+    }, []);
+
+    const worldHeight = dirY === 0 ? scale : Math.abs(incY);
+    const worldWidth = dirX === 0 ? scale : Math.abs(incX);
+
+    this._canvasDimensions = canvasDims // eslint-disable-line no-underscore-dangle
+      .reduce((acc, dims) => {
+        acc.push({
+          ...dims,
+          x: dirX === -1 ? dims.x + worldWidth - dims.width : dims.x,
+          y: dirY === -1 ? dims.y + worldHeight - dims.height : dims.y,
+        });
+
+        return acc;
+      }, []);
+
+    return this._canvasDimensions; // eslint-disable-line no-underscore-dangle
+  }
+
   /**
    * contentResourceToWorldCoordinates - calculates the contentResource coordinates
    * respective to the world.
    */
   contentResourceToWorldCoordinates(contentResource) {
-    const wholeBounds = this.worldBounds();
     const miradorCanvasIndex = this.canvases.findIndex(c => (
       c.imageResources.find(r => r.id === contentResource.id)
     ));
     const canvas = this.canvases[miradorCanvasIndex];
-    const scaledWidth = Math.floor(wholeBounds[3] * canvas.aspectRatio);
-    let x = 0;
-    if (miradorCanvasIndex === this.secondCanvasIndex) {
-      x = wholeBounds[2] - scaledWidth;
-    }
+    const [x, y, w, h] = this.canvasToWorldCoordinates(canvas.id);
+
     const fragmentOffset = canvas.onFragment(contentResource.id);
     if (fragmentOffset) {
       return [
         x + fragmentOffset[0],
-        0 + fragmentOffset[1],
+        y + fragmentOffset[1],
         fragmentOffset[2],
         fragmentOffset[3],
       ];
     }
     return [
       x,
-      0,
-      scaledWidth,
-      wholeBounds[3],
+      y,
+      w,
+      h,
     ];
   }
 
   /** */
   canvasToWorldCoordinates(canvasId) {
-    const wholeBounds = this.worldBounds();
-    const miradorCanvasIndex = this.canvases.findIndex(c => (c.id === canvasId));
-    const { aspectRatio } = this.canvases[miradorCanvasIndex];
-    const scaledWidth = Math.floor(wholeBounds[3] * aspectRatio);
-    let x = 0;
-    if (miradorCanvasIndex === this.secondCanvasIndex) {
-      x = wholeBounds[2] - scaledWidth;
-    }
+    const canvasDimensions = this.canvasDimensions.find(c => c.canvas.id === canvasId);
+
     return [
-      x,
-      0,
-      scaledWidth,
-      wholeBounds[3],
+      canvasDimensions.x,
+      canvasDimensions.y,
+      canvasDimensions.width,
+      canvasDimensions.height,
     ];
   }
 
-  /**
-   * secondCanvasIndex - index of the second canvas used for determining which
-   * is first
-   */
-  get secondCanvasIndex() {
-    return this.viewingDirection === 'right-to-left' ? 0 : 1;
+  /** */
+  get canvasDirection() {
+    switch (this.viewingDirection) {
+      case 'left-to-right': return [1, 0];
+      case 'right-to-left': return [-1, 0];
+      case 'top-to-bottom': return [0, 1];
+      case 'bottom-to-top': return [0, -1];
+      default: return [0, 1];
+    }
   }
 
-
   /** Get the IIIF content resource for an image */
   contentResource(infoResponseId) {
     const miradorCanvas = this.canvases.find(c => c.imageServiceIds.some(id => (
@@ -149,26 +198,14 @@ export default class CanvasWorld {
    * lined up horizontally starting from left to right.
    */
   worldBounds() {
-    const heights = [];
-    const dimensions = [];
-    this.canvases.forEach((canvas) => {
-      heights.push(canvas.getHeight());
-      dimensions.push({
-        height: canvas.getHeight(),
-        width: canvas.getWidth(),
-      });
-    });
-    const minHeight = Math.min(...heights);
-    let scaledWidth = 0;
-    dimensions.forEach((dim) => {
-      const aspectRatio = dim.width / dim.height;
-      scaledWidth += Math.floor(minHeight * aspectRatio);
-    });
+    const worldWidth = Math.max(...this.canvasDimensions.map(c => c.x + c.width));
+    const worldHeight = Math.max(...this.canvasDimensions.map(c => c.y + c.height));
+
     return [
       0,
       0,
-      scaledWidth,
-      minHeight,
+      worldWidth,
+      worldHeight,
     ];
   }
 }
diff --git a/src/state/selectors/windows.js b/src/state/selectors/windows.js
index 4ffbe1422d892edc4cf24a0937c86548f9c5d5cf..245fb617c0463b068d1e55715daf668419434bfd 100644
--- a/src/state/selectors/windows.js
+++ b/src/state/selectors/windows.js
@@ -88,6 +88,7 @@ export const getWindowViewType = createSelector(
   ],
   (window, manifestViewingHint, manifestBehaviors, defaultView) => {
     const lookup = {
+      continuous: 'continuous',
       individuals: 'single',
       paged: 'book',
     };