<div id="" class=" spinner -ar-7x5 ">
<ol class="spinner__images">
<li class="spinner__image -active">
<div class=" media ">
<img class="media__item" src="/demo/teaser/teaser1.jpg" alt=>
</div>
</li>
<li class="spinner__image ">
<div class=" media ">
<img class="media__item" src="/demo/teaser/teaser2.jpg" alt=>
</div>
</li>
<li class="spinner__image ">
<div class=" media ">
<img class="media__item" src="/demo/teaser/teaser3.jpg" alt=>
</div>
</li>
</ol>
<div class="spinner__icon" style="display: nonee;"></div>
</div>
<div id="" class="{{mixes}} spinner -ar-7x5 {{modifiers}}">{{!-- cm-spinner__canvas --}}
<ol class="spinner__images"> {{!-- cm-spinner__images cm-details__media-box --}}
{{#each images}}
<li class="spinner__image {{modifiers}}"> {{!-- cm-spinner__image --}}
{{render '@atoms-media--image' image}}
</li>
{{/each}}
</ol>
<div class="spinner__icon" style="display: nonee;"></div>{{!-- cm-spinner__icon --}}
</div>
{
"images": [
{
"modifiers": "-active",
"image": {
"src": "/demo/teaser/teaser1.jpg"
}
},
{
"image": {
"src": "/demo/teaser/teaser2.jpg"
}
},
{
"image": {
"src": "/demo/teaser/teaser3.jpg"
}
}
]
}
.spinner {
position: relative;
overflow: hidden;
cursor: ew-resize;
touch-action: none;
&__images {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
list-style: none;
}
&__image {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
width: 100%;
height: 100%;
visibility: hidden;
&.-active {
visibility: visible;
}
}
&__icon {
position: absolute;
right: 50%;
bottom: 50%;
width: 9rem;
max-width: 75%;
height: 9rem;
max-height: 75%;
transform: translate(50%, 50%);
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj4NCiAgPHN0eWxlPg0KICAgIC5zdDAgew0KICAgICAgZmlsbDogcmdiYSgwLCAwLCAwLCAwLjUpOw0KICAgICAgc3Ryb2tlOiAjRkZGRkZGOw0KICAgICAgc3Ryb2tlLXdpZHRoOiAzMDsNCiAgICAgIHN0cm9rZS1taXRlcmxpbWl0OiAxMDsNCiAgICB9DQogICAgLnN0MXsNCiAgICAgIGZpbGw6ICNGRkZGRkY7DQogICAgfQ0KICA8L3N0eWxlPg0KICA8Y2lyY2xlIGNsYXNzPSJzdDAiIGN4PSIyNTYiIGN5PSIyNTYiIHI9IjIzMSIvPg0KICA8cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTI1LjQgMjQ4LjlIMTM4YzYgMCAxMC40LTEuNSAxMy4zLTQuNSAyLjktMyA0LjMtNyA0LjMtMTEuOSAwLTQuOC0xLjQtOC41LTQuMy0xMS4yLTIuOS0yLjctNi44LTQtMTEuOC00LTQuNSAwLTguMyAxLjItMTEuMyAzLjdzLTQuNiA1LjctNC42IDkuN0gxMDBjMC02LjIgMS43LTExLjggNS0xNi43IDMuMy00LjkgOC04LjggMTQtMTEuNSA2LTIuOCAxMi42LTQuMiAxOS45LTQuMiAxMi42IDAgMjIuNCAzIDI5LjUgOSA3LjEgNiAxMC43IDE0LjMgMTAuNyAyNC44IDAgNS40LTEuNyAxMC40LTUgMTVzLTcuNyA4LjEtMTMgMTAuNWM2LjcgMi40IDExLjcgNiAxNSAxMC44IDMuMyA0LjggNC45IDEwLjQgNC45IDE3IDAgMTAuNS0zLjggMTktMTEuNSAyNS40LTcuNyA2LjQtMTcuOSA5LjUtMzAuNSA5LjUtMTEuOSAwLTIxLjUtMy4xLTI5LjEtOS40LTcuNS02LjMtMTEuMy0xNC41LTExLjMtMjQuOGgyMy42YzAgNC41IDEuNyA4LjEgNSAxMC45IDMuMyAyLjggNy41IDQuMiAxMi40IDQuMiA1LjYgMCAxMC0xLjUgMTMuMi00LjQgMy4yLTMgNC44LTYuOSA0LjgtMTEuOCAwLTExLjktNi41LTE3LjgtMTkuNi0xNy44aC0xMi41di0xOC4zek0yNjEuMSAxOTguOHYxOS40aC0yLjNjLTEwLjcuMi0xOS4yIDIuOS0yNS43IDguM3MtMTAuNCAxMi45LTExLjcgMjIuNGM2LjMtNi40IDE0LjMtOS42IDIzLjktOS42IDEwLjMgMCAxOC41IDMuNyAyNC42IDExLjEgNi4xIDcuNCA5LjEgMTcuMSA5LjEgMjkuMiAwIDcuNy0xLjcgMTQuNy01IDIxLTMuMyA2LjMtOC4xIDExLjEtMTQuMiAxNC42cy0xMyA1LjItMjAuOCA1LjJjLTEyLjUgMC0yMi42LTQuMy0zMC4zLTEzLTcuNy04LjctMTEuNS0yMC4zLTExLjUtMzQuOFYyNjRjMC0xMi45IDIuNC0yNC4zIDcuMy0zNC4xIDQuOS05LjkgMTEuOS0xNy41IDIxLTIyLjkgOS4xLTUuNCAxOS43LTguMSAzMS43LTguMmgzLjl6bS0yMyA1OS41Yy0zLjggMC03LjMgMS0xMC40IDMtMy4xIDItNS40IDQuNi02LjggNy45djcuMmMwIDcuOSAxLjUgMTQgNC42IDE4LjQgMy4xIDQuNCA3LjQgNi42IDEzIDYuNiA1LjEgMCA5LjEtMiAxMi4zLTYgMy4xLTQgNC43LTkuMiA0LjctMTUuNSAwLTYuNS0xLjYtMTEuNy00LjctMTUuNi0zLjEtNC03LjMtNi0xMi43LTZ6TTM3Mi45IDI2OS42YzAgMTYuNC0zLjQgMjktMTAuMiAzNy43LTYuOCA4LjctMTYuNyAxMy0yOS44IDEzLTEyLjkgMC0yMi44LTQuMy0yOS43LTEyLjgtNi44LTguNS0xMC40LTIwLjgtMTAuNS0zNi43VjI0OWMwLTE2LjYgMy40LTI5LjIgMTAuMy0zNy44IDYuOS04LjYgMTYuOC0xMi45IDI5LjctMTIuOXMyMi44IDQuMyAyOS43IDEyLjhjNi44IDguNSAxMC40IDIwLjcgMTAuNSAzNi43djIxLjh6bS0yMy42LTI0YzAtOS44LTEuMy0xNy00LTIxLjVzLTYuOS02LjctMTIuNi02LjdjLTUuNSAwLTkuNiAyLjEtMTIuMyA2LjQtMi42IDQuMy00IDExLTQuMiAyMHYyOC45YzAgOS43IDEuMyAxNi45IDQgMjEuNiAyLjYgNC43IDYuOSA3LjEgMTIuNyA3LjEgNS44IDAgOS45LTIuMyAxMi41LTYuOCAyLjYtNC41IDMuOS0xMS40IDQtMjAuN3YtMjguM3pNMzkxIDIyMC44YzAtNi4yIDIuMi0xMS41IDYuNi0xNS45IDQuNC00LjQgOS42LTYuNiAxNS42LTYuNiA1LjkgMCAxMSAyLjIgMTUuNCA2LjYgNC4zIDQuNCA2LjUgOS43IDYuNSAxNS45IDAgNi4zLTIuMiAxMS41LTYuNSAxNS44LTQuMyA0LjMtOS40IDYuNC0xNS40IDYuNC01LjkgMC0xMS0yLjEtMTUuNS02LjQtNC41LTQuMi02LjctOS41LTYuNy0xNS44em0yMi4yIDEwLjJjMi45IDAgNS4zLTEgNy4xLTIuOSAxLjktMS45IDIuOC00LjMgMi44LTcuMiAwLTIuOC0uOS01LjMtMi44LTcuMy0xLjktMi4xLTQuMy0zLjEtNy4xLTMuMS0yLjkgMC01LjMgMS03LjMgMy4xcy0yLjkgNC41LTIuOSA3LjMgMSA1LjIgMyA3LjIgNC40IDIuOSA3LjIgMi45eiIvPg0KPC9zdmc+');
}
}
Requeres jQuery plugin: https://github.com/heartcode/360-Image-Slider
Example code snippet from coremedia9:
import $ from "jquery";
import * as logger from '@coremedia/js-logger';
function threeSixtySpinner(spinner) {
logger.log("[360]", spinner);
const SPINNER_IMAGE_SELECTOR = '.spinner__image';
const SPINNER_ACTIVE_IMAGE_CLASS = '-active';
const $document = $(document);
const $container = $(spinner);
const images = $container.find(SPINNER_IMAGE_SELECTOR);
const totalFrames = images.length;
// Defaults parameters:
let ready = false;
let dragging = false;
let moved = false;
let pointerStartPosX = 0;
let pointerEndPosX = 0;
let pointerDistance = 0;
// The starting time of the pointer tracking period
let monitorStartTime = 0;
// The pointer tracking time duration
const monitorInt = 40;
// A setInterval instance used to call the rendering function
let ticker = 0;
// Sets the speed of the image sliding animation
const speedMultiplier = 0.5;
// The current frame value of the image slider animation
let currentFrame = 0;
// Stores all the loaded image objects
const frames = [];
// The value of the end frame which the currentFrame will be tweened to during the sliding animation
let endFrame = 0;
logger.log("[360]",totalFrames,images);
if (totalFrames > 1) {
logger.log("[360] Initialize 360° Spinner with " + totalFrames + " frames");
// Inititialize frames (array of image objects)
images.each(function () {
frames.push($(this));
});
// Show first frame
frames[0].addClass(SPINNER_ACTIVE_IMAGE_CLASS).fadeIn();
//Start
render();
/* --- Events --- */
/**
* Adds the jQuery "mousedown" and "touchstart" event to the image slider wrapper.
*/
$container.on("mousedown touchstart", function (event) {
// leftclick
if (event.type == "mousedown" && event.which == 1) {
ready = true;
event.preventDefault();
event.stopPropagation(); //w3c
event.cancelBubble = true; //ie
// touch
} else if (event.type == "touchstart") {
ready = true;
// stop swipe on touch devices
event.stopPropagation();
}
// start
if (ready) {
// Stores the pointer x position as the starting position
pointerStartPosX = getPointerEvent(event).pageX;
logger.log("360° Spinner: start dragging by " + event.type);
// Remove Icon
hideIcon();
}
});
/**
* Add the jQuery "mousemove" and "touchmove" event handler, if started dragging inside the $container.
*/
$document.on("mousemove touchmove", function (event) {
if (ready) {
dragging = true;
event.preventDefault();
event.stopPropagation();
// Starts tracking the pointer X position changes
trackPointer(event);
}
});
/**
* Adds the jQuery "mouseup" event to the document for stopping, if started dragging inside the $container.
*/
$document.on("mouseup touchend", function (event) {
ready = false;
if (dragging) {
dragging = false;
event.preventDefault();
event.stopImmediatePropagation();
// remove close click stop to enable outside click again
/* window.setTimeout(function () {
$container.closest(".mfp-container").attr("data-stopclosing", "false");
}, 100);*/
logger.log("360° Spinner: stop dragging");
}
});
/**
* Adds the jQuery "keydown" event to the document. You can move the spinner by pressing left or right on the keyboard.
*/
$document.on("keydown", function (event) {
// only if spinner is visible
if ($container.css("visibility") !== 'hidden') {
const key = event.keyCode || event.which;
switch (key) {
// left key, go one frame to the left
case 37:
endFrame--;
moved = true;
break;
// right key, go one frame to the right
case 39:
endFrame++;
moved = true;
break;
}
if (moved) {
logger.log("360° Spinner: Moved with keyboard");
hideIcon();
render();
moved = false;
}
}
});
} else {
logger.log("[360] Error: Found 360° Spinner without frames, can't initialize it.");
}
/* --- internal functions --- */
/**
* Renders the image slider frame animations.
*/
function render() {
// The rendering function only runs if the "currentFrame" value hasn't reached the "endFrame" one
if (currentFrame !== endFrame) {
// Calculates the 10% of the distance between the "currentFrame" and the "endFrame".
// By adding only 10% we get a nice smooth and eased animation.
// If the distance is a positive number, we have to ceil the value, if its a negative number, we have to floor it to make sure
// that the "currentFrame" value surely reaches the "endFrame" value and the rendering doesn't end up in an infinite loop.
const frameEasing = endFrame < currentFrame ? Math.floor((endFrame - currentFrame) * 0.1) : Math.ceil((endFrame - currentFrame) * 0.1);
// Sets the current image to be hidden
hidePreviousFrame();
// Increments / decrements the "currentFrame" value by the 10% of the frame distance
currentFrame += frameEasing;
// Sets the current image to be visible
showCurrentFrame();
} else {
// If the rendering can stop, we stop and clear the ticker
window.clearInterval(ticker);
ticker = 0;
}
}
/**
* Creates a new setInterval and stores it in the "ticker"
* By default I set the FPS value to 60 which gives a nice and smooth rendering in newer browsers
* and relatively fast machines, but obviously it could be too high for an older architecture.
*/
function refresh() {
// If the ticker is not running already...
if (ticker === 0) {
// Let's create a new one!
ticker = self.setInterval(render, Math.round(1000 / 60));
}
}
/**
* Hides the previous frame
* It calls the "getNormalizedCurrentFrame" method to translate the "currentFrame" value to the "totalFrames" range
*/
function hidePreviousFrame() {
frames[getNormalizedCurrentFrame()].removeClass(SPINNER_ACTIVE_IMAGE_CLASS); //TODO Refactor to constant
}
/**
* Displays the current frame
* It calls the "getNormalizedCurrentFrame" method to translate the "currentFrame" value to the "totalFrames" range
*/
function showCurrentFrame() {
frames[getNormalizedCurrentFrame()].addClass(SPINNER_ACTIVE_IMAGE_CLASS);
}
/**
* Returns the "currentFrame" value translated to a value inside the range of 0 and "totalFrames"
*/
function getNormalizedCurrentFrame() {
let c = Math.ceil(currentFrame % totalFrames);
if (c < 0) {
c += (totalFrames - 1);
}
return c;
}
/**
* Returns a simple event regarding the original event is a mouse event or a touch event.
*/
function getPointerEvent(event) {
return event.originalEvent.targetTouches ? event.originalEvent.targetTouches[0] : event;
}
/**
* Tracks the pointer X position changes and calculates the "endFrame" for the image slider frame animation.
* This function only runs if the application is ready and the user really is dragging the pointer; this way we
* can avoid unnecessary calculations and CPU usage.
*/
function trackPointer(event) {
const userDragging = ready && dragging;
if (userDragging) {
// Stores the last x position of the pointer
pointerEndPosX = getPointerEvent(event).pageX;
// Checks if there is enough time past between this and the last time period of tracking
if (monitorStartTime < new Date().getTime() - monitorInt) {
// Calculates the distance between the pointer starting and ending position during the last tracking time period
pointerDistance = pointerEndPosX - pointerStartPosX;
// Calculates the endFrame using the distance between the pointer X starting and ending positions and the "speedMultiplier" values
if (pointerDistance > 0) {
endFrame = currentFrame + Math.ceil((totalFrames - 1) * speedMultiplier * (pointerDistance / $document.width()));
} else {
endFrame = currentFrame + Math.floor((totalFrames - 1) * speedMultiplier * (pointerDistance / $document.width()));
}
// Updates the image slider frame animation
refresh();
// restarts counting the pointer tracking period
monitorStartTime = new Date().getTime();
// Stores the the pointer X position as the starting position (because we started a new tracking period)
pointerStartPosX = getPointerEvent(event).pageX;
}
}
}
/**
* Fade out overlay icon, if displayed
*/
function hideIcon() {
const $icon = $container.find('.spinner__icon');
if ($icon.length) {
$icon.fadeOut();
}
}
}
export default function (domElementOrJQueryResult) {
if (domElementOrJQueryResult instanceof $) {
$.each(domElementOrJQueryResult, function (index, item) {
threeSixtySpinner(item);
});
} else {
threeSixtySpinner(domElementOrJQueryResult);
}
}