SliderVC
/
script.js
308 строк · 8.3 Кб
1function lerp({ x, y }, { x: targetX, y: targetY }) {2const fraction = 0.2;3x += (targetX - x) * fraction;4y += (targetY - y) * fraction;5return { x, y };6}7class Slider {8constructor(el) {9const imgClass = (this.IMG_CLASS = "slider__images-item");10const textClass = (this.TEXT_CLASS = "slider__text-item");11const activeImgClass = (this.ACTIVE_IMG_CLASS = `${imgClass}--active`);12const activeTextClass = (this.ACTIVE_TEXT_CLASS = `${textClass}--active`);13
14this.el = el;15this.contentEl = document.getElementById("slider-content");16this.onMouseMove = this.onMouseMove.bind(this);17
18this.activeImg = el.getElementsByClassName(activeImgClass);19this.activeText = el.getElementsByClassName(activeTextClass);20this.images = el.getElementsByTagName("img");21
22document
23.getElementById("slider-dots")24.addEventListener("click", this.onDotClick.bind(this));25
26document
27.getElementById("left")28.addEventListener("click", this.prev.bind(this));29
30document
31.getElementById("right")32.addEventListener("click", this.next.bind(this));33
34window.addEventListener("resize", this.onResize.bind(this));35
36this.onResize();37
38this.length = this.images.length;39this.lastX = this.lastY = this.targetX = this.targetY = 0;40}41onResize() {42const htmlStyles = getComputedStyle(document.documentElement);43const mobileBreakpoint = htmlStyles.getPropertyValue("--mobile-bkp");44
45const isMobile = (this.isMobile = matchMedia(46`only screen and (max-width: ${mobileBreakpoint})`47).matches);48
49this.halfWidth = innerWidth / 2;50this.halfHeight = innerHeight / 2;51this.zDistance = htmlStyles.getPropertyValue("--z-distance");52
53if (!isMobile && !this.mouseWatched) {54this.mouseWatched = true;55this.el.addEventListener("mousemove", this.onMouseMove);56this.el.style.setProperty(57"--img-prev",58`url(${this.images[+this.activeImg[0].dataset.id - 1].src})`59);60this.contentEl.style.setProperty(61"transform",62`translateZ(${this.zDistance})`63);64} else if (isMobile && this.mouseWatched) {65this.mouseWatched = false;66this.el.removeEventListener("mousemove", this.onMouseMove);67this.contentEl.style.setProperty("transform", "none");68}69}70getMouseCoefficients({ pageX, pageY } = {}) {71const halfWidth = this.halfWidth;72const halfHeight = this.halfHeight;73const xCoeff = ((pageX || this.targetX) - halfWidth) / halfWidth;74const yCoeff = (halfHeight - (pageY || this.targetY)) / halfHeight;75
76return { xCoeff, yCoeff };77}78onMouseMove({ pageX, pageY }) {79this.targetX = pageX;80this.targetY = pageY;81
82if (!this.animationRunning) {83this.animationRunning = true;84this.runAnimation();85}86}87runAnimation() {88if (this.animationStopped) {89this.animationRunning = false;90return;91}92
93const maxX = 10;94const maxY = 10;95
96const newPos = lerp(97{98x: this.lastX,99y: this.lastY,100},101{102x: this.targetX,103y: this.targetY,104}105);106
107const { xCoeff, yCoeff } = this.getMouseCoefficients({108pageX: newPos.x,109pageY: newPos.y,110});111
112this.lastX = newPos.x;113this.lastY = newPos.y;114
115this.positionImage({ xCoeff, yCoeff });116
117this.contentEl.style.setProperty(118"transform",119`120translateZ(${this.zDistance})121rotateX(${maxY * yCoeff}deg)122rotateY(${maxX * xCoeff}deg)123`124);125
126if (this.reachedFinalPoint) {127this.animationRunning = false;128} else {129requestAnimationFrame(this.runAnimation.bind(this));130}131}132get reachedFinalPoint() {133const lastX = ~~this.lastX;134const lastY = ~~this.lastY;135const targetX = this.targetX;136const targetY = this.targetY;137
138return (139(lastX == targetX || lastX - 1 == targetX || lastX + 1 == targetX) &&140(lastY == targetY || lastY - 1 == targetY || lastY + 1 == targetY)141);142}143positionImage({ xCoeff, yCoeff }) {144const maxImgOffset = 1;145const currentImage = this.activeImg[0].children[0];146
147currentImage.style.setProperty(148"transform",149`150translateX(${maxImgOffset * -xCoeff}em)151translateY(${maxImgOffset * yCoeff}em)152`153);154}155onDotClick({ target }) {156if (this.inTransit) return;157
158const dot = target.closest(".slider__nav-dot");159
160if (!dot) return;161
162const nextId = dot.dataset.id;163const currentId = this.activeImg[0].dataset.id;164
165if (currentId == nextId) return;166
167this.startTransition(nextId);168
169clearTimeout(timer);170setTimeout(autoSlide, 5000);171}172transitionItem(nextId) {173function onImageTransitionEnd(e) {174e.stopPropagation();175
176nextImg.classList.remove(transitClass);177
178self.inTransit = false;179
180this.className = imgClass;181this.removeEventListener("transitionend", onImageTransitionEnd);182}183
184const self = this;185const el = this.el;186const currentImg = this.activeImg[0];187const currentId = currentImg.dataset.id;188const imgClass = this.IMG_CLASS;189const textClass = this.TEXT_CLASS;190const activeImgClass = this.ACTIVE_IMG_CLASS;191const activeTextClass = this.ACTIVE_TEXT_CLASS;192const subActiveClass = `${imgClass}--subactive`;193const transitClass = `${imgClass}--transit`;194const nextImg = el.querySelector(`.${imgClass}[data-id='${nextId}']`);195const nextText = el.querySelector(`.${textClass}[data-id='${nextId}']`);196
197let outClass = "";198let inClass = "";199
200this.animationStopped = true;201
202nextText.classList.add(activeTextClass);203
204el.style.setProperty("--from-left", nextId);205
206currentImg.classList.remove(activeImgClass);207currentImg.classList.add(subActiveClass);208
209if (currentId < nextId) {210outClass = `${imgClass}--next`;211inClass = `${imgClass}--prev`;212} else {213outClass = `${imgClass}--prev`;214inClass = `${imgClass}--next`;215}216
217nextImg.classList.add(outClass);218
219requestAnimationFrame(() => {220nextImg.classList.add(transitClass, activeImgClass);221nextImg.classList.remove(outClass);222
223this.animationStopped = false;224this.positionImage(this.getMouseCoefficients());225
226currentImg.classList.add(transitClass, inClass);227currentImg.addEventListener("transitionend", onImageTransitionEnd);228});229
230if (!this.isMobile) this.switchBackgroundImage(nextId);231}232startTransition(nextId) {233function onTextTransitionEnd(e) {234if (!e.pseudoElement) {235e.stopPropagation();236
237requestAnimationFrame(() => {238self.transitionItem(nextId);239});240
241this.removeEventListener("transitionend", onTextTransitionEnd);242}243}244
245if (this.inTransit) return;246
247const activeText = this.activeText[0];248const backwardsClass = `${this.TEXT_CLASS}--backwards`;249const self = this;250
251this.inTransit = true;252
253activeText.classList.add(backwardsClass);254activeText.classList.remove(this.ACTIVE_TEXT_CLASS);255activeText.addEventListener("transitionend", onTextTransitionEnd);256
257requestAnimationFrame(() => {258activeText.classList.remove(backwardsClass);259});260}261next() {262if (this.inTransit) return;263
264let nextId = +this.activeImg[0].dataset.id + 1;265
266if (nextId > this.length) nextId = 1;267
268this.startTransition(nextId);269}270prev() {271if (this.inTransit) return;272
273let nextId = +this.activeImg[0].dataset.id - 1;274
275if (nextId < 1) nextId = this.length;276
277this.startTransition(nextId);278}279switchBackgroundImage(nextId) {280function onBackgroundTransitionEnd(e) {281if (e.target === this) {282this.style.setProperty("--img-prev", imageUrl);283this.classList.remove(bgClass);284this.removeEventListener("transitionend", onBackgroundTransitionEnd);285}286}287
288const bgClass = "slider--bg-next";289const el = this.el;290const imageUrl = `url(${this.images[+nextId - 1].src})`;291
292el.style.setProperty("--img-next", imageUrl);293el.addEventListener("transitionend", onBackgroundTransitionEnd);294el.classList.add(bgClass);295}296}
297
298const sliderEl = document.getElementById("slider");299const slider = new Slider(sliderEl);300
301let timer = 0;302function autoSlide() {303requestAnimationFrame(() => {304slider.next();305});306timer = setTimeout(autoSlide, 5000);307}
308timer = setTimeout(autoSlide, 5000);309