PageSourceSearch

https://mobifitness.ru/local/templates/mf/assets/js/js.js

js mobifitness.ru collected 2026-10-01 11:48:37 UTC 18,348 bytes, 659 lines download raw bytes

1window.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
2window.bInitFormCounter = true;
3
4let appConstructor = function () {
5  this.control = {
6    field: '[data-form-field]',
7    input: 'data-form-input',
8    label: '[data-form-label]',
9    className: {
10      filled: 'form__field_filled',
11      error: 'form__field_error',
12    },
13  };
14
15  this.dropdown = {
16    container: '[data-custom-dropdown]',
17    value: '[data-custom-dropdown-value]',
18    option: '[data-custom-dropdown-option]',
19    className: {
20      container: {
21        opened: 'form__dropdown_opened',
22      },
23      select: 'form__dropdown-select',
24      value: 'form__dropdown-value',
25      list: 'form__dropdown-options',
26      item: {
27        el: 'form__dropdown-options-item',
28        selected: 'form__dropdown-options-item_selected',
29      },
30    },
31  };
32
33  this.modal = {
34    container: '[data-modal]',
35    opened: 'is-modal-opened',
36    visible: 'modal_visible',
37  };
38};
39
40appConstructor.prototype.getScrollbarWidth = function () {
41  return window.innerWidth - document.documentElement.clientWidth;
42}
43
44appConstructor.prototype.getScrollOffset = function (
45  el,
46  offset = 0,
47  left = false,
48  wrapper = null,
49) {
50  let
51    scroll = left ?
52      window.scrollX :
53      window.scrollY,
54    scrollWrapper = wrapper ?
55      (left ?
56        document.querySelector(wrapper).scrollLeft :
57        document.querySelector(wrapper).scrollTop) :
58      0,
59    position = left ?
60      el.getBoundingClientRect().left :
61      el.getBoundingClientRect().top
62  ;
63
64  return position + scroll + scrollWrapper - offset;
65}
66
67appConstructor.prototype.getWindowParams = function () {
68  return {
69    top: window.scrollY,
70    left: window.scrollX,
71    width: window.innerWidth,
72    height: window.innerHeight,
73    xScroll: document.body.scrollWidth > document.body.clientWidth,
74    yScroll: document.body.scrollHeight > document.body.clientHeight,
75  };
76}
77
78appConstructor.prototype.getContentOffset = function (screenWidth = 768) {
79  let self = this;
80
81  return self.getWindowParams().width < screenWidth ?
82    self.int.header.height :
83    self.int.header.heightMd;
84}
85
86appConstructor.prototype.scrollTo = function (
87  to,
88  duration = 300,
89  left = false,
90  wrapper = null,
91) {
92  let
93    element = wrapper ?
94      document.querySelector(wrapper) :
95      (document.scrollingElement ?? document.documentElement),
96    direction = left ?
97      element.scrollLeft :
98      element.scrollTop,
99    start = direction,
100    change = to - start,
101    startDate = +new Date(),
102    easeInOutQuad = function (t, b, c, d) {
103      t /= d / 2;
104      if (t < 1)
105        return c / 2 * t * t + b;
106      t--;
107      return -c / 2 * (t * (t - 2) - 1) + b;
108    },
109    animateScroll = function () {
110      let
111        currentDate = +new Date(),
112        currentTime = currentDate - startDate
113      ;
114
115      if (left) {
116        element.scrollLeft = parseInt(easeInOutQuad(currentTime, start, change, duration));
117
118        currentTime < duration ?
119          requestAnimationFrame(animateScroll) :
120          element.scrollLeft = to;
121      } else {
122        element.scrollTop = parseInt(easeInOutQuad(currentTime, start, change, duration));
123
124        currentTime < duration ?
125          requestAnimationFrame(animateScroll) :
126          element.scrollTop = to;
127      }
128    }
129  ;
130  animateScroll();
131}
132
133appConstructor.prototype.handleScrollTo = function (target, offset = null) {
134  let self = this;
135
136  let
137    anchor = typeof target === 'string' ? document.querySelector(target) : target,
138    _offset = offset ?? 100
139  ;
140
141  if (anchor) {
142    self.scrollTo(self.getScrollOffset(anchor, _offset));
143  }
144}
145
146appConstructor.prototype.modalShow = function (modal) {
147  let self = this;
148
149  let _modal = document.querySelector(modal);
150
151  document.querySelectorAll(self.modal.container).forEach((el) => {
152    el.classList.remove(self.modal.visible);
153  });
154
155  if (_modal) {
156    let offset = self.getScrollbarWidth();
157    _modal.classList.add(self.modal.visible);
158    document.documentElement.classList.add(self.modal.opened);
159    document.body.style.paddingRight = offset + 'px';
160  }
161}
162
163appConstructor.prototype.modalHide = function () {
164  let self = this;
165
166  document.querySelectorAll(self.modal.container).forEach((el) => {
167    el.classList.remove(self.modal.visible);
168  });
169
170  document.documentElement.classList.remove(self.modal.opened);
171  document.body.removeAttribute('style');
172}
173
174appConstructor.prototype.viewTestimonial = function (id) {
175  let self = this;
176
177  let
178    el = document.getElementById(id),
179    modalContent = document.getElementById('modalTextContent')
180  ;
181
182  if (el && modalContent) {
183    modalContent.innerHTML = el.innerHTML;
184    self.modalShow('#modalText');
185  }
186}
187
188appConstructor.prototype.handleDropdown = function () {
189  let self = this;
190
191  let dropdowns = document.querySelectorAll(self.dropdown.container);
192
193  dropdowns.forEach(function (dropdown) {
194    let
195      select,
196      options,
197      value,
198      list,
199      param
200    ;
201
202    if (dropdown.dataset.customDropdown !== 'inited') {
203      select = dropdown.querySelector('select');
204      options = select.querySelectorAll('option');
205
206      value = document.createElement('DIV');
207      value.classList.add(self.dropdown.className.value);
208      value.classList.add('form__field-control');
209      value.setAttribute('data-custom-dropdown-value', '');
210      value.innerHTML = select.options[select.selectedIndex].innerHTML;
211
212      list = document.createElement('DIV');
213      list.classList.add(self.dropdown.className.list);
214
215      options.forEach(function (option, i) {
216        let text = String(option.innerHTML).trim();
217        param = document.createElement('DIV');
218        param.innerHTML = text;
219        param.setAttribute('data-custom-dropdown-option', '');
220        param.classList.add(self.dropdown.className.item.el);
221        param.addEventListener('click', function (e) {
222          dropdown.querySelectorAll(self.dropdown.option).forEac
222h(function (el) {
223            el.classList.remove(self.dropdown.className.item.selected);
224          });
225
226          if (option.innerHTML === this.innerHTML) {
227            value.innerHTML = this.innerHTML;
228            this.classList.add(self.dropdown.className.item.selected);
229            select.selectedIndex = i;
230            select.dispatchEvent(new Event('change'));
231          }
232
233          value.click();
234        });
235
236        if (i === select.selectedIndex) {
237          param.classList.add(self.dropdown.className.item.selected);
238          if (text === '') {
239            value.innerHTML = dropdown.dataset.placeholder ?? '';
240          }
241        }
242
243        if (text !== '') {
244          list.appendChild(param);
245        }
246      });
247
248      dropdown.appendChild(value);
249      dropdown.appendChild(list);
250      select.classList.add(self.dropdown.className.select);
251      dropdown.dataset.customDropdown = 'inited';
252
253      value.addEventListener('click', function (e) {
254        e.stopPropagation();
255        handleClose(this);
256        dropdown.classList.toggle(self.dropdown.className.container.opened);
257      });
258    }
259  });
260
261  function handleClose(el) {
262    dropdowns.forEach(function (dropdown) {
263      let dropdownValue = dropdown.querySelector(self.dropdown.value);
264
265      if (el !== dropdownValue)
266        dropdown.classList.remove(self.dropdown.className.container.opened);
267    });
268  }
269
270  document.addEventListener('click', handleClose);
271}
272
273appConstructor.prototype.handleEllipsis = function () {
274  let self = this;
275
276  document.querySelectorAll('[data-ellipsis]').forEach((text) => {
277    let
278      className = 'testimonials__slide-text_collapsed',
279      parent = text.parentNode
280    ;
281
282    if (text.scrollHeight > text.clientHeight) {
283      parent.classList.add(className);
284    } else {
285      parent.classList.remove(className);
286    }
287  });
288}
289
290appConstructor.prototype.handleField = function () {
291  let self = this;
292
293  document.addEventListener('focusout', function (e) {
294    if (e.target && e.target.hasAttribute(self.control.input)) {
295      proceed(e.target);
296    }
297  });
298
299  document.addEventListener('input', function (e) {
300    if (e.target && e.target.hasAttribute(self.control.input)) {
301      proceed(e.target);
302      let field = e.target.closest(self.control.field);
303      if (field) {
304        field.classList.remove(self.control.className.error);
305      }
306    }
307  });
308
309  function proceed(input) {
310    let field = input.closest(self.control.field);
311
312    field && input.value.length && input.value !== '' ?
313      field.classList.add(self.control.className.filled) :
314      field.classList.remove(self.control.className.filled);
315  }
316}
317
318appConstructor.prototype.handleForm = function () {
319  let self = this;
320
321  let proceed = true;
322
323  if (proceed) {
324    Array.from(document.forms).forEach((form) => {
325      form.onsubmit = (e) => e.preventDefault();
326      form.querySelectorAll('[type="submit"]').forEach((btn) => {
327        btn.setAttribute('disabled', '');
328        btn.disabled = true;
329      });
330    });
331    window.addEventListener('touchstart', enable);
332    window.addEventListener('mousemove', enable);
333    window.addEventListener('click', enable);
334    window.addEventListener('keydown', enable);
335  }
336
337  function enable() {
338    if (!proceed) {
339      return false;
340    }
341    document.querySelectorAll('form').forEach((form) => {
342      form.onsubmit = (e) => true;
343      form.querySelectorAll('[type="submit"]').forEach((btn) => {
344        btn.removeAttribute('disabled');
345        btn.disabled = false;
346      });
347    });
348    proceed = false;
349  }
350}
351
352appConstructor.prototype.handleFormAgreement = function (control) {
353  let self = this;
354
355  let
356    el = control.closest('.form__agreement'),
357    form = control.closest('form'),
358    formDiv = control.closest('.form'),
359    btn = form ? form.querySelector('[type="submit"]') : null,
360    modalBody = control.closest('.modal__body')
361  ;
362
363  if (el) {
364    control.checked ? el.classList.remove('form__agreement_error') : el.classList.add('form__agreement_error');
365  }
366
367  if (btn) {
368    control.checked ? btn.removeAttribute('disabled') : btn.setAttribute('disabled', '');
369  }
370
371  if (modalBody) {
372    control.checked ? modalBody.classList.remove('modal__body_error') : modalBody.classList.add('modal__body_error');
373  } else if (formDiv) {
374    control.checked ? formDiv.classList.remove('form_error') : formDiv.classList.add('form_error');
375  }
376}
377
378appConstructor.prototype.handleFormAgreementSubscribe = function (control) {
379  let self = this;
380
381  let
382    form = control.closest('form'),
383    input = form.querySelector('[data-form-input="DO_SUBSCRIBE"]')
384  ;
385
386  if (input) {
387    input.value = control.checked ? 'Да' : 'Нет';
388  }
389}
390
391appConstructor.prototype.handleFormRequired = function (form) {
392  let self = this;
393
394  let
395    btn = form.querySelector('[type="submit"]'),
396    formDiv = form.querySelector('.form'),
397    modalBody = form.querySelector('.modal__body'),
398    required = form.querySelectorAll('[required]'),
399    valid = true,
400    focus = true
401  ;
402
403  required.forEach(control => {
404    if (control.type === 'checkbox') {
405      valid = control.checked;
406      let agreement = control.closest('.form__agreement');
407      if (agreement) {
408        agreement.classList[valid ? 'remove' : 'add']('form__agreement_error');
409      }
410    } else {
411      valid = control.value.length;
412      let field = control.closest('[data-form-field]');
413      if (field) {
414        field.classList[valid ? 'remove' : 'add']('form__field_error');
415      }
416    }
417    if (focus) {
418      focus = false;
419      if (!modalBody) {
420        self.handleScrollTo(control, 140);
421      }
422    }
423  });
424
425  /*if (btn) {
426    valid ? btn.removeAttribute('disabled') : btn.setAttribute('disabled', '');
427  }*/
428
429  if (modalBody) {
430    valid ? modalBody.classList.remove('modal__body_error') : modalBody.classList.add('modal__body_error');
431  } else if (formDiv) {
432    valid ? formDiv.classList.remove('form_error') : formDiv.classList.add('form_error');
433  }
434}
435
436appConstructor.prototype.handleMask = function () {
437  let self = this;
438
439  if (typeof Inputmask === 'function') {
440    Inputmask({
441      mask: '9{15}',
442      showMaskOnHover: false,
443      placeholder: '',
444      greedy: false,
445    }).mask('[type="tel"]');
446  }
447}
448
449appConstructor.prototype.handleParallax = function (items, endX = 0, endY = 0, x = 0, y = 0) {
450  let self = this;
451
452  let pxSpeed = .75;
453
454  x += Math.floor((endX - x) / pxSpeed);
455  y += Math.floor((endY - y) / pxSpeed);
456
457  items.forEach(function (item) {
458    let val = parseInt(item.dataset.px);
459    item.style.transform = `translate(${x / val}px, ${y / val}px)`;
460  });
461}
462
463appConstructor.prototype.handleSwiper = function (id, options = {}) {
464  let self = this;
465  document.addEventListener('DOMContentLoaded', () => {
466    new Swiper(id, options);
467  });
468}
469
470appConstructor.prototype.handleTags = function () {
471  let self = this;
472
473  document.querySelectorAll('[data-tags]').forEach((el) => {
474    let
475      cnt = el.querySelector('[data-tags-cnt]'),
476      invisible = 0,
477      items = el.querySelectorAll('[data-tags-item]')
478    ;
479
480    if (cnt) {
481      el.removeChild(cnt);
482    }
483
484    countInvisible();
485
486    if (el.scrollHeight - 8 > 24) {
487      cnt = document.createElement('div');
488      cnt.classList.add('tags__item');
489      cnt.classList.add('tags__item_cnt');
490      cnt.setAttribute('data-tags-cnt', String(invisible));
491      cnt.dataset.tagsCnt = String(invisible);
492      cnt.innerHTML = '+' + String(invisible);
493      el.insertAdjacentElement('beforeend', cnt);
494      invisible = 0;
495      countInvisible();
496      cnt.innerHTML = '+' + String(invisible);
497    } else {
498      cnt = el.querySelector('[data-tags-cnt]');
499      if (cnt) {
500        el.removeChild(cnt);
501      }
502    }
503
504    function countInvisible() {
505      items.forEach((item) => {
506        if (item.offsetTop + item.offsetHeight > el.offsetTop + el.offsetHeight) {
507          invisible++;
508        }
509      });
510    }
511  });
512}
513
514appConstructor.prototype.handleTippy = function () {
515  let self = this;
516
517  tippy('[data-tippy-content]', {
518    interactive: true,
519  });
520}
521
522appConstructor.prototype.handleViewport = function () {
523  let self = this;
524
525  let meta = document.querySelector('meta[name="viewport"]');
526  if (meta) {
527    let content = document.documentElement.clientWidth < 375 ? 'width=360px' : 'width=device-width';
528    meta.setAttribute('content', content);
529  }
530}
531
532appConstructor.prototype.formReinit = function () {
533  let self = this;
534
535  self.handleField();
536  self.handleMask();
537  self.handleDropdown();
538}
539
540appConstructor.prototype.formSubmit = function (
541  form,
542  isModal = false,
543  modalSuccess = 'modalSuccess',
544  url = '/local/ajax/form.php',
545) {
546  let self = this;
547
548  let focused = 0;
549
550  form.querySelector('[type="submit"]').addEventListener('click', () => {
551    self.handleFormRequired(form);
552  });
553
554  form.querySelectorAll('[required]').forEach(control => {
555    control.addEventListener('input', e => {
556      if (control.type !== 'checkbox') {
557        let field = control.closest('[data-form-field]');
558        if (field) {
559          field.classList[control.value.length ? 'remove' : 'add']('form__field_error');
560        }
561      }
562    });
563    control.addEventListener('focus', e => {
564      focused++;
565    });
566  });
567
568  if (typeof BX != 'undefined') {
569    BX.bind(form, 'submit', BX.proxy(function (e) {
570      BX.PreventDefault(e);
571
572      handlePreloader();
573
574      let formData = new FormData(form);
575      formData.append('focus' + (new Date()).getUTCHours(), encodeURIComponent(focused));
576      formData.append('ajaxCall', window.formToken);
577
578      fetch(url, {
579        body: formData,
580        method: 'POST',
581      }).then(r => r.json()).then(r => {
582        if (r.success) {
583          form.reset();
584          App.modalShow('#' + modalSuccess);
585          handlePreloader(false);
586          if (isModal && typeof ym === 'function') {
587            ym(22994086, 'reachGoal', 'pop_up');
588          }
589        } else {
590          handlePreloader(false);
591
592          if (r.errors && typeof r.errors === 'object') {
593            form.querySelectorAll('[data-form-input]').forEach(input => {
594              if (Object.keys(r.errors)?.length && Object.keys(r.errors).includes(input.dataset.formInput)) {
595                input.closest('[data-form-field]').classList.add('form__field_error');
596              }
597            });
598          }
599        }
600      });
601    }, form, url));
602  }
603}
604
605appConstructor.prototype.zoom = function (el, e, zoom = 2) {
606  const
607    img = el.querySelector('img'),
608    { pageX, pageY } = e,
609    { width, height, left, top } = el.getBoundingClientRect(),
610    x = (pageX - left - width / 2) / width * 100,
611    y = (pageY - (top + window.scrollY) - height / 2) / height * 100,
612    tx = -x * (zoom - 1) / 2,
613    ty = -y * (zoom - 1) / 2
614  ;
615
616  img.style.transform = `scale(${zoom}) translate(${tx}%, ${ty}%)`;
617}
618
619function handlePreloader(show = true) {
620  let el = document.getElementById('preloader');
621  if (el) {
622    el.classList[show ? 'add' : 'remove']('preloader_visible');
623  }
624}
625
626appConstructor.prototype.init = function () {
627  let self = this;
628
629  document.addEventListener('DOMContentLoaded', () => {
630    self.handleDropdown();
631    self.handleField();
632    self.handleForm();
633    self.handleMask();
634    self.handleEllipsis();
635    self.handleTags();
636    self.handleTippy();
637    self.handleViewport();
638
639    window.addEventListener('resize', e => {
640      self.handleEllipsis();
641      self.handleTags();
642      self.handleViewport();
643    });
644
645    if (window.innerWidth >= 1280) {
646      window.addEventListener('mousemove', e => {
647        self.handleParallax(
648          document.querySelectorAll('[data-px]'),
649          window.innerWidth / 2 - e.x,
650          window.innerHeight / 2 - e.y,
651        );
652      });
653    }
654  });
655}
656
657App = new appConstructor();
658
659App.init();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.