NOTICE:
This is a component page used internally by the SCP Wiki. It is intended to be used and included on other pages.
What this is
An image carousel component that, instead of showing just one image, cycles through many.
Usage
Wherever you want your Image Carousel, slap in this code instead of your standard image block.
Make sure that any images you want are already uploaded to your page. If you chose to ignore that sage advice and only upload your images after setting up the Carousel, then your images won't work until you Hard Refresh the page (Ctrl+Shift+R).
[[include :scp-wiki:component:carousel
| images=photograph.png,old-map.png,jumpscare.gif
| caption=A selection of images.
| interval=5
| wiki=scp-wiki
| page=SCP-173
| width=300px
| height=240px
| position=right
| no-caption=false
| background=white
| options=yes
]]
Using an image carousel presents certain accessibility concerns - the constant motion can be very distracting for some users. It is also not a great idea to force your reader to wait to see your article's images. This component is also a great way to irritate your reader by forcing interactivity onto them in an otherwise non-interactive context. If possible, I would recommend not using this component. Use a series of standard image blocks instead.
What each option is
Anything in italics is optional. Everything else you gotta have.
If you omit an optional option, then it will have its default value. If you omit a non-optional option, then don't expect the carousel to work properly.
These images will appear in the carousel in the order that you list them.
(Optional) A list of URLs for each image to link to, separated by commas. If provided, there must be the same number of URLs as there are images.
(Optional) The caption that goes underneath the carousel.
If you have no caption, make sure you set no-caption to true.
Default value: "{$caption}"
(Optional) If you set this to a non-zero number, then the carousel will automatically move onto the next image after this number of seconds.
If the user has clicked an arrow to manually change the image, or if they are currently hovering their mouse over the carousel, then the image will not rotate.
Default value: "0"
(Optional) The width of the widest image in the carousel.
Default value: "300px"
(Optional) The height of the tallest image in the carousel.
Default value depends on browser
(Optional) The horizontal position of the carousel on the page. "left", "right" or "center".
Default value: "right"
(Optional) Set this to "true" if you don't want a caption. Otherwise, leave it blank , or set it to "false", or get rid of it completely.
Default value: "false"
(Optional) The background colour behind the images.
Default value: "transparent"
(Optional) Do you want the detailed options (play/pause button and the row of little circles) to display? If not, set to anything but "yes".
Default value: "yes"
I want the carousel to spread across the whole page!
Set width to "100%" and position to "center".
I set width/height to the size of my biggest image but it's way too big!
Pick a smaller number, or make your images smaller.
Accessibility note
This component was not made with accessibility in mind, and violates several basic principles. For an accessible experience for your readers, do not use this component.
{$caption}
Codebase
⚠️ Looking for code inspiration? Please note: this was written in 2020 using technologies that were obsolete then and are even more so now. It's Bad Code.
HTML structure of the carousel
<htmlng-app="carousel"ng-controller="CarouselController"><head><scriptsrc="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script><scriptsrc="https://scp-wiki.wikidot.com/local--code/component%3Acarousel/2"></script><linkhref="https://d3g0gp89917ko0.cloudfront.net/v--edac79f846ba/common--theme/base/css/style.css"rel="stylesheet"><linkhref="https://scp-wiki.wdfiles.com/local--code/component%3Atheme/1"rel="stylesheet"><linkhref="https://scp-wiki.wdfiles.com/local--code/component%3Acarousel/4"rel="stylesheet"></head><body><divclass="wrapper"id="background"><divclass="carousel"><divclass="horsie"ng-repeat="image in images track by $index"ng-class="[index > $index ? 'past' : null, index === $index ? 'present' : null, index < $index ? 'future' : null]"><imgng-src="{{image}}"ng-if="!links[$index]"><ahref="{{links[$index]}}"target="_blank"ng-if="links[$index]"><imgng-src="{{image}}"></a></div></div><divclass="arrow decrementor"ng-class="index === 0 ? 'inactive' : 'active'"ng-click="increment(-1)"><divclass="image"></div></div><divclass="arrow incrementor"ng-class="index === images.length-1 ? 'inactive' : 'active'"ng-click="increment(1)"><divclass="image"></div></div><divclass="bubble-holder"ng-class="[options === 'yes' ? null : 'invisible']"><divclass="bubble"ng-repeat="image in images track by $index"ng-class="[index === $index ? 'present' : null]"ng-click="selectImage($index)"></div></div><divclass="control play"ng-click="control('play')"ng-class="[state === 'play' ? 'active' : null, options === 'yes' ? null : 'invisible']"></div><divclass="control pause"ng-click="control('pause')"ng-class="[state === 'pause' ? 'active' : null, options === 'yes' ? null : 'invisible']"></div></div></body></html>
JS to operate the carousel
functiongetQueryVariable(variable){varquery = document.location.href.match(/\?.*$/g)[0].substring(1); varvars = query.split("&"); for(vari = 0; i < vars.length; i++){// >varpair = vars[i].split("="); if(pair[0] === variable)returndecodeURIComponent(pair[1]); }returnfalse; }(function(){varcarousel = angular .module('carousel',[]) .controller('CarouselController',CarouselController); CarouselController.$inject = ['$scope','$timeout']; functionCarouselController($scope,$timeout){varwiki = getQueryVariable("wiki") || "scp-wiki"; varpage = getQueryVariable("page"); varstyle = getQueryVariable("style"); if(style !== "{$style}"){varstyleElement = document.createElement("style"); document.head.appendChild(styleElement); styleElement.appendChild(document.createTextNode(atob(style))); }varimages = getQueryVariable("images").split(","); $scope.images = images.map(image => {if(!/^https?:/.test(image)){// Make a full URL for the specified pagereturn `https://${wiki}.wdfiles.com/local--files/${page}/${image}`;}returnimage; })varlinks = getQueryVariable("links"); $scope.links = links !== "{$links}" ? links.split(",") : []; $scope.index = 0; $scope.increment = function(amount){if(amount > 0 && $scope.index < $scope.images.length-1){ $scope.index += amount; }if(amount < 0 && $scope.index > 0){ $scope.index += amount; } $scope.state = "pause"; }varinterval = Number(getQueryVariable("interval") || 0); if(interval === "{$interval}")interval = 0; $scope.state = "play"; if(interval === 0) $scope.state = "pause"; functionoscillate(){ $timeout(function(){if(!mouseover && $scope.state === "play"){if($scope.index < $scope.images.length-1){ $scope.index++; }else{ $scope.index = 0; }}if($scope.state === "play"){oscillate(); }}, interval*1000, true); }varmouseover = false; document.documentElement.onmouseover = function(){mouseover = true; }document.documentElement.onmouseout = function(){mouseover = false; }if($scope.state === "play"){oscillate(); }document.getElementById('background').style.background = getQueryVariable("background"); $scope.selectImage = function(index){ $scope.index = index; $scope.state = "pause"; } $scope.control = function(direction){switch(direction){case"play": $scope.state = "play"; oscillate(); break; case"pause": $scope.state = "pause"; break; }} $scope.options = getQueryVariable("options"); if($scope.options === "{$options}") $scope.options = "yes"; }})();
Styling for the caption box
.carousel-container{position:relative; z-index:1; float:right; margin:01em1em1em; }.carousel-container.left{float:left; }.carousel-container.center{float:none; clear:both; margin:0auto1emauto; }.carousel-containeriframe{position:relative; z-index:2; width:334px; border:none; }.carousel-container.carousel-caption{position:relative; background-color:#eee; border:solid1px#666; padding:2px0; font-size:80%; font-weight:bold; text-align:center; width:300px; margin-top: -3px; box-shadow:01px6px rgba(0,0,0,.25); z-index:3; max-width:675px; }#page-content.carousel-container.carousel-caption-wrapper{max-width:673px; }.carousel-container.carousel-caption-wrapper{padding:016px; }.carousel-container.carousel-captionp{margin:0; padding:010px; }.carousel-container.carousel-caption.true{display:none; }
Styling for the rest of the carousel
html{width: calc(100% - 32px); height: calc(100% - 4px); margin:0; padding:0; }body{width:100%; height:100%; margin:0; padding:2px16px; background:transparent; }.wrapper{position:relative; width:100%; height:100%; }.carousel{position:relative; width: calc(100% - 2px); height: calc(100% - 2px); overflow-x:hidden; border:1pxsolid#666; box-shadow:01px6px rgba(0,0,0,.25); box-sizing: content-box; }.horsie{position:absolute; height:100%; width:100%; top:0; left:0; transform: translate(0,0); transition: transform 0.3s ease-in-out; }.horsieimg{object-fit: contain; width:100%; height:100%; }.horsie.past{transform: translate(-100%,0); }.horsie.future{transform: translate(100%,0); }.arrow{height:30px; width:30px; border:1pxsolid#666; border-radius:50%; position:absolute; top:50%; background:#eee; box-shadow:01px6px rgba(0,0,0,.25); cursor:pointer; }.arrow.image{height:30px; width:30px;position:absolute; top:0; left:0; background-position:50%50%; background-size:80%80%; background-repeat:no-repeat; opacity:0.6; transition: opacity 0.1s ease-in-out; }.arrow.inactive{cursor:default; }.arrow.inactive.image{opacity:0; }.decrementor{left:0; transform: translate(-50%,-50%); }.decrementor.image{background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAAAdElEQVR4AezQJwKAMBQD0DCOjAfD9j1BXVUdR0OxkWzCJvHvD/x5aVxEDMZBWVfAIDB1JQwOY96IUSzGYjCaxdgMJuMwOQJ4c51jlnc0HgsCUs5pPbX82csozaQsFqVuSpksSjIpg0OJBdACqlorw7AEowAAblWUrl8sD5AAAAAASUVORK5CYII='); }.incrementor{right:0; transform: translate(50%,-50%); }.incrementor.image{background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAAAcUlEQVR4Ae3QpwJAUACF4WM+sU6xV9M0TdI8mmR389j+07878PfSHOjYnYAETTdtP5Sh4VAik8pZlMSkChYlM6mSRalLKWN2FqolVLNqGgsyOE/zOZ+9i5FZTMFgJBaT34gRWUzGYAABKYMZizvm75W1TreU8DMmtioAAAAASUVORK5CYII='); }.bubble-holder{display: flex; position:absolute; width: calc(100% - 60px); height:20%; justify-content:center; align-items: flex-end; flex-wrap: wrap; align-content: flex-end; bottom:0; margin:5px0; left:30px; }.bubble{border:2pxsolid#666; height:0; width:0; margin:5px; border-radius:50%; transition:all0.2s ease-in-out; background-color:white; }.bubble.present{height:2px; width:2px; background-color:white; margin:4px; }.bubble-holder:hover.bubble{height:6px; width:6px; margin:2px; cursor:pointer; }.bubble-holder:hover.bubble.present{height:12px; width:12px; margin: -1px; }.control{position:absolute; height:10px; width:10px; left:5px; bottom:5px; background-size: contain; opacity:0.3; cursor:pointer; }.control.active{opacity:1; cursor:default; }.control.play{background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAAAgElEQVR4Ae2UQRWAIBBEJwIRjGAEI9hEG0ATaaINtIE0kAbqnrh4/M/Tzr//B7A78nzF0wvKraiAiF5ODYzIWBQIkXFpZETGqo4R2bkmRmTs6hmRERUIURsKQNSGAhBlQlSYqyXisQ/i+6tmYiA3YkUqs7SZqJHCFFtS+Lf8PZ4HilOsPBFiYmoAAAAASUVORK5CYII='); }.control.pause{left:20px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAOUlEQVR4Ae3WoQ0AMAwDwY7ezdsFzKyg3EthBgdzJCl309X7opev3wMBAQEtAQEBAQEBAU09+ZL0AdrO+xGDE6h0AAAAAElFTkSuQmCC'); }.invisible{display:none; }