3D✨ Premium

360 Viewer

A strip three times wider than its frame, painted with a long gradient from midnight blue to yellow and back to night, that you slide with a mouse, a finger or the arrow keys, and that loops endlessly: the script copies it end to end. A horizontal panorama you unroll — all the way around for a 360° panoramic photo, with no looking up or down.

JSDragPanorama

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. 3D has 50 effects, including 8 free. Explore the category →

3 usage examples

Sunset from the terrace — page of a rooftop restaurant — 360 Viewer example 1

① Sunset from the terrace — page of a rooftop restaurant

WhenAt the top of the booking page: the gradient is the sky above a line of rooftops, with times from 7 pm to 9:30 pm placed in the sky; you slide from the yellow of 7 pm to the red of 8:30 pm and the blue of 9:30 pm, under the title "Book the terrace at golden hour".
WhyThe gradient already in the code is an evening sky: sliding it means letting time pass, and the loop brings you back to the next day. Since the capture cannot drag, the scene slides the strip back and forth by itself.
SettingsRoot without background, 540 × 360 px frame (inline height changed to 360 px), square corners; 120 px SVG rooftop line and time markers placed INSIDE the strip (they turn with it and are repeated in the copy); title and button laid on top, outside the strip; scene only: 6 s automatic back-and-forth, stopped by the first real drag.
The panorama from the viewpoint table — website of a mountain tourist office — 360 Viewer example 2

② The panorama from the viewpoint table — website of a mountain tourist office

WhenPage of the viewpoint table: a panorama of a volcanic range, with the markers "Puy de Dôme · 1,465 m", "Lake Aydat", "Clermont-Ferrand" and "Sancy range". The hiker slides the view the way they would turn their head, and goes all the way around.
WhySliding an image wider than the frame recreates the gaze sweeping the horizon; since the strip loops, the turn has no end stop, just like on site. The code accepts any background image.
SettingsStrip painted with a clear sky and a four-layer SVG (distant range, hills, lake, town), whose left and right edges sit at the same height so the seam does not show; markers absolutely positioned, as a percentage of the strip; scene only: continuous rotation at 110 px/s.
The vineyard's seasons you scroll through — "Our year" page of a wine estate — 360 Viewer example 3

③ The vineyard's seasons you scroll through — "Our year" page of a wine estate

WhenThe estate's "Our year" page: the strip recolored from winter (gray) to spring (soft green), summer (gold) and harvest (purple), then winter again, with the labels "January · Pruning", "April · Bud break", "August · Veraison", "September · Harvest" and rows of vines at the bottom.
WhyA continuous year you browse with a finger: dragging makes the duration felt, where four thumbnails would cut time into pieces, and the loop says that after the harvest comes a new pruning.
SettingsStrip gradient replaced by nine steps #9aa3ad → #a9d18e → #e5b544 → #7b2d5b → #9aa3ad (same colors at both ends); rows drawn with repeated gradients inside the strip; scene only: 7 s automatic back-and-forth.

How it works

