These are the mistakes that come up most often when setting up ease360°. Most are silent — no console error, just a viewer that looks wrong. Check here first before digging into the source.
First step when anything goes wrong: open the browser console. ease360° logs descriptive errors and warnings directly there — including what went wrong, why, and how to fix it. Many of the issues below produce a specific console message that will point you straight to the problem.
Open the browser console (F12 → Console) and look for a message
starting with [ease360]. ease360° logs a descriptive error for the
most common failure modes — including CORS blocks, missing assets, and dimension
detection failures. The message will tell you exactly what failed and how to fix it.
Common console messages and what they mean:
# Probe image loaded but dimensions blocked — CORS issue
[ease360] Could not read frame dimensions — likely a CORS issue.
The probe image loaded but naturalWidth/naturalHeight returned 0.
Fix: Ensure your CDN sends an Access-Control-Allow-Origin header.
Alternatively, provide sourceWidth and sourceHeight explicitly to skip the probe.
# First frame didn't load at all — URL wrong or asset missing
[ease360] Probe image failed to load: https://res.cloudinary.com/...
Check the URL is correct and the asset exists.
# Deprecation warnings — old API names still work but should be updated
[ease360] The "width" setting is deprecated as of v1.0.1. Please rename it to "sourceWidth".
[ease360] The "dragAxis" setting is deprecated as of v1.0.3. Please rename it to "dragDirection".
If the console is clean and the viewer still doesn't render, check that
ease360.css is included — without it the canvas cursor and
opacity transitions won't apply. Also confirm the container element exists
in the DOM before the script runs.
cover or cover-center modes. Or the viewer doesn't initialize at all and the console shows a CORS or probe error.
sourceWidth and sourceHeight tell ease360° the pixel
dimensions of the image that actually arrives in the browser — not the master
file. ease360° uses these for all internal canvas math: scaling, aspect ratio,
cover positioning, and crop offsets.
These are optional — when omitted, ease360° probes the first frame automatically. But there are two compelling reasons to provide them explicitly:
Speed. The probe adds an async step before init — typically
50–200ms depending on CDN and connection speed. Providing
sourceWidth and sourceHeight skips the probe entirely
and ease360° initializes synchronously. For a viewer above the fold this matters.
CORS. The probe image loads with crossOrigin: 'anonymous'
to read pixel dimensions. If your server doesn't send an
Access-Control-Allow-Origin header, naturalWidth
returns 0 and the viewer won't start. Providing explicit dimensions skips the
probe entirely — CORS headers become irrelevant for initialization.
// ── Omitting sourceWidth/sourceHeight (probe runs automatically) ───────────
// Convenient — ease360° probes frames[0] and reads naturalWidth/naturalHeight.
// Small async delay before init. Requires CORS headers on your CDN.
const myEase360 = ease360('#myEase360', {
frames: makeFrames(540),
backgroundSize: 'cover'
// sourceWidth and sourceHeight omitted — ease360° probes automatically
});
// ── Providing sourceWidth/sourceHeight (probe skipped) ─────────────────────
// Synchronous init — no probe, no delay, no CORS dependency.
// Use when you know the delivered size and want the fastest possible start.
const myEase360 = ease360('#myEase360', {
frames: makeFrames(540),
sourceWidth: 540, // matches w_540 in the Cloudinary URL — not the master
sourceHeight: 356, // Cloudinary maintains aspect ratio at w_540
backgroundSize: 'cover'
});
The value must match the delivered size — what the CDN sends
at the requested transform — not the master. If you request w_540,
set sourceWidth: 540. Setting it to the master's 1080px while
delivering 540px will produce incorrect canvas math.
Each breakpoint object's sourceWidth and sourceHeight must match
the intrinsic pixel dimensions of that breakpoint's source images.
Different breakpoints can use different source sizes — but each entry must be
internally consistent.
// ✗ Wrong — orange teapot source is 1024×1024 but told it's 540×540
{ breakpoint: 1024, frames: orangeTeapot, sourceWidth: 540, sourceHeight: 540, flex: { w: true } }
// ✓ Correct — width/height match the actual source dimensions
{ breakpoint: 1024, frames: orangeTeapot, sourceWidth: 1024, sourceHeight: 1024, flex: { w: true } }
ease360° renders frames in the exact order of the array. A mismatch between your naming convention (zero-based vs one-based) and your array builder produces a broken seam or a missing frame.
// Zero-based: teapot_0.jpg → teapot_35.jpg
const frames = Array.from({ length: 36 }, (_, i) => `teapot_${i}.jpg`);
// → teapot_0.jpg, teapot_1.jpg, ..., teapot_35.jpg ✓
// One-based: teapot_1.jpg → teapot_36.jpg
const frames = Array.from({ length: 36 }, (_, i) => `teapot_${i + 1}.jpg`);
// → teapot_1.jpg, teapot_2.jpg, ..., teapot_36.jpg ✓
// ✗ Common mistake — one-based files, zero-based builder:
// Requests teapot_0.jpg (404) and misses teapot_36.jpg
When preloadSmart: true, ease360° loads every other frame first —
50% of the frames. myEase360.progress reports against the full
frame count, so it reaches 0.5 (50%) when the smart load is done.
Without the multiplier your loader reads 0–50% then jumps.
// ✗ Wrong — progress only ever shows 0–50%
progressUpdate: () => {
const pct = Math.floor(myEase360.progress * 100);
// pct reaches 50, not 100, when smart load completes
}
// ✓ Correct — only multiply during the smart load phase
progressUpdate: () => {
const inSmartPhase = myEase360.preloadSmart &&
myEase360.totalLoaded <= myEase360.frames.length / 2;
const multiplier = inSmartPhase ? 2 : 1;
const pct = Math.floor(myEase360.progress * 100) * multiplier;
// pct correctly reaches 100 at smart load complete, never exceeds it
}
This was a bug fixed in v1.0.4. Prior to that, cover-center calculated
its own vertical position and ignored backgroundOffsetY entirely.
Update to v1.0.4 or later — offsets are now applied as post-calculations
on top of the centering math.
With cover-center, positive backgroundOffsetY shifts the
crop up and negative shifts it down — relative to
where center naturally lands, not from the top-left origin.
// Requires ease360° v1.0.4+
{ breakpoint: 1024, frames: orangeTeapot, sourceWidth: 1024, sourceHeight: 1024,
backgroundSize: 'cover-center',
backgroundOffsetY: 50, // shifts crop up 50px relative to center
flex: { w: true } }
This happens when a variable declared with const or let
in a script file conflicts with the same name declared elsewhere in the global scope —
often a second <script> tag, an inline script, or a copy-paste
that duplicated a block.
The error references a line number in your JS file. Check for duplicate
const declarations of the same name. If you need the same variable
in multiple scripts, declare it once and share it, or wrap each script in an IIFE
to give it its own scope.
// ✗ Two scripts both declare const colorLoaded → error
// script-a.js: const colorLoaded = { green: false };
// script-b.js: const colorLoaded = { green: false }; // ✗ already declared
// ✓ Wrap in an IIFE to scope each script independently
((() => {
const colorLoaded = { green: false };
// ... rest of script
})();
The setting was renamed twice across versions. Use dragDirection
(v1.0.0+). The legacy names dragAxis and
touchdirection (v0.x) still work with a console deprecation warning
but will be removed in a future major version.
// ✓ Current (v1.0.3+)
ease360('#viewer', { dragDirection: 'left-right' });
// Still works but deprecated
ease360('#viewer', { dragAxis: 'left-right' }); // v1.0.2 name
ease360('#viewer', { touchdirection: 'left-right' }); // v0.x name
The method is changeFrames() — plural, with an s.
Calling changeFrame() without the s fails silently
because it's not defined on the instance. JavaScript won't throw an error,
it just does nothing. This is one of the harder bugs to spot because the
rest of the UI — swatch highlight, loader state — may still update correctly
while the viewer itself doesn't change.
// ✗ Silent failure — changeFrame is not a method
myEase360.changeFrame(variantFrames.blue);
// ✓ Correct — note the s
myEase360.changeFrames(variantFrames.blue);
// ✓ Responsive variant — also plural
myEase360.changeFramesResponsive([frames1920, frames1440, frames1024]);