SliderVC

Форк
0
/
script.js 
308 строк · 8.3 Кб
1
function lerp({ x, y }, { x: targetX, y: targetY }) {
2
  const fraction = 0.2;
3
  x += (targetX - x) * fraction;
4
  y += (targetY - y) * fraction;
5
  return { x, y };
6
} 
7
class Slider {
8
  constructor(el) {
9
    const imgClass = (this.IMG_CLASS = "slider__images-item");
10
    const textClass = (this.TEXT_CLASS = "slider__text-item");
11
    const activeImgClass = (this.ACTIVE_IMG_CLASS = `${imgClass}--active`);
12
    const activeTextClass = (this.ACTIVE_TEXT_CLASS = `${textClass}--active`);
13

14
    this.el = el;
15
    this.contentEl = document.getElementById("slider-content");
16
    this.onMouseMove = this.onMouseMove.bind(this);
17

18
    this.activeImg = el.getElementsByClassName(activeImgClass);
19
    this.activeText = el.getElementsByClassName(activeTextClass);
20
    this.images = el.getElementsByTagName("img");
21

22
    document
23
      .getElementById("slider-dots")
24
      .addEventListener("click", this.onDotClick.bind(this));
25

26
    document
27
      .getElementById("left")
28
      .addEventListener("click", this.prev.bind(this));
29

30
    document
31
      .getElementById("right")
32
      .addEventListener("click", this.next.bind(this));
33

34
    window.addEventListener("resize", this.onResize.bind(this));
35

36
    this.onResize();
37

38
    this.length = this.images.length;
39
    this.lastX = this.lastY = this.targetX = this.targetY = 0;
40
  }
41
  onResize() {
42
    const htmlStyles = getComputedStyle(document.documentElement);
43
    const mobileBreakpoint = htmlStyles.getPropertyValue("--mobile-bkp");
44

45
    const isMobile = (this.isMobile = matchMedia(
46
      `only screen and (max-width: ${mobileBreakpoint})`
47
    ).matches);
48

49
    this.halfWidth = innerWidth / 2;
50
    this.halfHeight = innerHeight / 2;
51
    this.zDistance = htmlStyles.getPropertyValue("--z-distance");
52

53
    if (!isMobile && !this.mouseWatched) {
54
      this.mouseWatched = true;
55
      this.el.addEventListener("mousemove", this.onMouseMove);
56
      this.el.style.setProperty(
57
        "--img-prev",
58
        `url(${this.images[+this.activeImg[0].dataset.id - 1].src})`
59
      );
60
      this.contentEl.style.setProperty(
61
        "transform",
62
        `translateZ(${this.zDistance})`
63
      );
64
    } else if (isMobile && this.mouseWatched) {
65
      this.mouseWatched = false;
66
      this.el.removeEventListener("mousemove", this.onMouseMove);
67
      this.contentEl.style.setProperty("transform", "none");
68
    }
69
  }
70
  getMouseCoefficients({ pageX, pageY } = {}) {
71
    const halfWidth = this.halfWidth;
72
    const halfHeight = this.halfHeight;
73
    const xCoeff = ((pageX || this.targetX) - halfWidth) / halfWidth;
74
    const yCoeff = (halfHeight - (pageY || this.targetY)) / halfHeight;
75

76
    return { xCoeff, yCoeff };
77
  }
78
  onMouseMove({ pageX, pageY }) {
79
    this.targetX = pageX;
80
    this.targetY = pageY;
81

82
    if (!this.animationRunning) {
83
      this.animationRunning = true;
84
      this.runAnimation();
85
    }
86
  }
87
  runAnimation() {
88
    if (this.animationStopped) {
89
      this.animationRunning = false;
90
      return;
91
    }
92

93
    const maxX = 10;
94
    const maxY = 10;
95

96
    const newPos = lerp(
97
      {
98
        x: this.lastX,
99
        y: this.lastY,
100
      },
101
      {
102
        x: this.targetX,
103
        y: this.targetY,
104
      }
105
    );
106

107
    const { xCoeff, yCoeff } = this.getMouseCoefficients({
108
      pageX: newPos.x,
109
      pageY: newPos.y,
110
    });
111

112
    this.lastX = newPos.x;
113
    this.lastY = newPos.y;
114

115
    this.positionImage({ xCoeff, yCoeff });
116

117
    this.contentEl.style.setProperty(
118
      "transform",
119
      `
120
        translateZ(${this.zDistance})
121
        rotateX(${maxY * yCoeff}deg)
122
        rotateY(${maxX * xCoeff}deg)
123
      `
124
    );
125

126
    if (this.reachedFinalPoint) {
127
      this.animationRunning = false;
128
    } else {
129
      requestAnimationFrame(this.runAnimation.bind(this));
130
    }
131
  }
132
  get reachedFinalPoint() {
133
    const lastX = ~~this.lastX;
134
    const lastY = ~~this.lastY;
135
    const targetX = this.targetX;
136
    const targetY = this.targetY;
137

138
    return (
139
      (lastX == targetX || lastX - 1 == targetX || lastX + 1 == targetX) &&
140
      (lastY == targetY || lastY - 1 == targetY || lastY + 1 == targetY)
141
    );
142
  }
143
  positionImage({ xCoeff, yCoeff }) {
144
    const maxImgOffset = 1;
145
    const currentImage = this.activeImg[0].children[0];
146

147
    currentImage.style.setProperty(
148
      "transform",
149
      `
150
        translateX(${maxImgOffset * -xCoeff}em)
151
        translateY(${maxImgOffset * yCoeff}em)
152
      `
153
    );
154
  }
155
  onDotClick({ target }) {
156
    if (this.inTransit) return;
157

158
    const dot = target.closest(".slider__nav-dot");
159

160
    if (!dot) return;
161

162
    const nextId = dot.dataset.id;
163
    const currentId = this.activeImg[0].dataset.id;
164

165
    if (currentId == nextId) return;
166

167
    this.startTransition(nextId);
168

169
    clearTimeout(timer);
170
    setTimeout(autoSlide, 5000);
171
  }
172
  transitionItem(nextId) {
173
    function onImageTransitionEnd(e) {
174
      e.stopPropagation();
175

176
      nextImg.classList.remove(transitClass);
177

178
      self.inTransit = false;
179

180
      this.className = imgClass;
181
      this.removeEventListener("transitionend", onImageTransitionEnd);
182
    }
183

184
    const self = this;
185
    const el = this.el;
186
    const currentImg = this.activeImg[0];
187
    const currentId = currentImg.dataset.id;
188
    const imgClass = this.IMG_CLASS;
189
    const textClass = this.TEXT_CLASS;
190
    const activeImgClass = this.ACTIVE_IMG_CLASS;
191
    const activeTextClass = this.ACTIVE_TEXT_CLASS;
192
    const subActiveClass = `${imgClass}--subactive`;
193
    const transitClass = `${imgClass}--transit`;
194
    const nextImg = el.querySelector(`.${imgClass}[data-id='${nextId}']`);
195
    const nextText = el.querySelector(`.${textClass}[data-id='${nextId}']`);
196

197
    let outClass = "";
198
    let inClass = "";
199

200
    this.animationStopped = true;
201

202
    nextText.classList.add(activeTextClass);
203

204
    el.style.setProperty("--from-left", nextId);
205

206
    currentImg.classList.remove(activeImgClass);
207
    currentImg.classList.add(subActiveClass);
208

209
    if (currentId < nextId) {
210
      outClass = `${imgClass}--next`;
211
      inClass = `${imgClass}--prev`;
212
    } else {
213
      outClass = `${imgClass}--prev`;
214
      inClass = `${imgClass}--next`;
215
    }
216

217
    nextImg.classList.add(outClass);
218

219
    requestAnimationFrame(() => {
220
      nextImg.classList.add(transitClass, activeImgClass);
221
      nextImg.classList.remove(outClass);
222

223
      this.animationStopped = false;
224
      this.positionImage(this.getMouseCoefficients());
225

226
      currentImg.classList.add(transitClass, inClass);
227
      currentImg.addEventListener("transitionend", onImageTransitionEnd);
228
    });
229

230
    if (!this.isMobile) this.switchBackgroundImage(nextId);
231
  }
232
  startTransition(nextId) {
233
    function onTextTransitionEnd(e) {
234
      if (!e.pseudoElement) {
235
        e.stopPropagation();
236

237
        requestAnimationFrame(() => {
238
          self.transitionItem(nextId);
239
        });
240

241
        this.removeEventListener("transitionend", onTextTransitionEnd);
242
      }
243
    }
244

245
    if (this.inTransit) return;
246

247
    const activeText = this.activeText[0];
248
    const backwardsClass = `${this.TEXT_CLASS}--backwards`;
249
    const self = this;
250

251
    this.inTransit = true;
252

253
    activeText.classList.add(backwardsClass);
254
    activeText.classList.remove(this.ACTIVE_TEXT_CLASS);
255
    activeText.addEventListener("transitionend", onTextTransitionEnd);
256

257
    requestAnimationFrame(() => {
258
      activeText.classList.remove(backwardsClass);
259
    });
260
  }
261
  next() {
262
    if (this.inTransit) return;
263

264
    let nextId = +this.activeImg[0].dataset.id + 1;
265

266
    if (nextId > this.length) nextId = 1;
267

268
    this.startTransition(nextId);
269
  }
270
  prev() {
271
    if (this.inTransit) return;
272

273
    let nextId = +this.activeImg[0].dataset.id - 1;
274

275
    if (nextId < 1) nextId = this.length;
276

277
    this.startTransition(nextId);
278
  }
279
  switchBackgroundImage(nextId) {
280
    function onBackgroundTransitionEnd(e) {
281
      if (e.target === this) {
282
        this.style.setProperty("--img-prev", imageUrl);
283
        this.classList.remove(bgClass);
284
        this.removeEventListener("transitionend", onBackgroundTransitionEnd);
285
      }
286
    }
287

288
    const bgClass = "slider--bg-next";
289
    const el = this.el;
290
    const imageUrl = `url(${this.images[+nextId - 1].src})`;
291

292
    el.style.setProperty("--img-next", imageUrl);
293
    el.addEventListener("transitionend", onBackgroundTransitionEnd);
294
    el.classList.add(bgClass);
295
  }
296
}
297

298
const sliderEl = document.getElementById("slider");
299
const slider = new Slider(sliderEl);
300

301
let timer = 0;
302
function autoSlide() {
303
  requestAnimationFrame(() => {
304
    slider.next();
305
  });
306
  timer = setTimeout(autoSlide, 5000);
307
}
308
timer = setTimeout(autoSlide, 5000);
309

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.