The root .fx-0042-root (#0a0a0f background, at least 300 px tall) holds the .xr-360-viewer frame (full width × 300 px, 8 px rounded corners, overflow: hidden) and the .xr-360-scene strip, placed at the top left and 300% wide: the frame shows a third of it. It is painted with an eight-step horizontal gradient, from #1a1a2e to blue #0f3460, purple #533483, red #e94560, peach #f38181 and yellow #fce38a, then #1a1a2e again: the strip ends on the exact color it starts with, which is what makes the loop invisible.

On startup, the script clones the strip, marks the copy aria-hidden="true" and glues it to its left. It keeps a single number, the total offset, and places the two strips at p and p − width, where p is the offset brought back between 0 and the strip's width: whatever distance has been dragged, the frame is always covered. Measured in Chromium 151, Firefox 153 and WebKit 26: after drags of 400 px to the right then 1,500 and 3,000 px to the left, not a single empty pixel column in the frame. The drag follows the pointer to the pixel (120 px of dragging = 120 px of strip), with no transition and no inertia; a full turn takes three frame widths.

Pointer events serve mouse, finger and pen alike, with pointer capture during the gesture; touch-action: pan-y lets the page scroll vertically when the finger moves up or down, and keeps the horizontal gesture for the panorama (measured with a finger in Chromium using real touch events). With the keyboard, the frame takes focus and each left or right arrow turns by a tenth of the frame's width. One instance starts per frame found under a .fx-0042-root root: two panoramas on one page each slide on their own (measured). It is not a sphere: you turn your head left and right, never up or down, and the image is shown flat, with no projection correction.

Accessibility

  • Keyboard: the frame is focusable (tabindex="0"), announced by its aria-label (a panorama to drag or turn with the arrows), and gets a 2 px yellow outline on focus. Left or right arrow: a tenth of the frame's width per press. Measured in Chromium, Firefox and WebKit.
  • Reduced motion: nothing moves on its own and no transition animates the strip: it only moves under the finger, the mouse or the key, at the user's pace. Rendering is therefore identical with or without prefers-reduced-motion (measured: 0 animations in both cases).
  • Strip content: the loop's copy is hidden from screen readers, so markers placed in the strip are read only once. A background image has no text alternative: if the panorama carries information (peaks, times, steps), also write it as text, in the strip or next to it, as the scenes above do.
  • Touch: touch-action: pan-y keeps the page's vertical scrolling; only a horizontal drag turns the panorama.

Browser compatibility

CSS (2D transforms, gradient) + 41 lines of JavaScript (strip cloning, pointer events with capture, keyboard, resize), no dependencies. Measured in Chromium 151, Firefox 153 and WebKit 26: loop with no empty band after 3,000 px of dragging, pixel-accurate drag, keyboard, two independent copies; touch measured in Chromium. Nothing is 3D: WebKit's rendering is representative here.

Chrome 55+✓ Full
Firefox 59+✓ Full
Safari 13+✓ Full
Edge 79+✓ Full
Mobile iOS✓ Not verified on a real device
Android Chrome✓ Full

Without JavaScript, the frame shows the first third of the strip, still, and nothing slides. Without pointer events (very old browsers), same thing: the panorama stays readable but frozen. If your image's left and right edges do not match, the loop still works, but the seam shows on every turn.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0042-root">
  <div class="xr-360-viewer" tabindex="0" role="group" aria-label="Panorama: drag, or use the left and right arrow keys, to look around" style="width:100%;height:300px;">
    <div class="xr-360-scene">
      <!-- your panorama: gradient, background image, absolutely positioned markers -->
    </div>
    <!-- the script adds a copy of .xr-360-scene here (aria-hidden) for the loop -->
  </div>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Panoramic image (CSS — background on .xr-360-scene) eight-step gradient, #1a1a2e at both ends Replace the gradient with url(your-panorama.jpg) center / 100% 100%. For a seamless loop, the image's right edge must continue its left edge: a 360° panoramic photo does. For a gradient, end on the starting color.
Strip length (CSS — width: 300%) 300% (one turn = three frame widths) Match it to your image's ratio: a 6,000 × 1,000 px panorama in a 300 px tall frame is 1,800 px long, i.e. 300% of a 600 px frame. The longer the strip, the more dragging a full turn takes.
Frame height (HTML — style="height:300px" on the frame) 300 px The style is written in the HTML: change it there. If you increase the height, keep the strip's length proportional so the image is not distorted.
Keyboard step (JS — viewer.offsetWidth / 10) a tenth of the frame's width / 20 gives finer steps; / 4 jumps a quarter of the frame, handy to go from one marker to the next.
Markers in the strip (HTML — position: absolute elements inside .xr-360-scene) none Place them as a percentage of the strip (left: 32%): they turn with the panorama and the loop's copy repeats them automatically. Do not give them an id: the copy would duplicate it.
Background and outline (CSS — .fx-0042-root, the frame's border-radius and :focus-visible) #0a0a0f, 8 px corners, yellow #fce38a outline Remove the root's background to set the frame on your page; match the focus outline color to your brand while keeping a clear contrast with the panorama.

FAQ

For the horizontal turn, yes: the strip loops endlessly and a 360° panoramic photo goes all the way around without a seam. But it is not a sphere: you cannot look up or down, and the image is shown flat, with no projection correction. For a spherical (equirectangular) photo you explore in every direction, you need a WebGL library, which this code does not use.
For the loop. A single strip always ends up showing its edge; with two copies glued end to end and an offset brought back to the strip's length, the frame is always covered, whatever distance has been dragged (measured: no empty column after 3,000 px of dragging). The copy is marked aria-hidden so screen readers do not read the markers twice; avoid id attributes inside the strip, they would be duplicated.
No: touch-action: pan-y leaves vertical gestures to the browser, so the page scrolls when the finger moves up or down; only a horizontal gesture turns the panorama. The script uses pointer events, shared by mouse, finger and pen (measured with a finger in Chromium using real touch events).