Слайдер
на three.js

Модификаторы
Модификация позволяет создать слайдер на three.js с искажением изображений
Описание
Инструкция по подключению
В Zero блоке создаём HTML блок и помещаем в него код ниже
Шаг 1

<div id="wrap-texture">
  <div id="canvas"></div>
  <div class="planes">
    <div class="draggable-container">
      <div class="slider-names-container">
        <div class="slider-names">
          <div class="slider__name"></div>
          <div class="slider__name"></div>
          <div class="slider__name"></div>
          <div class="slider__name"> </div>
          <div class="slider__name"></div>
        </div>
      </div>

      <div class="draggable-hidden"></div>

      <div class="draggable-visible">
        <div class="plane"><img data-sampler="planeTexture" class="texture" src="https://static.tildacdn.com/tild6137-3161-4663-b734-333031643731/pierre-chatel-innoce.jpg" crossorigin="anonymous" /></div>
        <div class="plane"><img data-sampler="planeTexture" class="texture" src="https://static.tildacdn.com/tild3335-6134-4232-b932-346436646530/pierre-chatel-innoce.jpg" crossorigin="anonymous" /></div>
        <div class="plane"><img data-sampler="planeTexture" class="texture" src="https://static.tildacdn.com/tild3662-3764-4437-a134-366166396435/pierre-chatel-innoce.jpg" crossorigin="anonymous" /></div>
        <div class="plane"><img data-sampler="planeTexture" class="texture" src="https://static.tildacdn.com/tild3433-6637-4637-a462-656232306231/pierre-chatel-innoce.jpg" crossorigin="anonymous" /></div>
        <div class="plane"><img data-sampler="planeTexture" class="texture" src="https://static.tildacdn.com/tild6438-3936-4838-a261-653336393538/marten-bjork-j0Da0mE.jpg" crossorigin="anonymous" /></div>
 
    </div>
  </div>
</div>
</div>
  <script src='https://www.curtainsjs.com/build/curtains.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/draggabilly/2.2.0/draggabilly.pkgd.min.js'></script>
Растягиваем блок со слайдером на высоту и ширину экрана. В grid container выставляем необходимую высоту слайдера, к примеру 400px
Шаг 2
Через блок IM01 загружаем свои изображения и копируем url пути до изображений
Шаг 3
В HTML блоке заменяем url пути до изображений на свои
Шаг 4
Cоздаём блок T123 и помещаем в него стили
Шаг 5
<style>

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

img {
  max-width: 100%;
  display: block;
}

#wrap-texture {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
}

#canvas {
  /* make the canvas wrapper fits the document */
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index:0;
    width: 100%;
  height: 100%;

}

#canvas:before {
  content: "";
  position: fixed;
  width: 100%;
  height: 100%;

}

.planes {
  display: flex;
  align-items: center;
  width: 100%;
  height: 100%;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
  position: absolute;
  cursor: -webkit-grab;
  cursor: grab;
  white-space: nowrap;
  overflow: hidden;
  left: 30px;
}

.draggable-container {
  position: absolute;
  height: 100%;
}



.draggable-visible {
  display: grid;
  grid-template-columns: repeat(5, minmax(700px, 1fr));
  height: 100%;
  grid-column-gap: 50px;
}

.draggable-hidden {
  z-index: 5;
  width: 100%; 
  height: 100%;
  position: absolute;
}

.draggable-hidden .item {
  height: 100%;
}

.grabbing {
  cursor: -webkit-grabbing;
  cursor: grabbing;
}

.plane {
  height: 100%;
}

.plane img {
  /* hide the img element */
  display: none;
  -o-object-fit: cover;
     object-fit: cover;
  width: 100%;
  height: 100%;
    position: fixed;
  
}

@media screen and (max-width: 480px) {
    .draggable-visible {
         grid-template-columns: repeat(5, minmax(350px, 1fr));
    }
}
</style>
Cоздаём еще один блок T123 и помещаем в него скрипт для работы слайдера
Шаг 6
<script>
    console.clear();
