https://www.youtube.com/watch?v=PCLt3ZM5fsc
Want to get this as ready made template with just 1 CLICK INSTALL?
Code Snippet for 3D Curved Image Slider/Carousel:
<style> [class^='mdw-curved-slider'], [class*=' mdw-curved-slider']{ height: var(--min-height, 100vh); } [class^='mdw-curved-slider'] canvas, [class*=' mdw-curved-slider'] canvas{ position: absolute; top: 0; left: 0; } </style> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.160.0/three.min.js"></script> <script> if(!MDWNonce111){ var MDWNonce111 = true var selector = "[class^='mdw-curved-slider'], [class*=' mdw-curved-slider']", scene = [], renderer = [], options = [], time = [], camera = [], slideAmount = [], currentContainerHeight = [], previousContainerHeight = [], planes = [] addEventListener('DOMContentLoaded', function(){ function getWidth(gap){ return 1 + gap/100 } function getPlaneWidth(el, camera){ var vFov = camera.fov*Math.PI/180, height = 2*Math.tan(vFov/2)*camera.position.z, aspect = el.clientWidth/el.clientHeight, width = height*aspect return el.clientWidth/width } function init(e = 'none'){ Array.from(document.querySelectorAll(selector)).forEach(function(el, index){ if( e == 'none' ){ currentContainerHeight[index] = previousContainerHeight[index] = el.clientHeight }else{ currentContainerHeight[index] = el.clientHeight if( mobileHeightChage && currentContainerHeight[index] == previousContainerHeight[index] ) return } previousContainerHeight[index] = currentContainerHeight[index] var className = el.getAttribute('class'), classNameIndex = className.indexOf('mdw-curved-slider'), shortClass = className.substring(classNameIndex, className.indexOf(' ',classNameIndex)), values = shortClass.split('-') options[index] = { speed: 30, gap: 10, curve: 12, direction: -1 } values.forEach(function(value, i){ if(value=='speed' && values[i+1] && !isNaN(values[i+1])){ options[index].speed = values[i+1] } if(value=='gap' && values[i+1] && !isNaN(values[i+1])){ options[index].gap = values[i+1] } if(value=='curve' && values[i+1] && !isNaN(values[i+1])){ options[index].curve = values[i+1] } if(value=='reverse'){ options[index].direction = 1 } }) var images = [], allImages = [] time[index] = 0 Array.from(el.querySelectorAll('.elementor-widget-image-gallery .gallery-item')).forEach(function(el, index){ images.push(el.querySelector('img').getAttribute('src')) }) allImages = images slideAmount[index] = images.length scene[index] = new THREE.Scene() camera[index] = new THREE.PerspectiveCamera( 75, el.clientWidth / el.clientHeight, 0.1, 20 ) camera[index].position.z = 2 renderer[index] = new THREE.WebGLRenderer({ alpha: true, antialias: true }) renderer[index].setSize(el.clientWidth, el.clientHeight) renderer[index].setPixelRatio(window.devicePixelRatio) var previousCanvas = el.querySelector('canvas') if(previousCanvas) { el.removeChild(previousCanvas) } el.appendChild(renderer[index].domElement) var geometry = new THREE.PlaneGeometry(1, 1, 20, 20), planeSpace = getPlaneWidth(el,camera[index])*getWidth(options[index].gap), ratio = Math.ceil(el.clientWidth/(planeSpace*images.length)), totalImage = Math.ceil(el.clientWidth/planeSpace) + 1 + images.length, initialOffset = Math.ceil(el.clientWidth/(2*planeSpace)-0.5) for( var i = slideAmount[index]; i < totalImage; i++ ){ allImages.push(images[i%slideAmount[index]]) } planes[index] = [] allImages.forEach(function (image, i) { var loader = new THREE.TextureLoader() loader.load( image, function ( texture ) { var material = new THREE.ShaderMaterial({ uniforms: { tex: { value: texture }, curve: { value: options[index].curve } }, vertexShader: ` uniform float curve; varying vec2 vertexUV; void main(){ vertexUV = uv; vec3 newPosition = position; float distanceFromCenter = abs(modelMatrix*vec4(position, 1.0)).x; newPosition.y *= 1.0 + (curve/100.0)*pow(distanceFromCenter,2.0); gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0); } `, fragmentShader: ` uniform sampler2D tex; varying vec2 vertexUV; void main(){ gl_FragColor = texture2D(tex, vertexUV); } ` }) planes[index][i] = new THREE.Mesh( geometry, material ) planes[index][i].position.x = -1*options[index].direction*(i-initialOffset)*getWidth(options[index].gap) scene[index].add( planes[index][i] ) } ) }) }) } init() var currentWidth, previousWidth = window.innerWidth, mobileHeightChage = false function onResize(){ currentWidth = window.innerWidth mobileHeightChage = currentWidth < 768 && currentWidth == previousWidth init('resize') previousWidth = currentWidth } window.addEventListener('resize', function(){ onResize() setTimeout(onResize, 100) }) var previousTime = 0 function animate(currentTime){ var timePassed = currentTime - previousTime Array.from(document.querySelectorAll(selector)).forEach(function(el, index){ if(Math.abs(scene[index].position.x) >= getWidth(options[index].gap)*slideAmount[index]){ time[index] = 0 } time[index] += options[index].direction*timePassed*0.00001 scene[index].position.x = time[index]*options[index].speed renderer[index].render(scene[index], camera[index]) }) previousTime = currentTime requestAnimationFrame(animate) } requestAnimationFrame(animate) }) } </script>