Ответы пользователя по тегу JavaScript
  • Почему drag and drop некорректно работает при быстром перемещении?

    0xD34F
    @0xD34F Куратор тега JavaScript
    У вас обработчик mousemove вешается на перемещаемый элемент - естественно, что при выходе курсора за пределы элемента перемещение прекращается. А во втором примере, где всё окей - там обработчик mousemove вешается на document.
    Ответ написан
    1 комментарий
  • Javascript как зациклить вывод дней недели?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Собираем массив с днями:

    const days = ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'];
    
    // или
    
    const days = Array.from({ length: 7 }, (_, i) => new Date(1, 0, i)
      .toLocaleString('ru', { weekday: 'long' })
      .replace(/./, m => m.toUpperCase()
    ));

    Зацикливаем вывод:

    for (
      let iDay = 0;
      confirm(`${days[iDay]}. Хотите увидеть следующий день?`);
      iDay = (iDay + 1) % days.length
    ) ;
    Ответ написан
    Комментировать
  • Как найти последний элемент массива с нужный свойством?

    0xD34F
    @0xD34F Куратор тега JavaScript
    сделал это так, но мне кажется, это не совсем производительно и есть решение получше

    "Производительно" далеко не всегда значит "лучше". Бывает так, что производительностью можно пренебречь в пользу краткости и читаемости (шутка, являются ли варианты ниже более читаемыми, вопрос спорный). Код - он не только для компьютеров, но и для людей.

    Вот, смотрите - одна строка вместо шести ваших:

    const lastId = arr.reduce((id, n) => n.glob_id || id, null);
    // или
    const lastId = (arr.filter(n => n.glob_id).pop() || {}).glob_id;
    // или
    const lastId = ([...arr].reverse().find(n => n.glob_id) || {}).glob_id;
    // или
    const lastId = arr.map(n => n.glob_id).filter(Boolean).pop();

    Ну а если говорить более серьёзно, то можно перебирать массив от конца к началу, и прерывать цикл после того, как будет обнаружен первый подходящий элемент:

    let lastId = null;
    for (let i = arr.length; i--;) {
      if (arr[i].glob_id) {
        lastId = arr[i].glob_id;
        break;
      }
    }

    UPD. Наконец-то подъехал метод, позволяющий решить задачу и коротко, и без стояния на голове. И трёх с половиной лет ждать не пришлось:

    const lastId = arr.findLast(n => n.glob_id)?.glob_id;
    Ответ написан
  • Как узнать какой по счёту элемент в dom дереве имеет определённый класс?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Определимся с элементом, у которого будем узнавать индекс - внутри кого он находится, что это за элемент, какой у него должен быть класс:

    const parentSelector = 'div';
    const childSelector = 'p';
    const className = 'there';
    
    const siblingsSelector = `${parentSelector} > ${childSelector}`;
    const elementSelector = `${siblingsSelector}.${className}`;

    Дальше есть варианты:

    const index = Array.prototype.findIndex.call(
      document.querySelectorAll(siblingsSelector),
      n => n.classList.contains(className)
    );

    или

    let index = -1;
    for (
      let el = document.querySelector(elementSelector);
      el;
      index++, el = el.previousElementSibling
    ) ;

    или

    const el = document.querySelector(elementSelector);
    const index = el ? [...el.parentNode.children].indexOf(el) : -1;

    Если вдруг у элемента могут быть другие соседи, не соответствующие childSelector, и они не должны учитываться, то третий вариант не подходит, а во втором надо заменить index++ на index += el.matches(childSelector).
    Ответ написан
    Комментировать
  • Как сделать так, чтобы при прокрутке колеса мыши инициировалось нажатие какое-нибудь клавиши на клавиатуре?

    0xD34F
    @0xD34F Куратор тега JavaScript
    document.addEventListener('keypress', function(e) {
      if (e.keyCode === 32) {
        console.log('space pressed');
      }
    });
    
    document.addEventListener('wheel', function(e) {
      const event = new Event('keypress');
      event.keyCode = 32;
      document.dispatchEvent(event);
    });
    Ответ написан
    7 комментариев
  • Как из набора элементов удалить определённое количество из начала и конца?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Где элементы находятся, и сколько с какого конца надо удалить:

    const selector = 'div p';
    const removeFromStart = 5;
    const removeFromEnd = 3;

    Удаляем:

    document.querySelectorAll(selector).forEach((n, i, a) => {
      if (i < removeFromStart || i > a.length - removeFromEnd - 1) {
        n.remove();
      }
    });

    Или, если родитель действительно общий, то можно и так:

    document.querySelectorAll([
      `${selector}:nth-child(-n+${removeFromStart})`,
      `${selector}:nth-last-child(-n+${removeFromEnd})`,
    ]).forEach(n => n.outerHTML = '');
    Ответ написан
    Комментировать
  • Заменить все совпадения в тексте на ссылку?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Режете текст на куски регуляркой, соответствующей ссылкам (важный момент: надо поместить всё выражение в скобки, это позволит получить в результирующем массиве и сами разделители - будущие ссылки), обходите полученный массив - если элемент соответствует регулярке, делаете ссылку, нет - оставляете как есть. Типа так.
    Ответ написан
    Комментировать
  • Как сделать ограничение на уменьшение числа в input?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Добавить input'у атрибуты min/max; всем трём элементам добавить общую обёртку (уверен, на странице подобных блоков будет больше одного):

    <div class="counter">
      <button class="minus">-</button>
      <input type="number" min="2" max="17" value="5">
      <button class="plus">+</button>
    </div>

    При обновлении input'а ограничивать новое значение значениями атрибутов min/max (если где-то ограничений быть не должно, то вместо отсутствующих значений атрибутов подставлять +/- бесконечность). Кроме того, неплохо бы обрабатывать пользовательский ввод в input - а ну как кто захочет его напрямую отредактировать, мимо кнопок.

    Как это может выглядеть:

    const updateInput = change => e =>
      $('input', e.delegateTarget).val(function(i, v) {
        const min = this.min || -Infinity;
        const max = this.max || Infinity;
        return Math.max(min, Math.min(max, (v | 0) + change));
      });
    
    $('.counter')
      .on('click', '.minus', updateInput(-1))
      .on('click', '.plus', updateInput(1))
      .on('input', updateInput(0));

    Или, к чёрту jquery:

    const updateInput = change => e => {
      const input = e.target.closest('.counter').querySelector('input');
      const min = input.min || -Infinity;
      const max = input.max || Infinity;
      input.value = Math.min(max, Math.max(min, (input.value | 0) + change));
    };
    
    document.querySelectorAll('.counter').forEach(function(n) {
      n.querySelector('.minus').addEventListener('click', this[0]);
      n.querySelector('input').addEventListener('input', this[1]);
      n.querySelector('.plus').addEventListener('click', this[2]);
    }, [ -1, 0, 1 ].map(updateInput));

    Или, не обязательно назначать обработчики событий каждому элементу индивидуально, достаточно по одному делегированному на тип события:

    document.addEventListener('click', ({ target: t }) => {
      const change =
        t.classList.contains('plus')  ? +1 :
        t.classList.contains('minus') ? -1 :
                                         0;
      if (change) {
        const input = t.closest('.counter').querySelector('input');
        input.value -= -change;
        input.dispatchEvent(new Event('input'), { bubbles: true });
      }
    });
    
    document.addEventListener('input', ({ target: t }) => {
      if (t.closest('.counter')) {
        t.value = Math.min(t.max || Infinity, Math.max(t.min || -Infinity, t.value | 0));
      }
    });

    А ещё можно добавить кнопкам data-атрибуты, указывающие, на сколько должно быть изменено значение input'а, и тогда проверять у кнопок наличие классов будет не надо:

    <button class="minus" data-change="-1">-</button>
    <button class="plus" data-change="+1">+</button>

    document.addEventListener('input', updateInput);
    document.addEventListener('click', updateInput);
    
    function updateInput({ target: t }) {
      const counter = t.closest('.counter');
      if (counter) {
        const input = counter.querySelector('input');
        const min = input.min || -Infinity;
        const max = input.max || Infinity;
        input.value = Math.min(max, Math.max(min, (input.value | 0) + (t.dataset.change | 0)));
      }
    }
    Ответ написан
    Комментировать
  • Анимация скролла к элементу с id, при зафиксированных ссылках?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Надо добавлять к желаемому значению scrollTop текущий scrollTop контейнера, т.е.,

    position = anchor.position().top;

    замените на

    position = anchor.position().top + $('#container').scrollTop();
    Ответ написан
  • Как открыть в лайтбоксе только отфильтрованные фото?

    0xD34F
    @0xD34F Куратор тега JavaScript
    А чем у вас сейчас отфильтрованные изображения отличаются от прочих? Видимостью. Под это дело у jquery есть специальный селектор, :visible. Вот с его помощью и отбирайте следующее изображение, например так.

    UPD. Сразу не заметил, у вас там отфильтрованным изображениям назначается специальный класс - конечно, вместо :visible лучше использовать его (в примере по ссылке выше можно просто заменить одно на другое).
    Ответ написан
  • Как изменить содержимое в кнопке при клике?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const buttonSelector = 'button';
    const getButtons = () => document.querySelectorAll(buttonSelector);

    Можем делегировать обработку кликов по кнопкам:

    document.addEventListener('click', e => {
      if (e.target.matches(buttonSelector)) {
        const buttons = getButtons();
        const texts = Array.from(buttons, n => n.textContent);
        for (const [ i, n ] of buttons.entries()) {
          n.textContent = texts[-~i % texts.length];
        }
      }
    });

    А можем назначить обработчик клика каждой кнопке индивидуально:

    const buttons = getButtons();
    const onClick = () =>
      buttons.forEach(function(n, i) {
        n.innerText = ++i === buttons.length ? this : buttons.item(i).innerText;
      }, buttons.item(0).innerText);
    
    buttons.forEach(n => n.addEventListener('click', onClick));
    Ответ написан
    Комментировать
  • Как удалить второй текст div?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Оборачивайте текст в span'ы - тогда и проблем с удалением не будет.

    Ну или можно воспользоваться знанием того факта, что ваш текст представляет собой отдельные узлы DOM-дерева:

    const div = document.querySelector('.line');
    div.removeChild(div.childNodes[2]);
    Ответ написан
    1 комментарий
  • Как добавить общие обёртки элементам с одним классом, которые находятся между элементами с другим классом?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const $headers = $('.bf-attr-group-header');
    $headers.each(i => $headers
      .eq(i)
      .nextUntil($headers.get(i + 1))
      .wrapAll('<div class="wrapper">')
    );

    или

    document.querySelectorAll('.bf-attr-group-header').forEach((n, i, a) => {
      const wrapper = document.createElement('div');
      const elems = [];
      for (let el = n; (el = el.nextElementSibling) && el !== a[i + 1]; elems.push(el)) ;
      wrapper.append(...elems);
      wrapper.classList.add('wrapper');
      n.after(wrapper);
    });
    Ответ написан
    Комментировать
  • Можно ли захватить и использовать количество кликов по кнопке?

    0xD34F
    @0xD34F Куратор тега JavaScript
    document.querySelectorAll('button').forEach(n => n.addEventListener('click', onClick));
    
    function onClick() {
      const count = (+this.dataset.clickCount || 0) + 1;
      this.innerText = `clicked: ${count}`;
      this.dataset.clickCount = count;
    }
    Ответ написан
    1 комментарий
  • Как добавить в начало строки +?

    0xD34F
    @0xD34F Куратор тега JavaScript
    $('span').text((i, text) => text[0] === '-' ? text : `+${text}`);

    или

    document.querySelectorAll('span').forEach(n => {
      n.innerText = n.innerText.replace(/^(?!-)/, '+');
    });

    или

    for (const n of document.getElementsByTagName('span')) {
      n.textContent = '+'.slice(n.textContent[0] === '-') + n.textContent;
    }
    Ответ написан
    Комментировать
  • Как достать данные из такого массива js?

    0xD34F
    @0xD34F Куратор тега JavaScript
    function find(arr, id) {
      for (const { name, hobbies } of arr) {
        const { title } = hobbies.find(n => n.id === id) || {};
        if (title) {
          return { name, title };
        }
      }
    
      return null;
    }
    
    
    const data = find(arr, 4);
    Ответ написан
    Комментировать
  • Как сделать так чтобы в массиве не было одинаковых элементов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Делаем нормально: [...new Set(arr)].

    И ненормально:

    Object.values(arr.reduce((acc, n) => (acc[n] = n, acc), {}))
    
    // или
    
    Array.from(new Map(arr.map(n => [ n, n ])).values())
    
    // или
    
    arr.filter(function(n) {
      return !(this[n] = this.hasOwnProperty(n));
    }, {})
    
    // или
    
    arr.filter(((picked, n) => !picked.set(n, picked.has(n)).get(n)).bind(null, new Map))
    
    // или
    
    arr.filter((n, i, a) => i === a.indexOf(n))
    
    // или
    
    arr.reduce((acc, n) => (acc.includes(n) || acc.push(n), acc), [])
    Ответ написан
    2 комментария
  • Как сделать чтобы открывалось внутри выбранного селектора?

    0xD34F
    @0xD34F Куратор тега JavaScript
    $('p select').change(function() {
      $(this)
        .parent()
        .find('.colors').hide()
        .end()
        .find(`.${this.value}`).show();
    });
    Ответ написан
    Комментировать
  • Как при клике по однотипным элементам делать их текстовое содержимое значением input'а?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const input = document.querySelector('#textLink');
    const buttonSelector = '.link';

    Делегирование, назначаем обработчик клика один раз:

    document.addEventListener('click', e => {
      const button = e.target.closest(buttonSelector);
      if (button) {
        input.value = button.innerText;
      }
    });

    Или, назначаем обработчик клика каждому элементу индивидуально:

    document.querySelectorAll(buttonSelector).forEach(function(n) {
      n.addEventListener('click', this);
    }, e => input.value = e.currentTarget.textContent);
    Ответ написан
    Комментировать