const webGLCurtain = new Curtains({ container: "canvas" });
const planeContainer = document.querySelector(".planes");
const planeElements = planeContainer.querySelectorAll(".plane");
const draggableHidden = planeContainer.querySelector(".draggable-hidden");
const draggableVisible = planeContainer.querySelector(".draggable-visible");
const sliderNamesContainer = planeContainer.querySelector(
  ".slider-names-container"
);
const sliderNames = planeContainer.querySelector(".slider-names");
const names = planeContainer.querySelectorAll(".slider__name");
const items = planeContainer.querySelectorAll(".plane");
const draggie = new Draggabilly(draggableHidden, { axis: "x" });

sliderNamesContainer.style.height =
  names[0].getBoundingClientRect().height + "px";

const width = innerWidth;

let mouse = {
  currentPosX: 0,
  previousPosX: 0,
  minPosX: 0,
  maxPosX: width - draggableHidden.getBoundingClientRect().width,
  isMouseDown: false
};

let sliderNamesProps = {
  containerHeight:
    sliderNamesContainer.getBoundingClientRect().height -
    sliderNames.getBoundingClientRect().height
};

let start = performance.now();
let velocity = 0;
let lastVelocity = 0;
let posNames = 0;
let lastPosNames = 0;
let amplitude = 0.1;
let lastMouse = 0;
let easing = 0.05;
let cancelAnimation = true;
let isOut = false;

const planes = [];

const MathUtils = {
  lerp: (a, b, n) => (1 - n) * a + n * b,
  map_range: (value, low1, high1, low2, high2) => {
    return low2 + ((high2 - low2) * (value - low1)) / (high1 - low1);
  }
};

// crear canvas
const createCanvas = () => {
  const shader = {
    vertex: `    
    #ifdef GL_ES
    precision mediump float;
    #endif
    
    #define PI 3.14159265359
    
    // those are the mandatory attributes that the lib sets
    attribute vec3 aVertexPosition;
    attribute vec2 aTextureCoord;

    // those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    uniform mat4 planeTextureMatrix;

    // if you want to pass your vertex and texture coords to the fragment shader
    varying vec3 vVertexPosition;
    varying vec2 vTextureCoord;

    varying float vDirection;

    uniform float uDirection;

    void main() {
        vec3 position = aVertexPosition;

        float x = sin((position.y * 0.5 - 0.5) * PI) * uDirection;

        position.x -= x;
        
        gl_Position = uPMatrix * uMVMatrix * vec4(position, 1.0);

        // set the varyings
        vTextureCoord = (planeTextureMatrix * vec4(aTextureCoord, 0., 1.)).xy;
        vVertexPosition = position;

        vDirection = uDirection;
    }`,
    fragment: `
    #ifdef GL_ES
    precision mediump float;
    #endif

    #define PI2 6.28318530718
    #define PI 3.14159265359
    #define S(a,b,n) smoothstep(a,b,n)
    
    // get our varyings
    varying vec3 vVertexPosition;
    varying vec2 vTextureCoord;

    // the uniform we declared inside our javascript
    uniform float uTime;

    // our texture sampler (default name, to use a different name please refer to the documentation)
    uniform sampler2D planeTexture;
    
    varying float vDirection;

    void main(){
        vec2 uv = vTextureCoord;

        float scale = -abs(vDirection) * 0.8;

        uv = (uv - 0.5) * scale + uv;

        float r = texture2D(planeTexture, vec2(uv.x - vDirection * 0.0, uv.y)).r;
        float g = texture2D(planeTexture, vec2(uv.x - vDirection * 0.0, uv.y)).g;
        float b = texture2D(planeTexture, vec2(uv.x - vDirection * 0.0, uv.y)).b;
        
        gl_FragColor = vec4(r, g, b, 1.0);  
    }
    `
  };

  const params = {
    vertexShader: shader.vertex, // our vertex shader ID
    fragmentShader: shader.fragment, // our framgent shader ID
    widthSegments: 40,
    heightSegments: 40, // we now have 40*40*6 = 9600 vertices !
    uniforms: {
      time: {
        name: "uTime",
        type: "1f",
        value: 0
      },
      direction: {
        name: "uDirection",
        type: "1f",
        value: 0
      }
    }
  };

  webGLCurtain.disableDrawing();

  // crear nuestros planos
  for (let i = 0; i < planeElements.length; i++) {
    const plane = webGLCurtain.addPlane(planeElements[i], params);

    planes.push(plane);
    startPlane(items[i], plane);
  }

  initEvents();
  requestAnimationFrame(onUpdate);
};

