Ответы пользователя по тегу JavaScript
  • Как разбить textarea на массив?

    mizutsune
    @mizutsune
    Frontend Developer
    1. Получаем содержимое textarea.
    2. Используя метод split разбиваем полученное значение на отдельные слова или предложения по разделителю (перенос строки/точка с запятой/запятая/точка/пробел/etc).
    4. Profit.

    Окончательный результат зависит от того => массив каких значений вам нужно получить и в связи с этим, нужно составлять определенные функции, которые будут решать поставленные задачи, при этом нужно учитывать достаточно множество моментов, при которых что-то может пойти не так.

    Несколько примеров

    Пример - №1:

    Задача: Получить простой массив слов, без модификации регистра заглавных букв и без удаления специальных символов, ну и всего остального.

    let value = '   Illusion is the first of all pleasures.   ';
    
    value.split(" ").filter(n => n !== "");


    1. На первом этапе разбиваем строку на массив слов, используя в качестве разделителя пробел.
    2. На втором этапе удаляются пустые значения из массива.

    Результат:

    ['Illusion', 'is', 'the', 'first', 'of', 'all', 'pleasures.']


    Пример - №2:

    Задача: Получить массив слов без специальных символов и прочего.

    let value = `       Lorem ipsum dolor sit amet, consectetur adipiscing elit, 
    sed do eiusmod, tempor et dolore magna aliqua.
    Amet facilisis magna!!! 
    Ornare quam viverra volutpat odio facilisis mauris.    `;
    
    value.split(" ").map(n => n.replace(/[\r\n.,!]/g, '')).filter(n => n !== "");


    1. На первом этапе разбиваем строку на массив слов, используя в качестве разделителя пробел.
    2. На втором этапе удаляем из каждого "слова" точки, запятые и восклицательный знак.

    Результат:

    ["Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing",
    "elit", "sed", "do", "eiusmod", "tempor", "et", "dolore",
    "magna", "aliquaAmet", "facilisis", "magna", "Ornare", "quam",
    "viverra", "volutpat", "odio", "facilisis", "mauris"]


    Пример - №3:

    Задача: Получить массив предложений из текста.

    let value = " Lorem ipsum. Dolor sit amet. Consectetur adipiscing elit.      ";
    
    value.split(".").map(n => n.trim()).filter(n => n!== "").map(n => `${n}.`):


    1. На первом этапе разбиваем строку на массив слов, используя в качестве разделителя символ точки, так как точка символизирует окончание предложения в тексте. По крайней мере, в большинстве случаев.
    2. На втором этапе обрезаем пробелы вначале и в конце каждого элемента массива.
    3. На третьем этапе избавляемся от пустых значений.
    4. На четвёртом этапе расставляем точки на концах строк, чтобы превратить их в предложения.

    Результат:

    ['Lorem ipsum.', 'Dolor sit amet.', 'Consectetur adipiscing elit.']


    Пример - №4:

    Задача: Разделить строку на части и записать их в массив, используя несколько разных разделителей.

    let value = "Lorem ipsum, tempor magna aliqua. Dolor sit amet? Consectetur! Adipiscing elit.";
    
    value.split(/[,.?!]/).map(n => n.trim()).filter(n => n!== "");


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

    Результат:

    ['Lorem ipsum', 'tempor et dolore magna aliqua', 'Dolor sit amet', 'Consectetur', 'Adipiscing elit']




    Вариантов разделения строки на массив может быть огромное множество.
    Ответ написан
    Комментировать
  • Почему не работает анимация? CSS JS?

    mizutsune
    @mizutsune
    Frontend Developer
    Анимация не работает потому что в переменной --animation-duration скорее всего содержится <число>ms. Если указать только число, то всё будет работать.

    Однако зачем такие calc(var(--animation-duration) * 1ms) сложности? Неужели не проще написать напрямую время анимации или пусть даже в переменной, но без calc() и прочего? Для чего лишние вычисления и прочие операции? Не нужно ничего усложнять.

    Можно написать намного проще.

    Указываем нужное время анимации в переменной:

    --animation-duration: 500ms;

    Используем без всяких наворотов и лишних движений:

    .add-panel_button.sending .svg{
        animation: fly-out-in var(--animation-duration) ease-in-out;
    }


    В скрипте тоже содержится достаточно лишнего кода. Вместо setTimeout можно прослушивать событие animationend на нужном элементе и тогда лишние вычисления станут реально "лишними" и их можно будет удалить.

    addTodoButton.addEventListener("click", () => {
         addTodoButton.classList.add("sending");
         addTodoButton.addEventListener("animationend", e => {
                   addTodoButton.classList.remove("sending");
                   addItem();
              }, { once: true }
         );
    });
    Ответ написан
    1 комментарий
  • Как объединить название объекта и переменную?

    mizutsune
    @mizutsune
    Frontend Developer
    let shop_id = 82;
    data[`SHOPID_${shop_id}`].name_shop;
    Ответ написан
    Комментировать
  • Как задать положение Tippy.js в зависимости от размера окна?

    mizutsune
    @mizutsune
    Frontend Developer
    Можно попробовать таким образом менять положение:

    const instance = tippy(".target");
    const breakpoint = window.matchMedia("(max-width: 1200px)");
    
    const breakpointChecker = () => {
         if (breakpoint.matches) {
                  instance[0].setProps({
                   placement: "bottom-end"
              });
    
         } else {
              instance[0].setProps({
                   placement: "left-start"
              });
         }
    };
    
    breakpoint.addEventListener("change", breakpointChecker);
    breakpointChecker();


    Библиотека Tippy предоставляет метод setProps, который позволяет менять свойства экземпляра, а значит если воспользоваться этим методом при достижении нужной ширины экрана, можно обновить свойство placement.

    Вместо matchMedia конечно можно воспользоваться resize, но на мой взгляд - matchMedia намного лучше справляется с поставленными задачами.

    Кстати Tippy возвращает массив независимо от того, какой тип селектора используется. Это связано с тем, что функция Tippy может возвращать несколько элементов в зависимости от селектора. Поэтому чтобы вызывать методы у экземпляра, придётся обращаться к нему как к элементу массива, но это конечно если инициализация происходит без использования цикла.

    UPD:

    Если нужна инициализация нескольких тултипов и изменение параметров при определенной ширине экрана, тогда нужного результата можно добиться например так:

    // коллекция тултипов
    const triggers = document.querySelectorAll(".item__right, .item__prc");
    
    triggers.forEach(element => {
         const instance = tippy(element, {
              // ваши настройки
         });
    
         // ширина экрана при достижении которой нужно изменить настройки
         const breakpoint = window.matchMedia("(max-width: 1200px)");
    
         const togglePlacement = status => {
              // селекторы классов используемых тултипов
              const triggerOne = "item__right";
              const triggerTwo = "item__prc";
    
              const { reference: target } = instance;
    
              const isTargetElement = v => Boolean(target.classList.contains(v));
    
              switch (status) {
                   case "update":
                        if (isTargetElement(triggerOne)) {
                             instance.setProps({
                                  placement: "bottom-end"
                             });
                        } else if (isTargetElement(triggerTwo)) {
                             instance.setProps({
                                  placement: "left-start"
                             });
                        }
                        break;
                   case "default":
                        if (isTargetElement(triggerOne)) {
                             instance.setProps({
                                  placement: "left-start"
                             });
                        } else if (isTargetElement(triggerTwo)) {
                             instance.setProps({
                                  placement: "bottom-end"
                             });
                        }
              }
         };
    
         const breakpointChecker = () => {
              if (breakpoint.matches) {
                   return togglePlacement("update");
              } else {
                   return togglePlacement("default");
              }
         };
    
         breakpoint.addEventListener("change", breakpointChecker);
         breakpointChecker();
    });


    Настройки, селекторы и значения свойств placement в функции togglePlacement замените на нужные вам.
    Ответ написан
  • Как в зависимости от ширины экрана заменить html код страницы?

    mizutsune
    @mizutsune
    Frontend Developer
    Если по каким-то причинам недостаточно медиа запросов из CSS, тогда можно воспользоваться методом matchMedia.

    Простой пример использования:

    const targetElement = document.querySelector(".target");
    const breakpoint = window.matchMedia("(max-width: 700px)");
    
    const breakpointChecker = () => {
         if (breakpoint.matches) {
              targetElement.textContent = "Apple";
         } else {
              targetElement.textContent = "Orange";
         }
    };
    
    breakpoint.addEventListener("change", breakpointChecker);
    breakpointChecker();


    Однако несмотря на доступность и простоту использования данного подхода, разумеется намного лучше ограничиться использованием медиа запросов из CSS, чтобы не создавать дополнительную нагрузку применяя JavaScript там где можно обойтись без него.

    Вообще современный CSS уже способен решать многие задачи, связанные например с такими вот случаями, когда требуется модификация блока для отображения на декстопе/мобильнике и если раньше приходилось использовать две разные вёрстки того или иного элемента для разных разрешений экрана или добавлять javascript для решения подобных задач, то уже сейчас в большинстве случаев, такой нужды не осталось.

    И всё как бы понятно и хорошо, но всё же есть такие моменты когда использование JS при работе с медиа запросами - оправданно и matchMedia отлично справляется с поставленными задачами, например когда нужно включить/отключить какой-нибудь плагин или произвести какие-нибудь действия, которые невозможно выполнить используя один только CSS.

    Как альтернативу matchMedia, конечно можно использовать событие resize, но в таком случае придётся оптимизировать функцию используемую в обработчике, так как resize имеет свои подводные камни.
    Ответ написан
    Комментировать
  • Как понять, в каком из input'ов был нажат enter?

    mizutsune
    @mizutsune
    Frontend Developer
    const parentContainer = document.querySelector(".inputs");
    
    parentContainer.addEventListener("keypress", event => {
       if (event.target.tagName !== "INPUT") return;
       if (event.key !== "Enter") return;
    
       console.log(event.target.id);
    });
    Ответ написан
    Комментировать
  • Как выбрать все элементы кроме текущего в forEach?

    mizutsune
    @mizutsune
    Frontend Developer
    Варианты решения с помощью делегирования событий:

    Вешаем обработчик клика на корневой элемент и слушаем событие клика. Если клик произошел на элементе с нужным классом, тогда проходим циклом по всем элементам с этим классом, далее удаляем у них "активные" классы и добавляем их текущему элементу:

    const elements = document.querySelectorAll(".value-item");
    const stateClasses = ["value-active", "basic__shadow"];
    
    document.addEventListener("click", ({ target }) => {
       if (!target.classList.contains("value-item")) return;
       elements.forEach(v => v.classList.remove(...stateClasses));
       target.classList.add(...stateClasses);
    });


    Или:

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

    const stateClasses = ["value-active", "basic__shadow"];
    
    document.addEventListener("click", ({ target }) => {
       if (!target.classList.contains("value-item")) return;
       const activeItem = document.querySelector(".value-active");
       activeItem && activeItem.classList.remove(...stateClasses);
       target.classList.add(...stateClasses);
    });


    Варианты решения с помощью цикла forEach:

    const elements = document.querySelectorAll(".value-item");
    const stateClasses = ["value-active", "basic__shadow"];
    
    const toggleActiveElement = element => {
       element.addEventListener("click", () => {
          elements.forEach(n => n.classList.remove(...stateClasses));
          element.classList.add(...stateClasses);
       });
    };
    
    elements.forEach(v => toggleActiveElement(v));


    Или:

    const elements = document.querySelectorAll(".value-item");
    const stateClasses = ["value-active", "basic__shadow"];
    
    const toggleActiveElement = element => {
       element.addEventListener("click", () => {
          document.querySelector(".value-active")?.classList.remove(...stateClasses);
          element.classList.add(...stateClasses);
       });
    };
    
    elements.forEach(v => toggleActiveElement(v));


    Однако у этих двух вариантов есть небольшие минусы и для таких целей как переключение активного класса при клике, хорошо подходит делегирование событий. Но почему в большинстве случаев не стоит использовать цикл для решения подобных задач?

    1. Вы заставляете браузер искать все элементы с определенным классом на странице.
    2. Добавляется обработчик на каждый элемент, хотя возможно пользователь и не кликнет на этот элемент.
    3. addEventListener добавленный через цикл к каждому элементу определенного NodeList - не будет работать на динамически добавленных элементах. Разумеется если не добавить к ним обработчики, после создания, но это конечно так себе работа.

    Возможно есть и другие побочные эффекты и/или минусы у такого решения задачи.

    Обычно добавление обработчиков в цикле используется в связи с недостатком опыта или просто потому что данный способ в той или иной степени решает поставленную задачу и делать по другому тупо нет желания или смысла. Но даже если отказ от подобных решений и является просто напросто экономией на спичках и/или чем-то там ещё, в любом случае лучше использовать инструменты и функции по назначению, а не потому что просто так хочется или "удобно".

    Правда всё же есть исключения когда добавление обработчиков в цикле оправданно и будет наиболее подходящим способом решить задачу чем например через делегирование событий.
    Ответ написан
    Комментировать
  • Как обрезать строку до определенной длины и добавить многоточие?

    mizutsune
    @mizutsune
    Frontend Developer
    Если нужно просто обрезать строку по количеству символов, то этого можно добиться, например одной из этих функций:

    const truncateString = (s, w) => s.length > w ? s.slice(0, w) + "..." : s;
    
    const truncateString = (s, w) => s.length > w ? s.substring(0, w) + "..." : s;


    Пример использования:

    console.log(truncateString("123456789", 5));

    Но в этих двух вариантах есть по крайней мере, один заметный минус. Если после обрезки строки, в конце будет пробел: "123456 789", то выглядеть результат будет так: "123456 ..."

    Это решается достаточно легко. Воспользуемся функцией .trim() и в итоге, выше описанные функции, будут выглядеть так:

    const truncateString = (s, w) => s.length > w ? s.slice(0, w).trim() + "..." : s;
    
    const truncateString = (s, w) => s.length > w ? s.substring(0, w).trim() + "..." : s;


    Дополнительные решения

    Если нужно обрезать строку, а также удалить и определенные символы с конца строки(лишние точки, запятые, пробелы и прочее), перед добавлением троеточия, тогда можно сделать вот так:

    const truncateStringByLimitChars = ({ target, limit, pattern, useREGEx = true }) => {
      const ignoredChars = new RegExp(`[${pattern}]+$`, "g");
    
      const truncateString = () => {
        return useREGEx === false
          ? target.substring(0, limit).trim() + "..."
          : target.substring(0, limit).replace(ignoredChars, "").trim() + "...";
      };
    
      return target.length >= limit ? truncateString() : target;
    };


    Пример использования:

    console.log(
      truncateStringByLimitChars({
        target: "1234_______",
        limit: 8,
        pattern: [",", ".", "_"]
      })
    );


    Однако в таком случае - финальный размер строки, может быть намного меньше указанного в лимите, потому что функция удалит с конца строки все "лишние" символы. Хотя в некоторых случаях это может быть полезно.

    К примеру имеется строка: Hello__World. Обрезаем без удаления "лишних" символов до 7 символов:

    let string = "Hello__World",
    
    console.log(string.length > 7 ? string.slice(0, 7).trim() + "..." : string)


    Результат: Hello__...

    Наверное, ожидалось что в конце перед точками, не будет подчеркивания, однако оно есть и портит своим присутствием полученный результат. Тоже самое будет если вместо подчеркивания будет запятая, вопросительный знак, восклицательный знак и прочее.

    Возможно такой результат не совсем так плох, но всё же, порой это выглядит не слишком, как-бы, красиво и/или удобно. А вот если удалить лишние символы, после обрезки по лимиту символов, то конечно финальный результат будет выглядеть лучше.

    Однако это ещё не всё и при желании, можно дополнительно улучшить функцию - truncateStringByLimitChars, чтобы она корректно работала с такими строками:

    Hello-World.loooooooong.image.name.sss.123456789.jpg

    BackgroundImage_for__container.with.blog-news.cards.jpg


    Допустим имеется такая строка: "BackgroundImageForContainerWithBlogNewsCards.jpg" - это название изображения, которое выводится в определенном блоке.

    Пример:

    <div>Файл "BackgroundImageForContainerWithBlogNewsCards.jpg" успешно загружен</div>


    Задача: Обрезать эту длинную строку до 15 символов + сохранить расширение файла.

    Можно конечно и не модифицировать функцию, чтобы получить желаемый результат, например склеивая обрезанную строку с полученным расширением файла вне функции truncateStringByLimitChars, но это уже другая история и поэтому всё же модифицируем функцию.

    Модифицированная версия будет выглядеть так:

    const truncateString = configuration => {
         const {
            targetString: target,
            limitWords: limit,
            pattern = [",", ".", "_"],
            useREGEx = true,
            suffix = "...",
            addFileExtension = false
        } = configuration;
    
         const ignoredChars = new RegExp(`[${pattern}]+$`, "g");
    
         const reduceContentByLimitCharset = () => {
              return useREGEx === false ?
                 target.substring(0, limit).trim() :
                 target.substring(0, limit).replace(ignoredChars, "").trim();
         };
    
         const updatedContent = () => {
              const croppedString = reduceContentByLimitCharset();
              const fileExtension = target.slice(target.lastIndexOf(".") + 1);
    
              return addFileExtension === false ?
                 `${croppedString}${suffix}` :
                 `${croppedString}${suffix} .${fileExtension}`;
         };
    
         return target.length >= limit ? updatedContent() : target;
    };


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

    console.log(
         truncateString({
              targetString: "BackgroundImageForContainerWithBlogNewsCards.jpg",
              limitWords: 15,
              addFileExtension: true
         })
    );


    Результат: BackgroundImage... .jpg


    Есть конечно и другие варианты решения всех этих задач по обрезке строк, но, возможно вышеописанных методов будет достаточно.
    Ответ написан
    1 комментарий
  • Не работают кнопки js. Как исправить?

    mizutsune
    @mizutsune
    Frontend Developer
    Если вам нужны табы, то их можно написать как-то так:



    В итоге можно создавать несколько контейнеров с табами на одной странице, переключать активные классы не только у панелей, но и у кнопок, и возможно дополнить функционал какими-нибудь дополнительными функциями.
    Ответ написан
  • Как динамически создать всплывающее окно для элемента?

    mizutsune
    @mizutsune
    Frontend Developer
    Поисковые запросы для поиска примеров и готовых решений данной задачи:

    popover on selected text
    popover on selected text vanilla js
    tooltip on text selection

    Как это реализовать? Используйте Selection API, как вам и подсказали в соседнем ответе, далее нужно будет определить позицию выделенного текста. Далее создаем popover окно с нужным содержимым, устанавливаем абсолютное позиционирование, добавляем дополнительные стили, а ещё: устанавливаем в качестве параметров top и left, полученные позиции. Вот нашелся вариант: https://codepen.io/FezVrasta/pen/vWXQdq
    Ответ написан
    Комментировать
  • Как сделать переключение тем на js?

    mizutsune
    @mizutsune
    Frontend Developer
    Ответ написан
    Комментировать
  • Как в скрипт смены темы добавить смену svg иконки кнопки с солнца на луну?

    mizutsune
    @mizutsune
    Frontend Developer
    Смену иконки можно реализовать без дополнительного JS. Ниже добавил два варианта решения задачи.

    Первый пример:

    Берем две иконки SVG. Допустим это Луна и Солнце. Далее нужно будет закодировать SVG для использования в CSS и сделать это можно, например, с помощью этого инструмента.

    Далее обработанные SVG применим для иконки. Сама иконка будет представлять из себя псевдоэлемент, хотя можно использовать и HTML тег, например span, но всё же не стоит добавлять лишние сущности в разметку, если в этом нет нужды.

    Добавляем в кнопку псевдоэлемент для иконки:

    .btn::after {
         content: "";
         position: relative;
         display: inline-flex;
         width: 20px;
         height: 20px;
         background-size: contain;
         background-repeat: no-repeat;
    }


    Далее на основе выбранной темы, будем менять бекграунд иконки. Так как через JS происходит добавление класса к корневому элементу с названием текущей темы - можно воспользоваться этим и через CSS без лишних проблем менять оформление иконке.

    Светлая тема:

    .light .btn::after{
    background-image: url("data:image/svg+xml...//иконка-солнца");
    }


    Темная тема:

    .dark .btn::after {
    background-image: url("data:image/svg+xml...//иконка-луны");
    }


    Хотя этот вариант и кажется простым и доступным, всё же он хранит в себе некоторые недостатки.

    1. Нельзя просто взять и изменить цвет иконки при наведении.
    2. При желании изменить дефолтный цвет иконки, придётся взять оригинальный SVG - вектор, перекрасить его и заново закодировать, после чего обновить значение в CSS файле.
    3. Если иконки большие по обьему, они будут увеличивать размер CSS файла, но тут есть решение этой проблемы. Перед конвертацией SVG в CSS, прогоняем вектор через SVGO. Правда и это порой не сильно помогает.

    Второй вариант:

    Добавить обе иконки в кнопку, в качестве SVG, задать иконкам разные классы и по дефолту скрыть обе иконки, например:

    <svg class="icon__light">
    // содержимое SVG
    </svg>


    <svg class="icon__dark">
    // содержимое SVG
    </svg>


    И через CSS менять видимость иконок.

    Пример:

    .icon__light,
    .icon__dark {
         display: none;
    }
    
    .light .icon__light {
         display: block;
    }
    
    .dark .icon__dark {
         display: block;
    }


    У этого подхода тоже имеются свои подводные камни, впрочем, как и у любого способа решить ту или иную задачу, но минусами этого подхода намного проще бороться.
    Ответ написан
    2 комментария
  • Можно ли как-то найти элемент, создаваемый по клику?

    mizutsune
    @mizutsune
    Frontend Developer
    Можно ли как-то найти элемент, создаваемый по клику?


    Конечно можно. Например прочитав документацию по MutationObserver и применив полученные знания на практике, можно добиться желаемого результата.

    Однако можно обойтись и без MutationObserver, но это уже зависит от структуры исходного кода который вы используете и желаемого результата.
    Ответ написан
    Комментировать
  • Почему не работает бургер меню?

    mizutsune
    @mizutsune
    Frontend Developer
    Не работает потому что у вас ошибка в строчках:

    menu.toggleClass('.burger-menu_active');
    menu.hasClass('.burger-menu_active')

    Нужно просто убрать точки перед именами классов в этих методах:

    menu.toggleClass() & menu.hasClass()

    Полезная информация по этим методам: JQuery - toggleClass и JQuery - hasClass и совет на будущее: Если используете ту или иную библиотеку, читайте внимательнее документацию. Это намного упрощает жизнь.

    Однако уже пора отказаться от JQuery, так как эта библиотека устарела и нет смысла тянуть лишнию зависимость.

    Переписать исходную функцию на нативный JS, можно например так:

    function burgerMenu(selector) {
      const menuElement = document.querySelector(selector);
    
      const elements = [
        "burger-menu_link",
        "burger-menu_button",
        "burger-menu_overlay",
      ];
    
      const isTriggerElement = (element) => {
        return [...element.classList].some((className) =>
          elements.includes(className)
        );
      };
    
      menuElement.addEventListener("click", ({ target }) => {
        if (isTriggerElement(target)) {
          toggleMenu();
        }
      });
    
      function toggleMenu() {
        menuElement.classList.toggle("burger-menu_active");
        menuElement.classList.contains("burger-menu_active")
          ? document.body.classList.add("no-scroll")
          : document.body.classList.remove("no-scroll");
      }
    }
    
    burgerMenu(".burger-menu");


    И немного дополнить стили.

    Добавить правило pointer-events: none для элемента .burger-menu_lines

    .burger-menu_lines {
       pointer-events: none;
    }


    И добавить новый класс:

    .no-scroll {
       overflow: hidden;
    }


    Намного проще написать подобный функционал на чистом JS и не стоит тащить в проект зависимость в виде Jquery. Лучшие времена этой библиотеки уже давно прошли.
    Ответ написан
    1 комментарий