const startPlane = (planeElement, plane) => {
  plane.onLoading(() => {
    plane.mouseOver = false;

    planeElement.addEventListener("mouseenter", () => {
      plane.mouseOver = true;
      webGLCurtain.enableDrawing();
    });

    planeElement.addEventListener("mouseleave", () => {
      plane.mouseOver = false;
    });

    webGLCurtain.needRender();
  });
};

const onUpdate = () => {
  if (!cancelAnimation) {
    for (let i = 0, l = planes.length; i < l; i++) {
      planes[i].uniforms.direction.value = lastVelocity;
      planes[i].updatePosition();
    }

    const { lerp } = MathUtils;
    const now = performance.now();

    const velocity =
      ((mouse.currentPosX - lastMouse) / (now - start)) * amplitude;

    lastVelocity = lerp(lastVelocity, velocity, easing);

    start = now;
    lastMouse = mouse.currentPosX;

    mouse.previousPosX = lerp(mouse.previousPosX, mouse.currentPosX, easing);

    draggableVisible.style.transform = `translate3d(${mouse.previousPosX}px, 0, 0)`;

    posNames =
      (draggie.position.x / mouse.maxPosX) * sliderNamesProps.containerHeight;

    lastPosNames = lerp(lastPosNames, posNames, easing);

    sliderNames.style.transform = `translate3d(0,${lastPosNames}px,0)`;

    if (Math.round(mouse.previousPosX) === mouse.currentPosX) {
      webGLCurtain.disableDrawing();
      cancelAnimation = true;
    }
  }

  requestAnimationFrame(onUpdate);
};

// elemento arrastrar
const onDragMove = () => {
  cancelAnimation = false;
  webGLCurtain.enableDrawing();

  // Si esta en el limite se trasladara la mitad del ancho del viewport
  if (draggie.position.x > mouse.minPosX) {
    mouse.currentPosX = MathUtils.map_range(
      draggie.position.x,
      0,
      innerWidth,
      0,
      innerWidth / 3
    );
  } else if (draggie.position.x < mouse.maxPosX) {
    mouse.currentPosX = MathUtils.map_range(
      draggie.position.x,
      mouse.maxPosX,
      mouse.maxPosX - innerWidth,
      mouse.maxPosX,
      mouse.maxPosX - innerWidth / 3
    );
  } else {
    mouse.currentPosX = draggie.position.x;

    easing = 0.05;
  }

  amplitude = 0.1;
};

const onDragStart = (e) => {
  planeContainer.style.cursor = "grabbing"; // Aplica al cursor el icono de agarrando;
};

const onDragEnd = () => {
  planeContainer.style.cursor = "grab"; // Aplica al cursor el icono de agarrar

  // Si esta en el limite volvera a su posicion inicial o final
  if (draggie.position.x > mouse.minPosX) {
    mouse.currentPosX = 0;
    draggie.setPosition(mouse.currentPosX, draggie.position.y);
    amplitude = 0;
    easing = 0.07;
  } else if (draggie.position.x < mouse.maxPosX) {
    mouse.currentPosX = mouse.maxPosX;
    draggie.setPosition(mouse.currentPosX, draggie.position.y);
    amplitude = 0;
    easing = 0.07;
  } else {
    draggie.setPosition(mouse.currentPosX, draggie.position.y);
  }
};

// Al reescalar calcula denuevo la posicion maxima
const onResize = () => {
  sliderNamesContainer.style.height =
    names[0].getBoundingClientRect().height + "px";

  mouse.maxPosX =
    window.innerWidth - draggableHidden.getBoundingClientRect().width;

  sliderNamesProps = {
    containerHeight:
      sliderNamesContainer.getBoundingClientRect().height -
      sliderNames.getBoundingClientRect().height
  };
};

const initEvents = () => {
  draggie.on("dragMove", () => {
    if (isOut) return;
    onDragMove();
  });
  draggie.on("pointerDown", () => {
    isOut = false;
    onDragStart();
  });
  draggie.on("pointerUp", onDragEnd);

  planeContainer.addEventListener("mouseleave", () => {
    isOut = true;
    onDragEnd();
  });

  window.addEventListener("resize", onResize);
};

window.addEventListener("load", createCanvas);
</script>
Made on
Tilda