В серверных компонентах Javascript-библиотеки React обнаружилась критическая уязвимость самого высокого уровня опасности. Под угрозой все React и Nextjs приложения. #программирование #javascript #reactjs
В серверных компонентах Javascript-библиотеки React обнаружилась критическая уязвимость самого высокого уровня опасности. Под угрозой все React и Nextjs приложения. #программирование #javascript #reactjs
Он был разработан за очень короткий срок, изменил мир интернета и стал одним из самых популярных языков программирования в мире. В декабре JavaScript исполняется 30 лет. Вспоминаем самые интересные факты о нём.
Идея, которая пришла из любви
Сегодня хочу поделиться своим опытом использования кастомного хука "useArray" в React. Этот инструмент стал для меня настоящим открытием, упростив работу с массивами в различных проектах. Давайте разберёмся, как он работает и чем может быть полезен.
Для своего проекта мне приходилось использовать сторонний виджет шахматной доски. Это ускоряет разработку на старте, но вызывает сложности при поддержке проекта. Так я решил создать универсальный модуль шахматной доски на чистом JavaScript + Canvas, лёгкий, без зависимостей и с открытым кодом.
Хотел поделиться небольшой разработкой, которая может быть полезна тем, кто использует сервисы Yandex в своих проектах. Речь идет о плагине Yandex GPT Writer.
Что такое Ya…
Представьте обычное утро в метро. Вы подходите к турникету, прикладываете карту, и... турникет решает, пропустить вас или нет. В этот момент происходит маленькое чудо автоматизации – турникет действует как конечный автомат, простая но эффективная система принятия решений.
Асинхронный код — это одна из самых сложных тем для начинающих разработчиков. Особенно, когда речь заходит о Promise и их цепочках. Задача усложняется еще больше, когда в учебниках и туториалах используют запутанные примеры, которые не помогают, а только больше сбивают с толку. Давайте разберемся с основами работы с промисами раз и навсегда, без ли…
В этой статье я расскажу о процессе разработки модификации для сайта на конструкторе Tilda — слайдера с отзывами в виде карточек, которые расположены по кругу, наподобие игральных карт.
Пишу клон игры Flappy Bird с помощью chatGPT и новейшей текстовой модели o1 на React JS!
В ролике:
2. В любой HTML элемент добавьте data-chessboard атрибут:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Шахматная доска отобразиться при загрузке страницы:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Шахматная доска с установленной позицией FEN: rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6","image":{"type":"image","data":{"uuid":"0368845d-86da-5593-891c-529deac16fa7","width":400,"height":400,"size":10312,"type":"png","color":"748baa","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAABAUGB//EACQQAAICAQIGAwEAAAAAAAAAAAECAwQRBSEABhITMWEHIkGB/8QAFgEBAQEAAAAAAAAAAAAAAAAAAwEF/8QAHBEBAAICAwEAAAAAAAAAAAAAAQACAxESQfAx/9oADAMBAAIRAxEAPwDVV5L0xKkPZWFJVcP3Y4/t1bEkZ3C7eDuT0k4ZQp3mtfhMQs62wex8Z8t3Z5Lk9R5JJ3Mrua9VyzMcklmkDHOfJAJ/QOLwr7cnO/gjyZiGyCcmnAx9koxJ/pAJ9gcCrsIwCLKHS9PoTaZUllo13d4I2ZmiUliVGSTjc8Bksl0HuPjqNBTqf//Z"}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Примеры использования"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"1. С помощью установки FEN можно анимировать ходы:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const board = new Chessboard({skin: \"brown-theme\"});\nconst fenPositions = [/* FEN-строки */];\nfenPositions.forEach((fen, index) => {\n setTimeout(() => board.setFEN(fen), (index + 1) * 1000);\n});","lang":""}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Динамическое изменение позиции на шахматной доске","image":{"type":"image","data":{"uuid":"7f905296-52f6-5a42-a747-b2ab43c719e4","width":400,"height":400,"size":30076,"type":"gif","color":"efd8b6","hash":"","external_service":[],"duration":13.8,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2. Создаем доску размером 6x6, переворачиваем и изменяем тему:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Устанавливаем нестандартный размер доски:\nconst board = new Chessboard({\n rows: 6, \n cols: 6, \n fen: \"nqkbnr/pppppp/6/6/PPPPPP/NQKBNR\"\n});\nboard.setOrientation(\"black\"); // Переворачиваем доску\n// Поддерживаемые темы:\nconst skinList = [\"green-theme\", \"brown-theme\", \"blue-theme\"];\n// Меняем тему каждые 2 секунды:\nskinList.forEach((skin, index) => {\n setTimeout(() => {\n board.setSkin(skin);\n }, (index + 1) * 2000);\n});","lang":""}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Нестандартный размер, переключение темы и переворот доски","image":{"type":"image","data":{"uuid":"5a169bb6-f2c6-5a41-a083-094762300ecb","width":400,"height":400,"size":88686,"type":"gif","color":"718ba3","hash":"","external_service":[],"duration":7.5,"isVideo":false,"has_audio":false}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Репозиторий"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fgitlab.com%2Fapicase%2FChessboard&postId=1905646","title":"Sergey Efimenko / Chessboard · GitLab","description":"Chessboard.js: Interactive Lightweight Chessboard","image":{"type":"image","data":{"uuid":"8d2a4b76-f7fb-50fe-922a-c0add3864014","width":180,"height":180,"size":3632,"type":"png","color":"ed5428","hash":"","external_service":[]}},"v":1,"hostname":"gitlab.com"}}}},{"type":"delimiter","cover":false,"hidden":false,"anchor":"","data":{"type":"default"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"#Универсальня_шахматаная_доска #Шахматная_доска #Шахматы #Chess #Chessboard #ChessboardJS #UniversalChessBoard #JavaScript #Canvas #GitLab
"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":5,"favorites":2,"reposts":0,"views":1702,"hits":404,"reads":null,"online":0},"dateFavorite":0,"hitsCount":404,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":"Универсальная шахматная доска, модуль на JavaScript, рендеринг через Canvas, установка позиций с FEN-нотацией, кастомизация и нестандартные размеры.","url":"https://vc.ru/tribuna/1905646-chessboard-js-universalnaya-shahmatnaya-doska","author":{"id":72936,"name":"Сергей Ефименко","nickname":null,"description":null,"uri":"","avatar":{"type":"image","data":{"uuid":"6aeec8ce-95cf-5153-86f5-2ef3246f899f","width":640,"height":640,"size":111806,"type":"jpg","color":"bed8e5","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAICAgICAQICAgIDAgIDAwYEAwMDAwcFBQQGCAcJCAgHCAgJCg0LCQoMCggICw8LDA0ODg8OCQsQERAOEQ0ODg7/2wBDAQIDAwMDAwcEBAcOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7/wAARCAAKAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAABwj/xAAmEAACAQIFAgcAAAAAAAAAAAABAgMEBQAGBxEhEzEUIkJRYXFy/8QAFAEBAAAAAAAAAAAAAAAAAAAABv/EABoRAAIDAQEAAAAAAAAAAAAAAAECAAMRIRP/2gAMAwEAAhEDEQA/ALBzZm+jXVXL9FQvAtplzU/jJyzDdCIX5UeUrv2J5B+8Lkmpen0VQ8Ul8tyujFWBePgjv6sQBcUQZrZwihzbusWA56gYbP8Aoe/fALW3u9NeKtmu9azGZiSapySdz84Si29Ccc9hPxpcDVHJ/9k="}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":5348510,"userId":72936,"count":0,"shareImage":"https://api.vc.ru/achievements/share/5348510"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":1724423,"userId":72936,"count":0,"shareImage":"https://api.vc.ru/achievements/share/1724423"},{"title":"5 лет на vc.ru","code":"registration_5_years","description":"Провёл 5 лет вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"a9140d54-73b8-5f40-afa8-449fbaafd42b","formats":{"glb":"https://static.vc.ru/achievements/whale.glb","usdz":"https://static.vc.ru/achievements/whale.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.533203125,"textY":0.658203125,"logoX":0.533203125,"logoY":0.77734375,"logoXNoText":0.4375,"logoYNoText":0.66015625},"id":497715,"userId":72936,"count":0,"shareImage":"https://api.vc.ru/achievements/share/497715"}],"lastModificationDate":1764905263,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":true,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":199116,"name":"Трибуна","description":"Место для продвижения себя или своего проекта. Берёте в руки микрофон и рассказываете всем, почему вы крутой. Как это делается: https://vc.ru/tribuna/58504","uri":"/tribuna","avatar":{"type":"image","data":{"uuid":"04607ca7-338b-561e-9403-3f06a70ef789","width":1200,"height":1200,"size":78591,"type":"png","color":"ebfbe3","hash":"302828e8f0303030","external_service":[]}},"cover":{"type":"image","data":{"uuid":"469caab9-c9db-5650-af7d-edf2c276c021","width":960,"height":280,"size":19176,"type":"png","color":"ebfbe3","hash":"","external_service":[]}},"lastModificationDate":1695887949,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"tribuna","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":14}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1861977,"customUri":null,"subsiteId":4237909,"title":"Упрощаем работу с Yandex GPT: небольшой плагин Yandex GPT Writer для node.js","date":1741849594,"dateModified":1760567430,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"Хотел поделиться небольшой разработкой, которая может быть полезна тем, кто использует сервисы Yandex в своих проектах. Речь идет о плагине Yandex GPT Writer.
"}},{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"Что такое Yandex Writer?
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"По сути, это плагин, который служит своего рода \"оберткой\" для API YandexGPT. Он призван облегчить взаимодействие с этим API, предоставив более удобный интерфейс.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Пример:
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Можно добавить в телеграм бота на node.js
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"e71d6a62-cf3e-5290-8807-97de2d9ddafc","width":1074,"height":116,"size":36238,"type":"png","color":"1d242c","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQEAkACQAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAUI/8QAIRAAAQMEAQUAAAAAAAAAAAAAAQACAwQFBhFBEhYhktL/xAAWAQEBAQAAAAAAAAAAAAAAAAAABAX/xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAwDAQACEQMRAD8Aw9VY1Q08b3x5ZaJy0gdMbpNnzokbYBrlaCKHb1r5y+1+s3whENAQf//Z"}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В чем смысл?
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Основная идея – это простота и удобство. Не нужно тратить время на изучение документации и настройку API. Плагин старается максимально упростить этот процесс.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Что необходимо:
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Нужно иметь платежный аккаунт в yandex cloud, получить Catalog ID и Oauth Token для работы с GPT.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Заключение
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В общем, вот такой небольшой инструмент. Возможно, кому-то пригодится. Буду рад, если поделитесь своим мнением!
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"#AI #YandexGPT #NodeJS #NPM #разработкаприложений #TelegramBot #IT #YandexCloud #javascript #typescript #telegrambot
"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":0,"reposts":0,"views":83,"hits":103,"reads":null,"online":0},"dateFavorite":0,"hitsCount":103,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1861977-uproshaem-rabotu-s-yandex-gpt-nebolshoi-plagin-yandex-gpt-writer-dlya-nodejs","author":{"id":4237909,"name":"Михаил Липовка","nickname":null,"description":"От набросков в блокноте до работающего приложения: взгляд разработчика. Здесь вы найдете полезные инструменты и реальные кейсы из мира разработки ПО.","uri":"","avatar":{"type":"image","data":{"uuid":"73eb8acd-f94f-5a9b-a130-909fda9d21ad","width":688,"height":1100,"size":120194,"type":"jpg","color":"34312c","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAKAAoDASEAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABAYH/8QAIhAAAQQCAAcBAAAAAAAAAAAAAQIDBBEABQYhMTNBYXFy/8QAFgEBAQEAAAAAAAAAAAAAAAAABAMF/8QAHREAAgEEAwAAAAAAAAAAAAAAAAECBBIxMlFS8P/aAAwDAQACEQMRAD8Am9ht9I3t25De4SJcpwBxwG1JTRqjzArr7wp2EoEgcZwyB5LCLzNtcdkNt6sxeEtSosVSlKJLa7JPzAu91f6OPq8R9wQpnk//2Q=="}},"cover":{"cover":{"type":"image","data":{"uuid":"53b847de-330e-5c18-aabc-a53f70cca839","width":1328,"height":1328,"size":556813,"type":"png","color":"c1c4e3","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAACAUH/8QAJxAAAgECBAQHAAAAAAAAAAAAAQIDBAUABhExEhMhUSQyNEFDcdL/xAAWAQEBAQAAAAAAAAAAAAAAAAAFBAb/xAAdEQEAAgICAwAAAAAAAAAAAAABAAMEEQIxEjJR/9oADAMBAAIRAxEAPwBcWfK1BDKKqC5UzwIRxjmrxaHY9/bbGtszVNakNWRz8gSXlzPkpVCm4T6gaenk/OIW23fURLz5A/lG7XV64o9zq2Uh2IMzEEheh3wvaEDo9ianFcrjyk8fU+UfK3b7xMhuJnU//9k="}},"cover_y":63},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 29 ноября 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":6170365,"userId":4237909,"count":0,"shareImage":"https://api.vc.ru/achievements/share/6170365"}],"lastModificationDate":1764905263,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":true,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1767177,"customUri":null,"subsiteId":411234,"title":"Что общего между Telegram-ботами, турникетами и светофорами: конечные автоматы на практике","date":1737539864,"dateModified":1737539932,"blocks":[{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"#разработка #боты #javascript #typescript #python #nocode
"}},{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"Представьте обычное утро в метро. Вы подходите к турникету, прикладываете карту, и... турникет решает, пропустить вас или нет. В этот момент происходит маленькое чудо автоматизации – турникет действует как конечный автомат, простая но эффективная система принятия решений.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В этой статье я хочу вам рассказать и показать на простом примере что это такое и как их можно применять в своих проектах. Больше интересных заметок о разработке и программировании читайте в моем Telegram-блоге «Код без тайн».
"}},{"type":"person","cover":false,"hidden":false,"anchor":"","data":{"image":{"type":"image","data":{"uuid":"ceb832ff-62d3-565d-8fc3-1f1a200855be","width":720,"height":720,"size":234708,"type":"png","color":"b29d8a","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwUI/8QAJRAAAQMDAwQDAQAAAAAAAAAAAQIDBAUGEQASEwcUITFBUWGR/8QAGAEAAgMAAAAAAAAAAAAAAAAABAYAAwf/xAAiEQEAAgEDAwUAAAAAAAAAAAABAgMAITFBBCJRERITFJH/2gAMAwEAAhEDEQA/AEyLKaqFXiP8qWoshtruosNW1h1xaBv3p2nkBSU4zg/3S1KaTiGzjFXTCVM5O5k57rbEgvOQm4dASiOotJCpmwgJOBlOfB8evjRX1rnUi/jgfy1nOZE6aXBXxTa22K5UAlMxCgnuV4BTIwk+/YAAH0ANaDPpqT2dhoIaGLxdYku519Oce7ptm213PV1Kt6mlRnyCSYjeSeRX5qUX2xqiEnY5fGU2VVs1Ym/jP//Z"}},"title":"Алексей Иванов","description":"Фулстек веб-разработчик"}},{"type":"delimiter","cover":false,"hidden":false,"anchor":"","data":{"type":"default"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Конечные автоматы в повседневной жизни"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Конечный автомат – это система, которая может находиться в одном из нескольких состояний и переходить между ними по определённым правилам. Звучит сложно? Давайте разберем на примере того же турникета.
"}},{"type":"media","cover":true,"hidden":false,"anchor":"","data":{"items":[{"title":"Источник: пресс-служба Московского метрополитена","image":{"type":"image","data":{"uuid":"dcf09680-dcb0-5cdc-b4a6-e2138343052a","width":1280,"height":960,"size":158894,"type":"jpg","color":"403b36","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAKAAoDASEAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwUG/8QAIxAAAgIBAgYDAAAAAAAAAAAAAQIDEQQAEgUHExQhMQYjkf/EABYBAQEBAAAAAAAAAAAAAAAAAAQCA//EABwRAAMAAQUAAAAAAAAAAAAAAAABAhEDEiFBUf/aAAwDAQACEQMRAD8AuR8w+INw1J+6RcpEKCPZYvd7u687R+6xGVzejxsmXH7iJuk5S+ld0SPd+dYu93CEKHPaYbYeXlH4XKDkzEFCx+w+T7vRbO7vPI7uzMzEkk2Sb1UhtN5dZ9P/2Q=="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"У него есть два состояния: проход либо закрыт, либо открыт.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Каждое состояние четко определено на схеме автомата, и переходы между ними происходят по конкретным правилам – приложили карту, проверили баланс, открыли проход или показали ошибку. В случае турникета возможно всего два события — можно либо попытаться пройти через него (даже в случае, если он закрыт), а можно сначала оплатить. На схеме эти правила можно представить вот так:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Схема конечного автомата для турникета. Источник: Chetvorno","image":{"type":"image","data":{"uuid":"dc545dfc-6a27-5a96-90c1-2643e0ebeac6","width":1120,"height":471,"size":61167,"type":"png","color":"ecded0","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAABAYHCf/EACcQAAIBAwEGBwAAAAAAAAAAAAECAwQFEQAGEhMhUXEHFBVBVZPR/8QAFgEBAQEAAAAAAAAAAAAAAAAAAwAB/8QAGREAAgMBAAAAAAAAAAAAAAAAAAECAyER/9oADAMBAAIRAxEAPwDTawQpU2yO61XFpqqpQSVC8ZlEb+43ScLjoR31i61pr4ngnvtr4hq7LHYY2UEhW8nK2R1yDg9xy0LsmnkRlCtrZFGlpKWU70tNE5xjLIDy04AJ6LZ/iaP6E/NRH//Z"}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"От турникетов к чат-ботам"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Теперь давайте представим, что вместо турникета у нас Telegram-бот для записи к парикмахеру. Принцип тот же – бот находится в определенном состоянии и ждет действий пользователя. Например:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"from enum import Enum\n\nclass BotState(Enum):\n WAITING = \"ожидание\"\n CHOOSING_SERVICE = \"выбор услуги\"\n SELECTING_DATE = \"выбор даты\"\n CONFIRMING = \"подтверждение\"\n \nclass HairdresserBot:\n def __init__(self):\n self.state = BotState.WAITING\n self.user_data = {}\n \n # Обработка вхоядщего сообщения\n def handle_message(self, message: str) -> str:\n if self.state == BotState.WAITING:\n return self.start_booking()\n elif self.state == BotState.CHOOSING_SERVICE:\n return self.handle_service_choice(message)\n # ... остальные состояния","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В этом простом примере бот – это обычный конечный автомат с четырьмя состояниями. Каждое состояние определяет, как бот будет реагировать на сообщения пользователя.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Почему нельзя просто написать код?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Конечные автоматы – это не просто красивая теория. Они помогают:
"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Структурировать логику бота","Упростить отладку и тестирование","Легко добавлять новые функции","Контролировать пользовательский опыт"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Благодаря такому подходу можно упростить реализацию сложной логики в боте, особенно когда нужно учитывать много переплетающихся между собой состояний и условий. Без использования конечного автомата в этом случае у вас получится спагетти из if-then-else:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"def handle_message(message):\n if not user_data:\n if message == \"/start\":\n return \"Привет! Выберите услугу\"\n else:\n return \"Используйте /start\"\n elif user_data.get('service') is None:\n if message in services:\n user_data['service'] = message\n return \"Выберите дату\"\n else:\n return \"Неверная услуга\"\n elif user_data.get('date') is None:\n if is_valid_date(message):\n # и так далее...","lang":""}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Реализация сложной логики в вашем бота без конечных автоматов. Источник","image":{"type":"image","data":{"uuid":"0f2b39b9-9d89-5220-b15c-b7c809b15d26","width":600,"height":302,"size":37504,"type":"webp","color":"cbc0b8","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAACAIF/8QAJRAAAgECBQMFAAAAAAAAAAAAAQIDBAUABhESIQgx0RMWQVFx/8QAGQEAAgMBAAAAAAAAAAAAAAAAAwQAAQIF/8QAHBEAAgMAAwEAAAAAAAAAAAAAAQIAAxEEEpGh/9oADAMBAAIRAxEAPwBE0vXneatIZTkelpllhjmQSSs3qKx+O208jg64Ve25W0KCp9nQr4lbHo7Yfk1X6+7HC7Qz5NnWSMlXG9xow78bPvBFt0bky3AdSRo9igewWJhq1loD+0yeMGiMn23l08mwW0k8kmlj8YkvTP/Z"}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Как использовать конечные автоматы для разработки"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Существует 3 варианта, как можно использовать такой подход при разработке своего бота:
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"1. Написать собственную маленькую реализацию конечного автомата
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Этот подход рекомендуется, если вы сами занимаетесь программированием и хотите получить новый инструмент в свой арсенал. Много времени потратить не придется, потому что реализовать конечный автомат на самом деле легко — основное время вы потратите на изучение теории.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Зато в результате вы поймете как применять на практике мощную абстракцию, которая используется повсеместно: от интерфейсов на веб-сайтах, до микроволновки на вашей кухне.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2. Воспользоваться готовыми библиотеками
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Такой вариант подходит, если вам нужно создавать и поддерживать много сценариев, реализованных на базе конечных автоматов.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Некоторые библиотеки, например, xstate, имеют графический редактор и инструменты для создания и моделирования автоматов отдельно от вашего кода. Это может быть удобно, если у вас несколько человек в команде и не все занимаются программированием.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Например, продакт-менеджер или другой член вашей команды может скорректировать сценарий, а вам как разработчику останется всего лишь обновить код.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"3. Использовать no-code конструкторы
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Все no-code конструкторы для создания ботов используют интерфейс, похожий на тот, который используется для создания конечных автоматов. На самом деле, если вы ранее создавали ботов в подобных конструкторах, то материал в данной статье может показаться вам знакомым.
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Пример создания бота в no-code конструкторе. Источник","image":{"type":"image","data":{"uuid":"6a285f2f-55a1-5715-a8d2-ba8fc65281d0","width":696,"height":442,"size":28390,"type":"png","color":"f2f8f9","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwQJ/8QAJBAAAgIBAwIHAAAAAAAAAAAAAQIDBBEABRMxYRIhIjJBQnH/xAAXAQEBAQEAAAAAAAAAAAAAAAADAgQF/8QAIREAAgIBAwUBAAAAAAAAAAAAAQIAEQMEEqEiUWGBsfD/2gAMAwEAAhEDEQA/ANIN6qx8m3xiPAjfCh4ZpyBkfZT5frZ11tMzbWPfyF4I4ExZlW1B+E/vcrl3qWOV4xQLBGKg8gGcHrohgUi93Eo6ijVRd2qVbM9ZrFaKUo2VLoG8J7Z6aPHkfGCEJFxHRXPULgTInM/oX3H476sE1BYC5//Z"}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Дело в том, что принципы, которые используются в подобных конструкторах идентичные: вы создаете состояния (обычно это действия вроде отправки ответов или создания записи в таблице). Далее указываются триггеры для срабатывания состояний — какое состояние идет за каким и при каких условиях это событие срабатывает.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Подводя итог"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Какой бы способ вы ни выбрали, на мой взгляд важно понимать то, как устроена изнутри система, которую вы разрабатываете. Даже если вы не являетесь программистом, важно понимать фундаментальные принципы, которые лежат в системах, которыми вы пользуетесь. Это дает большую свободу при взаимодействии с такими системами, а также повышает ваш уровень как специалиста.
"}},{"type":"delimiter","cover":false,"hidden":false,"anchor":"","data":{"type":"default"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Если вам понравилась эта статья, буду благодарен, если поставите лайк 🔥 и напишите комментарий — так я пойму, что на подобные темы стоит писать больше.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Также я веду свой Telegram-блог «Код без тайн», в котором пишу о веб-разработке, информатике и других технологиях:
"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Ft.me%2Fcodeunlocked&postId=1767177","title":"Код без тайн","description":"Пишу о веб-разработке, информатике и технологиях, которые меня вдохновляют Связаться со мной: @MajorLettuce","image":{"type":"image","data":{"uuid":"0c5ec522-6456-5837-a7a9-efa6a12645d2","width":180,"height":180,"size":4016,"type":"png","color":"26a5e4","hash":"","external_service":[]}},"v":1,"hostname":"t.me"}}}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":1,"favorites":2,"reposts":0,"views":1157,"hits":265,"reads":null,"online":0},"dateFavorite":0,"hitsCount":265,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1767177-chto-obshego-mezhdu-telegram-botami-turniketami-i-svetoforami-konechnye-avtomaty-na-praktike","author":{"id":411234,"name":"Алексей Иванов","nickname":null,"description":"Пишу о веб-разработке, информатике и технологиях, которые меня вдохновляют t.me/codeunlocked","uri":"","avatar":{"type":"image","data":{"uuid":"b1684aaf-7e32-5456-b404-f0311174c2d2","width":615,"height":615,"size":300361,"type":"png","color":"b07ce6","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwIG/8QAJBAAAQIFAwUBAAAAAAAAAAAAAQIDAAQFERIGITEHE0FRYXH/xAAWAQEBAQAAAAAAAAAAAAAAAAAHBgP/xAAkEQABAwMEAQUAAAAAAAAAAAABAAIDBBESBiExQQUUQlFhcf/aAAwDAQACEQMRAD8A3TLCFOhyYU6UWOAbdwCT5yHbVkN/Y4+3hE1V5rytNqmkoaVzhG7HYe67jkT+D54tdW7IQ+MvJ4UwuLBH3T+q1OZ07q9UzUpp0sVdCGit5Si2klNwm52H5ANVSPOqoJLnLffvvtTsM0npZTkb5Dv7CQBwIeG8BUI4X//Z"}},"cover":{"cover":{"type":"image","data":{"uuid":"72ff990a-0165-5b54-aef8-918d3219d480","width":3951,"height":2222,"size":1200866,"type":"jpg","color":"80b7cd","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAKAAoDASEAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAQQGCP/EACEQAAIBAwMFAAAAAAAAAAAAAAECAwAEEQUGIQcUMWFx/8QAFgEBAQEAAAAAAAAAAAAAAAAABgMF/8QAHREAAQQDAQEAAAAAAAAAAAAAAgABAwQFIUEGEf/aAAwDAQACEQMRAD8AYgs93XOnlbnQ07kjAkZlwPZGcZ+VMS9Nt4zSvK73AZ2LEC4wATzSTL+knAhGBtfOonisPXcSKYtrQD8SADgUT5NZN5tCrVer/9k="}},"cover_y":25},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":5015307,"userId":411234,"count":0,"shareImage":"https://api.vc.ru/achievements/share/5015307"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":1391220,"userId":411234,"count":0,"shareImage":"https://api.vc.ru/achievements/share/1391220"},{"title":"5 лет на vc.ru","code":"registration_5_years","description":"Провёл 5 лет вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"a9140d54-73b8-5f40-afa8-449fbaafd42b","formats":{"glb":"https://static.vc.ru/achievements/whale.glb","usdz":"https://static.vc.ru/achievements/whale.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.533203125,"textY":0.658203125,"logoX":0.533203125,"logoY":0.77734375,"logoXNoText":0.4375,"logoYNoText":0.66015625},"id":164512,"userId":411234,"count":0,"shareImage":"https://api.vc.ru/achievements/share/164512"}],"lastModificationDate":1764905263,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":true,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":6},{"id":24,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1646153,"customUri":null,"subsiteId":411234,"title":"Кейс модификации Tilda: разные слайдеры с фото и описание для каждого варианта в карточке товара","date":1737095909,"dateModified":1737100806,"blocks":[{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"#кейс #модификация #тильда #tilda #javascript #веб #web #frontend
"}},{"type":"media","cover":true,"hidden":false,"anchor":"","data":{"items":[{"title":"Слайдеры с фото и описание меняются в зависимости от выбранной комплектации","image":{"type":"image","data":{"uuid":"680b891e-f4f9-5bb6-bed3-6adfd8b42463","width":1172,"height":704,"size":523870,"type":"gif","color":"ecddc0","hash":"","external_service":[],"duration":0,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"Сегодня расскажу о разработке модификации для расширения стандартного функционала конструктора Tilda — динамическое переключение слайдеров с фото и текста описания при выборе разных комбинаций товара. Пример того, как работает данная модификация можно посмотреть на странице магазина Медведь Электро.
"}},{"type":"person","cover":false,"hidden":false,"anchor":"","data":{"image":{"type":"image","data":{"uuid":"ceb832ff-62d3-565d-8fc3-1f1a200855be","width":720,"height":720,"size":234708,"type":"png","color":"b29d8a","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwUI/8QAJRAAAQMDAwQDAQAAAAAAAAAAAQIDBAUGEQASEwcUITFBUWGR/8QAGAEAAgMAAAAAAAAAAAAAAAAABAYAAwf/xAAiEQEAAgEDAwUAAAAAAAAAAAABAgMAITFBBCJRERITFJH/2gAMAwEAAhEDEQA/AEyLKaqFXiP8qWoshtruosNW1h1xaBv3p2nkBSU4zg/3S1KaTiGzjFXTCVM5O5k57rbEgvOQm4dASiOotJCpmwgJOBlOfB8evjRX1rnUi/jgfy1nOZE6aXBXxTa22K5UAlMxCgnuV4BTIwk+/YAAH0ANaDPpqT2dhoIaGLxdYku519Oce7ptm213PV1Kt6mlRnyCSYjeSeRX5qUX2xqiEnY5fGU2VVs1Ym/jP//Z"}},"title":"Алексей Иванов","description":"Фулстек веб-разработчик"}},{"type":"delimiter","cover":false,"hidden":false,"anchor":"","data":{"type":"default"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Содержание"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Постановка задачи","Замена активного слайдера с помощью кода","Ассоциирование фото и описания с вариантом товара","Конечный результат"],"type":"OL"}},{"type":"header","cover":false,"hidden":false,"anchor":"1","data":{"style":"h2","text":"1. Постановка задачи"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Дано
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"У клиента есть сайт на Tilda с карточкой товара, у которого есть несколько вариантов комплектации и цвета. Необходимо показывать разные фото и описание для всех комбинаций комплектации и цветов.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Проблема
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В Tilda есть возможность добавить фото для вариантов товара, однако в этом случае на странице отображаются сразу все фото для всех вариантов. Кроме того, необходимо также менять текст описания товара, что невозможно стандартными средствами.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Решение
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Было принято решение реализовать необходимый функционал максимально близко к стандартному функционалу Tilda. Каждый раз при переключении варианта переиспользовать слайдеры, созданные в отдельных блоках. Для пользователя такой вариант выглядит неотличимо от стандартного функционала конструктора Tilda.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"2","data":{"style":"h2","text":"2. Замена активного слайдера с помощью кода"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Чтобы изменять фото и описания, логично, что их надо где-то определить. Поскольку в административной панели Tilda это сделать невозможно, воспользуемся небольшой хитростью: добавим необходимые нам фото и описание в блок GL21 \"Галерея с текстовой информацией\". Как будет видно дальше — это также поможет при повторной инициализации слайдера, поскольку структура HTML-кода для слайдера у блоков ST200 и GL21 идентичная.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Создадим два блока GL21 с разными фото и описанием:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Блок GL21 с первым слайдером и тексто описания — #rec823142810 в кодеДалее напишем небольшой скрипт, который бы перемещал слайдер из нужного нам блока GL21 в блок ST200. Для простоты, сделаем переключение по нажатию на кнопки. Код представлен ниже:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$(document).ready(() => {\n const block = $('#rec823147906');\n const sliders = ['#rec823142810', '#rec823163609'].map(id => $(id).find('.t-slds'));\n\n // Установка слайдера: копирование, очистка атрибутов и инициализация\n const setSlider = (index) => {\n const slider = sliders[index].clone()\n .find('.t-slds__items-wrapper')\n .removeAttr('data-slider-initialized data-swiper-initialized').end();\n block.find('.t744__col').first().empty().append(slider);\n t_sldsInit('823147906'); t_lazyload_update?.(); // Инициализация Tilda\n };\n\n // Привязка кнопок к переключению слайдеров\n $('#rec823164226 .t142A__btn').on('click', function () {\n setSlider($(this).index());\n });\n\n setSlider(0); // Установка начального слайдера\n});","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Важно заметить, что недостаточно просто клонировать элемент слайдера и вставить его внутрь блока ST200. Необходимо также сделать его вновь интерактивным. Для этого надо выполнить код, который Tilda выполняет при загрузке страницы, а именно — функцию t_sldsInit. Я нашел эту функцию через отладчик Firefox. На самом деле, у Tilda множество функций открыты для выполнения в любой момент, что значительно облегчает разработку подобных модификаций. Про JavaScript API Tilda я уже упоминал ранее в одном из своих прошлых постов:
"}},{"type":"osnovaEmbed","cover":false,"hidden":false,"anchor":"","data":{"osnovaEmbed":{"type":"osnovaEmbed","data":{"original_id":1650163,"isNotAvailable":false,"title":"Создание нестандартных модификаций с помощью JavaScript API Tilda","description":"В очередной раз я погрузился в недры исходников Тильды при разработке казалось бы простой модификации.","isEditorial":false,"image":{"type":"image","data":{"uuid":"2157aaa3-16b3-5508-b1a0-fcc6628a164b","width":800,"height":599,"size":583684,"type":"png","color":"d3d8e4","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABQcI/8QAIxAAAgEDAwQDAAAAAAAAAAAAAQIDBAURAAYhEhNBkRRRof/EABgBAAIDAAAAAAAAAAAAAAAAAAABAgQF/8QAGREBAAMBAQAAAAAAAAAAAAAAAAEREiEx/9oADAMBAAIRAxEAPwDXNg3VerNtKae6Q/KFFMIFqIJ1zImMFuT4P561o5V/Z4Jjv98mjWYb6pUDgMFdcsufBP3pZO1Jaz2lqA0jWukMHfVu0YF6M9Q5xjGi5ojK2+gVQq0NOABgARLwPWo3If/Z"}},"url":"https://vc.ru/dev/1650163-sozdanie-nestandartnyh-modifikacii-s-pomoshyu-javascript-api-tilda","blocks":[{"type":"media","cover":true,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"2157aaa3-16b3-5508-b1a0-fcc6628a164b","width":800,"height":599,"size":583684,"type":"png","color":"d3d8e4","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABQcI/8QAIxAAAgEDAwQDAAAAAAAAAAAAAQIDBAURAAYhEhNBkRRRof/EABgBAAIDAAAAAAAAAAAAAAAAAAABAgQF/8QAGREBAAMBAQAAAAAAAAAAAAAAAAEREiEx/9oADAMBAAIRAxEAPwDXNg3VerNtKae6Q/KFFMIFqIJ1zImMFuT4P561o5V/Z4Jjv98mjWYb6pUDgMFdcsufBP3pZO1Jaz2lqA0jWukMHfVu0YF6M9Q5xjGi5ojK2+gVQq0NOABgARLwPWo3If/Z"}}}]}},{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"В очередной раз я погрузился в недры исходников Тильды при разработке казалось бы простой модификации.
"}}],"date":1731428408,"author":{"id":411234,"name":"Алексей Иванов","avatar":{"type":"image","data":{"uuid":"b1684aaf-7e32-5456-b404-f0311174c2d2","width":615,"height":615,"size":300361,"type":"png","color":"b07ce6","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwIG/8QAJBAAAQIFAwUBAAAAAAAAAAAAAQIDAAQFERIGITEHE0FRYXH/xAAWAQEBAQAAAAAAAAAAAAAAAAAHBgP/xAAkEQABAwMEAQUAAAAAAAAAAAABAAIDBBESBiExQQUUQlFhcf/aAAwDAQACEQMRAD8A3TLCFOhyYU6UWOAbdwCT5yHbVkN/Y4+3hE1V5rytNqmkoaVzhG7HYe67jkT+D54tdW7IQ+MvJ4UwuLBH3T+q1OZ07q9UzUpp0sVdCGit5Si2klNwm52H5ANVSPOqoJLnLffvvtTsM0npZTkb5Dv7CQBwIeG8BUI4X//Z"}}},"subsite":{"id":235819,"name":"Разработка","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}}},"likes":0,"comments":3,"isBlur":false,"warningFromEditor":null,"warningFromEditorTitle":null}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В результате получим интересующий нас функционал: при вызове функции setSlider будет склонирован элемент слайдера из блока GL21, \"очищен\" от атрибутов и дополнительных слайдов, после чего он будет вставлен вместо текущего слайдера в блоке ST200. Чтобы сделать его вновь интерактивным будет вызвана функция t_sldsInit JS API Tilda, которая инициализует необходимую логику для переключения слайдов.
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Переключение слайдера внутри карточки товара ST200 по нажатию кнопки","image":{"type":"image","data":{"uuid":"61be9816-4bdb-5133-8268-3abca0b1f2fa","width":1208,"height":752,"size":1633674,"type":"gif","color":"d2cdcc","hash":"","external_service":[],"duration":13.7,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Теперь, когда сделана основная сложная работа по программной замене слайдера остается связать эту логику с выбором варианта товара.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"3","data":{"style":"h2","text":"3. Ассоциирование фото и описания с вариантом товара"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Теперь, когда у нас есть базовый функционал для переключения слайдеров, необходимо связать его с системой вариантов товара в Tilda. Для начала разберемся, как работают варианты товара в конструкторе.
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Варианты комплектации и цвета, заданные в редакторе товара TildaВ административной панели Tilda можно задать несколько характеристик товара и их возможные значения. В нашем случае это \"комплектация\" и \"цвет\". Tilda автоматически создаст все возможные комбинации этих характеристик в карточке товара.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В отличие от решения выше, теперь будет использоваться более гибкий подход — поиск нужного блока GL21 по его названию, которое содержит информацию о варианте товара. Это избавляет нас от необходимости поддерживать специальный объект с маппингом вариантов на индексы слайдеров.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Структура названия блока GL21 выглядит следующим образом:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"<опция1>: <значение1>, <значение2>; <опция2>: <значение3>","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Например:
"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["\"комплектация: Задний привод 500Вт; цвет: серый\"","\"комплектация: Передний привод 750Вт; цвет: черный, серый\""],"type":"UL"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"/**\n * Нахождение блока GL21 для варианта товара\n * @param {Object} variant - Характеристики товара (ключ: значение)\n * @returns {Element} - Соответствующий блок\n */\nconst getVariantBlock = (variant) => {\n const blocks = document.querySelectorAll('.uc-product-info');\n\n for (const block of blocks) {\n const text = block.querySelector('.js-product-name').innerText.toLowerCase();\n let includesAllText = Object.keys(variant).every(key => {\n const chunk = text.split(';').find(item => item.includes(key.toLowerCase()));\n return chunk?.split(':')[1]?.split(',').some(\n value => value.includes(variant[key].toLowerCase())\n );\n });\n if (includesAllText) return block;\n }\n};","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Данная функция ищет блок GL21, название которого содержит все выбранные значения характеристик товара. Алгоритм работы следующий:
"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Проходим по всем блокам GL21 на странице;","Для каждого блока разбиваем его название на части по точке с запятой;","Для каждой характеристики товара: находим часть названия, содержащую имя характеристики и проверяем, содержится ли выбранное значение в списке значений после двоеточия;","Если все характеристики найдены и их значения совпадают — возвращаем этот блок."],"type":"OL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"После того как нужный блок найден, мы можем использовать его для копирования слайдера с фотографиями и обновления текста описания товара. Логика замены слайдера схожа с кодом из предыдущего параграфа.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Создадим блоки GL21 для каждого варианта:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Вариант: комплектация: Задний привод 500Вт; цвет: серый","image":{"type":"image","data":{"uuid":"dedc9af6-a91b-53b8-b0fb-590ca635d17b","width":1169,"height":646,"size":150666,"type":"png","color":"e3e3e3","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABgUI/8QAJhAAAgEDAwIHAQAAAAAAAAAAAQIDBAUGABExByETFBYiMkFDYf/EABcBAAMBAAAAAAAAAAAAAAAAAAABBAX/xAAbEQACAQUAAAAAAAAAAAAAAAAAARECAwQFkf/aAAwDAQACEQMRAD8A250/67z3nMKDH7rhN3tNflFqpK+BawskUdQySlonU7tGxSJmGykdtid+FICeowzC3qJWfErYzF2JY0y7k7860Ftc1KFdq6QPV4Tcu1Twm34n1tQXDc+ahNDFHP8AoiPWIrqG5AYEgjgg99Z5eJp2bx5Pcfmfv+6YH//Z"}}}]}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Вариант: комплектация: Задний привод 500Вт; цвет: черный","image":{"type":"image","data":{"uuid":"591555f7-e469-5396-8715-01dee1474009","width":1166,"height":640,"size":145037,"type":"png","color":"e0e0e0","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwYI/8QAJBAAAgEDAwQDAQAAAAAAAAAAAQIDBAUGERIhAAciMRMyQbH/xAAXAQADAQAAAAAAAAAAAAAAAAABBAUA/8QAGxEAAgEFAAAAAAAAAAAAAAAAAAECAwQFEZH/2gAMAwEAAhEDEQA/ANs9q+82WZBmiYxlWA1Vmu14s1LckgnqG2wT7GMqOpBKDQL5KNu7x5PJASrqcNwp6mV5MQtLszsWY0iEk6+zx1QWUvYrSqy6xB4yyb26UeIPO39/vtZTUdVV3qvnmavtUJkkqXZjG0g3LqTrtP6PR6njwszk/PJyfuf70TH/2Q=="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"и так далее для всех возможных комбинаций вариантов товара...
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h3","text":"Смена слайдера при выборе варианта"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Чтобы все работало автоматически, при выборе вариантов товара добавляем обработчики события change на все селекты с вариантами:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// Ждем загрузки карточки товара и элементов с вариантами\nconst block = await waitForElement('.uc-product-card');\nawait waitForChildren(block, '.js-product-edition-option');\nconst options = block.querySelectorAll('.js-product-edition-option');\n\n/**\n * Отображает текущий вариант товара и обновляет слайдер\n */\nconst render = () => {\n const variant = Array.from(options).reduce((acc, option) => {\n const el = option.querySelector('.js-product-edition-option-variants');\n acc[option.dataset.editionOptionId] = el.value;\n return acc;\n }, {});\n\n const variantBlock = getVariantBlock(variant);\n if (variantBlock) {\n block.querySelector('[field=\"descr\"]')?.innerHTML = \n variantBlock.querySelector('[field=\"descr\"]')?.innerHTML || '';\n updateSlider(variantBlock); // Обновляем слайдер\n }\n};\n\n// Навешиваем обработчики на изменение вариантов\noptions.forEach(option => {\n option.querySelector('.js-product-edition-option-variants')\n .addEventListener('change', render);\n});\n\nrender(); // Первоначальная отрисовка","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Стоит отметить некоторые важные особенности реализации:
"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Поддержка множественных значений: одна характеристика может иметь несколько значений, перечисленных через запятую. Например, один блок GL21 может содержать фотографии, подходящие для нескольких цветов товара;","Регистронезависимый поиск: поиск значений производится без учета регистра, что делает систему более устойчивой к опечаткам в названиях блоков."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Для контент-менеджера такой подход максимально удобен:
"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Не надо запоминать или искать ID блоков;","Достаточно просто правильно назвать блок GL21 в соответствии с форматом;","Можно создавать общие наборы фотографий для нескольких вариантов товара."],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"4","data":{"style":"h2","text":"4. Конечный результат"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Слайдеры с фото и описание меняются в зависимости от выбранной комплектации","image":{"type":"image","data":{"uuid":"14271997-79eb-5816-8eae-895147e4ac7e","width":1172,"height":704,"size":536608,"type":"gif","color":"ecddbf","hash":"","external_service":[],"duration":16.733333,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Пример того, как работает данная модификация можно посмотреть на странице магазина Медведь Электро.
"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fmedved-electro.ru%2Ffold%23kupiti&postId=1646153","title":"Электровелосипеды Медведь Fold","description":"Медведь Fold это специальная серия надежных электровелосипедов для любых условий","image":{"type":"image","data":{"uuid":"9bbbb801-dc95-5f7d-bc68-9c6206e48290","width":180,"height":180,"size":4324,"type":"png","color":"3c3c3c","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/wAALCAAKAAoBAREA/8QAFwAAAwEAAAAAAAAAAAAAAAAAAgQFCP/EACQQAAEDBAEDBQAAAAAAAAAAAAECAwQABQcRIgYhQRITMlGR/9oACAEBAAA/AJOOsY4Ku0LFs/qhbTE+RGbXc7YXlBV7VJmOsMFPfj7akbc1rhqsyXllqPd50dhIS21JdQhI8JCiAPylQ88ChQdXtv4H1Hj57fVCSSdk7Jr/2Q=="}},"v":1,"hostname":"medved-electro.ru"}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Таким образом, была успешно решена задача отображения разных наборов фотографий и текста описаня для различных вариантов товара за счет расширения стандартных возможностей Tilda.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"При этом для пользователя весь процесс выглядит абсолютно естественно — будто это стандартный функционал Tilda. Модификация органично встраивается в существующий интерфейс и не требует никаких дополнительных действий от пользователя.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Для контент-менеджера решение также получилось максимально удобным. Не надо запоминать никакие технические детали или ID блоков — достаточно просто правильно назвать блок GL21, указав в его названии комплектацию и цвет.
"}},{"type":"delimiter","cover":false,"hidden":false,"anchor":"","data":{"type":"default"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Если вам понравилась статья — поставьте лайк или напишите комментарий. Так я пойму, что подобные посты с кейсами интересны и буду писать больше таких статьей.
"}},{"type":"quote","cover":false,"hidden":false,"anchor":"","data":{"text":"Если вам нужна разработка уникальной модификации или интеграция Tilda со сторонним сервисом — мое портфолио и контакты доступны по ссылке: codly.cc
","subline1":""}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fcodly.cc&postId=1646153","title":"Codly — интеграция Tilda с любыми сервисами под ключ","description":"Разработка модификаций для Tilda, интеграция по API с CRM, ERP, платежными системами, системами складского учета","image":{"type":"image","data":{"uuid":"https://leonardo.osnova.io/ico/codly.cc","width":0,"height":0,"size":0,"type":"jpg","color":"","hash":"","external_service":[]}},"v":1,"hostname":"codly.cc"}}}},{"type":"quote","cover":false,"hidden":false,"anchor":"","data":{"text":"А в своем блоге «Код без тайн» я периодически пишу о веб-разработке, информатике и других сферах, которые меня вдохновляют (искусственный интеллект, дизайн и многое другое).
","subline1":""}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":3,"favorites":4,"reposts":0,"views":2143,"hits":1550,"reads":null,"online":0},"dateFavorite":0,"hitsCount":1550,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":true,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1646153-keis-modifikacii-tilda-raznye-slaidery-s-foto-i-opisanie-dlya-kazhdogo-varianta-v-kartochke-tovara","author":{"id":411234,"name":"Алексей Иванов","nickname":null,"description":"Пишу о веб-разработке, информатике и технологиях, которые меня вдохновляют t.me/codeunlocked","uri":"","avatar":{"type":"image","data":{"uuid":"b1684aaf-7e32-5456-b404-f0311174c2d2","width":615,"height":615,"size":300361,"type":"png","color":"b07ce6","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwIG/8QAJBAAAQIFAwUBAAAAAAAAAAAAAQIDAAQFERIGITEHE0FRYXH/xAAWAQEBAQAAAAAAAAAAAAAAAAAHBgP/xAAkEQABAwMEAQUAAAAAAAAAAAABAAIDBBESBiExQQUUQlFhcf/aAAwDAQACEQMRAD8A3TLCFOhyYU6UWOAbdwCT5yHbVkN/Y4+3hE1V5rytNqmkoaVzhG7HYe67jkT+D54tdW7IQ+MvJ4UwuLBH3T+q1OZ07q9UzUpp0sVdCGit5Si2klNwm52H5ANVSPOqoJLnLffvvtTsM0npZTkb5Dv7CQBwIeG8BUI4X//Z"}},"cover":{"cover":{"type":"image","data":{"uuid":"72ff990a-0165-5b54-aef8-918d3219d480","width":3951,"height":2222,"size":1200866,"type":"jpg","color":"80b7cd","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAKAAoDASEAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAQQGCP/EACEQAAIBAwMFAAAAAAAAAAAAAAECAwAEEQUGIQcUMWFx/8QAFgEBAQEAAAAAAAAAAAAAAAAABgMF/8QAHREAAQQDAQEAAAAAAAAAAAAAAgABAwQFIUEGEf/aAAwDAQACEQMRAD8AYgs93XOnlbnQ07kjAkZlwPZGcZ+VMS9Nt4zSvK73AZ2LEC4wATzSTL+knAhGBtfOonisPXcSKYtrQD8SADgUT5NZN5tCrVer/9k="}},"cover_y":25},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":5015307,"userId":411234,"count":0,"shareImage":"https://api.vc.ru/achievements/share/5015307"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":1391220,"userId":411234,"count":0,"shareImage":"https://api.vc.ru/achievements/share/1391220"},{"title":"5 лет на vc.ru","code":"registration_5_years","description":"Провёл 5 лет вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"a9140d54-73b8-5f40-afa8-449fbaafd42b","formats":{"glb":"https://static.vc.ru/achievements/whale.glb","usdz":"https://static.vc.ru/achievements/whale.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.533203125,"textY":0.658203125,"logoX":0.533203125,"logoY":0.77734375,"logoXNoText":0.4375,"logoYNoText":0.66015625},"id":164512,"userId":411234,"count":0,"shareImage":"https://api.vc.ru/achievements/share/164512"}],"lastModificationDate":1764905263,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":true,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":15},{"id":2,"count":1},{"id":19,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1717502,"customUri":null,"subsiteId":4237909,"title":"Календарь в телеграм боте на Node.js за 5 минут: готовое решение.","date":1734597340,"dateModified":1736534820,"blocks":[{"type":"media","cover":true,"hidden":false,"anchor":"","data":{"items":[{"title":"Создание календаря c датами","image":{"type":"image","data":{"uuid":"cfd10e99-b9c4-5f43-aa1f-ad88687621f9","width":674,"height":448,"size":10423,"type":"png","color":"223139","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAOAA4AAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwII/8QAIBAAAgEEAQUAAAAAAAAAAAAAAQIDABIhMWETIjKxwv/EABcBAAMBAAAAAAAAAAAAAAAAAAABBQL/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDFZjmWFor2sLXEWnYu44qwmDkedJGQMe1iNGgKZmzk6b7pNAmY9V8nyPumb//Z"}}}]}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Создание календаря c выбранной датой","image":{"type":"image","data":{"uuid":"4f02ff6e-d79b-5078-81c6-6fff9340c681","width":664,"height":442,"size":11646,"type":"png","color":"223139","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAOAA4AAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABAMI/8QAIRAAAgEDBAMBAAAAAAAAAAAAAQIDABIhBBEisTI0UXL/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQX/xAAWEQEBAQAAAAAAAAAAAAAAAAAAESH/2gAMAwEAAhEDEQA/AMWGN10zRXvsXutsxgNnf7itfazEpJNQkjIHOysQONUIcDlgeLdPUBtR7Ev7buqP/9k="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Разработка Telegram-ботов – это увлекательное занятие, но иногда приходится сталкиваться с рутинными задачами, которые отнимают драгоценное время.
Одна из таких задач – создание календаря для выбора дат. Реализация собственного календаря может занять много времени и ресурсов, особенно если вы работаете с Node.js.
В этой статье я представлю готовое решение, которое позволит вам интегрировать функциональный календарь в ваш бот всего за несколько минут, используя две легкие библиотеки: telegram-bot-calendar-lite для дат и telegram-bot-time-calendar-lite для времени.
1. Убедитесь, что у вас установлен Node.js и npm (или yarn).
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2. Создайте новый проект и инициализируйте его: npm init -y
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"3. Установите библиотеки:
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"* Для выбора дат: npm install telegram-bot-calendar-lite
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"* Для выбора времени: npm install telegram-bot-time-calendar-lite
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Мощный функционал:"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• setDate(date): Устанавливает начальную дату календаря. Параметр date - объект Date. Пример: calendar.setDate(new Date()) выставит текущую дату.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• setMonthNames(names): Позволяет использовать пользовательские названия месяцев. Параметр names - массив строк (12 элементов). Пример: calendar.setMonthNames(['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'])
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• setSelectIcon(icon): Устанавливает иконку для выбранных дат. icon - строка, например, calendar.setSelectIcon('✅').
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• setPrevMonthIcon(icon): Иконка для кнопки \"Предыдущий месяц\".
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• setNextMonthIcon(icon): Иконка для кнопки \"Следующий месяц\".
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• setCloseIcon(icon): Иконка для неактивных дат (например, для дат в прошлом).
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• setSelectDay(selectDay): Настройка иконки для выбранного дня.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• setDatesLock(datesLock): Задает массив дат, которые нельзя выбрать. datesLock - массив объектов Date.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Заключение:"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Эти библиотеки позволяют быстро и легко добавить календарь в ваш Telegram-бот на Node.js. Они предоставляют гибкие настройки и простой API, что значительно ускоряет разработку. Попробуйте это решение сегодня и ускорьте разработку своего Telegram-бота!
"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Ftelegram-bot-calendar-lite&postId=1717502","title":"telegram-bot-calendar-lite","description":"Calendar for telegram bot. Latest version: 1.0.21, last published: 9 days ago. Start using telegram-bot-calendar-lite in your project by running `npm i telegram-bot-calendar-lite`. There are no other projects in the npm registry using telegram-bot-calendar-lite.","image":{"type":"image","data":{"uuid":"1b726a8b-4e1f-5168-a8c5-eca10791f4de","width":230,"height":230,"size":794,"type":"png","color":"ebb3b3","hash":"","external_service":[]}},"v":1,"hostname":"www.npmjs.com"}}}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Ftelegram-bot-time-calendar-lite&postId=1717502","title":"telegram-bot-time-calendar-lite","description":"Calendar time for telegram bot. Latest version: 1.0.0, last published: 8 days ago. Start using telegram-bot-time-calendar-lite in your project by running `npm i telegram-bot-time-calendar-lite`. There are no other projects in the npm registry using telegram-bot-time-calendar-lite.","image":{"type":"image","data":{"uuid":"1b726a8b-4e1f-5168-a8c5-eca10791f4de","width":230,"height":230,"size":794,"type":"png","color":"ebb3b3","hash":"","external_service":[]}},"v":1,"hostname":"www.npmjs.com"}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"#TelegramBot #NodeJS #JavaScript #Calendar #Development #Programming #Bots #API #Tutorial #Coding #TelegramBotAPI #NodeJSDevelopment
"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":3,"reposts":0,"views":113,"hits":1340,"reads":null,"online":0},"dateFavorite":0,"hitsCount":1340,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1717502-kalendar-v-telegram-bote-na-nodejs-za-5-minut-gotovoe-reshenie","author":{"id":4237909,"name":"Михаил Липовка","nickname":null,"description":"От набросков в блокноте до работающего приложения: взгляд разработчика. Здесь вы найдете полезные инструменты и реальные кейсы из мира разработки ПО.","uri":"","avatar":{"type":"image","data":{"uuid":"73eb8acd-f94f-5a9b-a130-909fda9d21ad","width":688,"height":1100,"size":120194,"type":"jpg","color":"34312c","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAKAAoDASEAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABAYH/8QAIhAAAQQCAAcBAAAAAAAAAAAAAQIDBBEABQYhMTNBYXFy/8QAFgEBAQEAAAAAAAAAAAAAAAAABAMF/8QAHREAAgEEAwAAAAAAAAAAAAAAAAECBBIxMlFS8P/aAAwDAQACEQMRAD8Am9ht9I3t25De4SJcpwBxwG1JTRqjzArr7wp2EoEgcZwyB5LCLzNtcdkNt6sxeEtSosVSlKJLa7JPzAu91f6OPq8R9wQpnk//2Q=="}},"cover":{"cover":{"type":"image","data":{"uuid":"53b847de-330e-5c18-aabc-a53f70cca839","width":1328,"height":1328,"size":556813,"type":"png","color":"c1c4e3","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAACAUH/8QAJxAAAgECBAQHAAAAAAAAAAAAAQIDBAUABhExEhMhUSQyNEFDcdL/xAAWAQEBAQAAAAAAAAAAAAAAAAAFBAb/xAAdEQEAAgICAwAAAAAAAAAAAAABAAMEEQIxEjJR/9oADAMBAAIRAxEAPwBcWfK1BDKKqC5UzwIRxjmrxaHY9/bbGtszVNakNWRz8gSXlzPkpVCm4T6gaenk/OIW23fURLz5A/lG7XV64o9zq2Uh2IMzEEheh3wvaEDo9ianFcrjyk8fU+UfK3b7xMhuJnU//9k="}},"cover_y":63},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 29 ноября 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":6170365,"userId":4237909,"count":0,"shareImage":"https://api.vc.ru/achievements/share/6170365"}],"lastModificationDate":1764905263,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":true,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1696956,"customUri":null,"subsiteId":791575,"title":"Как правильно работать с цепочками промисов в JavaScript: без сложностей и путаницы","date":1733643197,"dateModified":1733643197,"blocks":[{"type":"media","cover":true,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"b7711be9-c914-5731-8572-bbfebd2e3b83","width":1200,"height":801,"size":43004,"type":"jpg","color":"e7c5b0","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAKAAoDASEAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABQQH/8QAIRAAAQMEAQUAAAAAAAAAAAAAAQIDBAAREiEFEzJRcZH/xAAWAQEBAQAAAAAAAAAAAAAAAAAGBQf/xAAiEQABBAECBwAAAAAAAAAAAAABAAMEEQIFEhQhMTOBgrH/2gAMAwEAAhEDEQA/ANxjyTGdS29LXJzFkJ6YG/dIh1JF8k/aybSNEhPsB6Qa3dBdUBySbPfiaCC48nJ3Z7DU1z5NHpZPBR/b6qjfcz8L/9k="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Асинхронный код — это одна из самых сложных тем для начинающих разработчиков. Особенно, когда речь заходит о Promise и их цепочках. Задача усложняется еще больше, когда в учебниках и туториалах используют запутанные примеры, которые не помогают, а только больше сбивают с толку. Давайте разберемся с основами работы с промисами раз и навсегда, без лишней сложности.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h3","text":"🔍 Почему примеры из учебников не работают?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Большинство обучающих материалов, объясняя цепочки промисов, используют примеры с fetch() и response.json(). Вот что мы обычно видим:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"fetch('https://jsonplaceholder.typicode.com/posts')\n .then(response => {\n console.log('First then');\n return response.json(); // возвращается новый промис\n })\n .then(data => {\n console.log('Second then');\n console.log('Data received:', data); // данные из JSON\n })\n .catch(error => {\n console.log('Error:', error);\n });","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"На первый взгляд пример выглядит логично. Но начинающий разработчик часто задается вопросами:
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Почему в then() возвращается промис?
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Что вообще происходит с этими значениями внутри цепочки?
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Как это понимать без привязки к сетевым запросам?
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Такой подход усложняет понимание, так как акцент делается на работу с API, а не на основах работы промисов.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h3","text":"✅ Правильный пример для понимания"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Начнем с самого простого примера:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"new Promise((resolve) => {\n resolve(5); // Разрешаем промис со значением 5\n})\n .then((res) => res) // Первый then: передаем значение дальше\n .then((res) => res) // Второй then: снова передаем значение\n .then((res) => console.log(res)); // Выводим 5","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h3","text":"Что происходит?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"1. Промис создается с помощью new Promise, и он сразу разрешается со значением 5.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2. Каждый .then() получает результат предыдущего шага и возвращает его дальше.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"3. В итоге в консоли мы видим 5.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Этот пример прост, но он объясняет главный принцип работы промисов: каждый .then() обрабатывает результат предыдущего шага и передает его дальше.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h3","text":"🚀 Основные принципы работы с промисами"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"1. Каждый .then() возвращает новый промис.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Даже если внутри .then() вы просто возвращаете значение, это значение автоматически “оборачивается” в промис.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2. Можно возвращать что угодно.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В .then() можно вернуть:
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Примитивное значение (число, строку, объект);
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Новый промис;
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Ничего (undefined), если результат не важен.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"3. Асинхронность цепочек.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Если внутри .then() возвращается новый промис, следующий .then() начнет работать только после его разрешения.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Пример для закрепления:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"new Promise((resolve) => {\n resolve('Hello');\n})\n .then((res) => {\n console.log(res); // Hello\n return 'World';\n })\n .then((res) => {\n console.log(res); // World\n return new Promise((resolve) => setTimeout(() => resolve('!'), 1000));\n })\n .then((res) => console.log(res)); // !","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h3","text":"⚡ Заблуждения о промисах"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"1. “Нужно возвращать новый промис в каждом .then().”
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Нет! Возвращать новый промис нужно только тогда, когда вы выполняете асинхронные операции (например, таймеры, запросы).
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2. “Цепочка .then() не работает без catch().”
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Ложь! catch() нужен только для обработки ошибок. Если ошибок нет, цепочка выполнится без него.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h3","text":"💡 Как не запутаться?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Используйте .then() только для обработки результата предыдущего шага.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Не возвращайте промис там, где он не нужен.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Если вы работаете с цепочкой промисов, разделяйте задачи — обработка данных и асинхронные действия.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h3","text":"🔥 Применение на практике"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Пример с API, но без путаницы:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"fetch('https://jsonplaceholder.typicode.com/posts')\n .then((response) => response.json()) // Парсим JSON\n .then((data) => {\n const firstPost = data[0];\n console.log('First post:', firstPost); // Показываем первый пост\n return firstPost.title;\n })\n .then((title) => console.log('Title:', title)); // Показываем заголовок","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Здесь каждый шаг логически обоснован:
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Сначала обрабатываем response (парсинг JSON);
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Затем берем первый пост;
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"• Наконец, выводим его заголовок.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h3","text":"🔗 Итог"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"1. Понимание работы промисов начинается с простых примеров, а не с API.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2. .then() — это обработчик, а не место для лишнего создания промисов.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"3. Асинхронный код не должен быть сложным — он должен быть логичным.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Теперь у вас есть инструменты для написания чистого и понятного кода. Не путайтесь и делайте все проще. Успехов в освоении асинхронности!
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Если статья помогла вам, не забудьте поделиться и поддержать автора! Подписывайтесь на мой telegram канал! Тут много интересного :)
"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Ft.me%2Figoreshait&postId=1696956","title":"ИгорешаIT","description":"Новости из мира технологий 📲 Интересные наблюдения 🌏 Делаем стартапы / оценю ваш FREE 💸 Консалтинг в области построения digital проектов ✌🏻💳📲 Сотрудничество и предложения: @russiancmo 🧑💻","image":{"type":"image","data":{"uuid":"0c5ec522-6456-5837-a7a9-efa6a12645d2","width":180,"height":180,"size":4016,"type":"png","color":"26a5e4","hash":"","external_service":[]}},"v":1,"hostname":"t.me"}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"#js #урокиjs #промисы #разработка #разработкаприложений #разработкасайтов #javascript #javascriptdeveloper
"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":2,"favorites":1,"reposts":0,"views":833,"hits":274,"reads":null,"online":0},"dateFavorite":0,"hitsCount":274,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1696956-kak-pravilno-rabotat-s-cepochkami-promisov-v-javascript-bez-slozhnostei-i-putanicy","author":{"id":791575,"name":"Игорь Лобода","nickname":null,"description":"Опыт и наблюдения ведущего разработчика ПО. Все от кода до квантовой запутанности :)","uri":"","avatar":{"type":"image","data":{"uuid":"17a08cf1-1216-565a-82fb-f85d7ec48771","width":200,"height":200,"size":43655,"type":"jpg","color":"a1948a","hash":"b071e4e1f2b4e467","external_service":[]}},"cover":{"cover":{"type":"image","data":{"uuid":"2236b1b2-2f71-56ac-b2fa-8fcc92a0fac2","width":1920,"height":768,"size":136821,"type":"jpg","color":"343333","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQEAHgAeAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAQECAQEBAgICAgICAgICAQICAgICAgICAgL/2wBDAQEBAQEBAQEBAQECAQEBAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgL/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMHCv/EACIQAAEDAwMFAAAAAAAAAAAAAAECAxEABiEFEjEVFiZRgf/EABcBAAMBAAAAAAAAAAAAAAAAAAAEBQb/xAAlEQACAQMDAgcAAAAAAAAAAAABAhEAAwQSITEFQQYTI0JhYtH/2gAMAwEAAhEDEQA/AM3XQrMDTkXwyVhxKW5t7WQpbcDc5gFKBI4MnPqlj5xuBxd0219ujn5JO/fsRxVhG6euK1hsAXcm7BF45IlN1BVUUBN4Ma1YgMTuAIb2ra5yL4bg8eOarx9XS56ljAkG6JH1f8qqPBnXWAZcE6W3E38eYPE+pzU/qjWSooor/9k="}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4645848,"userId":791575,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4645848"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":1021761,"userId":791575,"count":0,"shareImage":"https://api.vc.ru/achievements/share/1021761"}],"lastModificationDate":1764905263,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":true,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":3}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1624724,"customUri":null,"subsiteId":411234,"title":"Кейс модификации Tilda: создание слайдера с анимированными карточками отзывов для сайта","date":1732507326,"dateModified":1733830562,"blocks":[{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"#кейс #тильда #tilda #javascript #веб #web #frontend
"}},{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"В этой статье я расскажу о процессе разработки модификации для сайта на конструкторе Tilda — слайдера с отзывами в виде карточек, которые расположены по кругу, наподобие игральных карт.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Помимо слайдера, при нажатии на карточку также должна воспроизводиться анимация переворачивания карточки, и должно открываться всплывающее окно.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Во время реализации я опирался на референс с Dribble, который мне предоставил клиент:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"reference","data":{"items":[{"title":"Референс слайдера, который предоставил клиент (Источник: Outcrowd)","image":{"type":"image","data":{"uuid":"7247aacd-d5c6-5f0c-a96b-f3d567ff587a","width":1600,"height":1200,"size":1711582,"type":"gif","color":"d6d6db","hash":"","external_service":[],"duration":7,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Зачем делать ещё один слайдер?
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В статье я расскажу о своем подходе к разработке модификаций для Tilda на примере слайдера и покажу как можно интегрировать и расширить уже существующее решение на примере библиотеки Swiper.
"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fswiperjs.com%2F&postId=1624724","title":"Swiper - The Most Modern Mobile Touch Slider","description":"Swiper is the most modern free mobile touch slider with hardware accelerated transitions and amazing native behavior.","image":{"type":"image","data":{"uuid":"0a6a78a5-5df2-5e21-a7b5-37d1e1ed09d8","width":512,"height":512,"size":8507,"type":"png","color":"0484fc","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABQIH/8QAHBAAAgIDAQEAAAAAAAAAAAAAAQMCBAAGEQUx/8QAFgEBAQEAAAAAAAAAAAAAAAAABQEI/8QAHhEBAAMAAgIDAAAAAAAAAAAAAQACAwUhBBEGMUH/2gAMAwEAAhEDEQA/AM/zccx1AbOgaJdstuXNJ8F73zk1rW+amU2TkemUiY9JJJJJwbX47w+97a6+Jla1lVc6Kr2qp2v6xXPnuVxoZ5+VoVD0BewAfQHvoI8fuMQmUAOfMsk//9k="}},"v":1,"hostname":"swiperjs.com"}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Цель этой статьи — поделиться моим опытом разработки модификаций для Тильды и осветить некоторые особенности, которые могут возникнуть в ходе разработки нового функционала.
"}},{"type":"person","cover":false,"hidden":false,"anchor":"","data":{"image":{"type":"image","data":{"uuid":"ceb832ff-62d3-565d-8fc3-1f1a200855be","width":720,"height":720,"size":234708,"type":"png","color":"b29d8a","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwUI/8QAJRAAAQMDAwQDAQAAAAAAAAAAAQIDBAUGEQASEwcUITFBUWGR/8QAGAEAAgMAAAAAAAAAAAAAAAAABAYAAwf/xAAiEQEAAgEDAwUAAAAAAAAAAAABAgMAITFBBCJRERITFJH/2gAMAwEAAhEDEQA/AEyLKaqFXiP8qWoshtruosNW1h1xaBv3p2nkBSU4zg/3S1KaTiGzjFXTCVM5O5k57rbEgvOQm4dASiOotJCpmwgJOBlOfB8evjRX1rnUi/jgfy1nOZE6aXBXxTa22K5UAlMxCgnuV4BTIwk+/YAAH0ANaDPpqT2dhoIaGLxdYku519Oce7ptm213PV1Kt6mlRnyCSYjeSeRX5qUX2xqiEnY5fGU2VVs1Ym/jP//Z"}},"title":"Алексей Иванов","description":"Фулстек веб-разработчик"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Содержание"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Выбор библиотеки для слайдера: почему Swiper","Преобразование Zero-block в интерактивный слайдер","Первая попытка: анимация с помощью effect creative","Разработка собственного модуля Swiper с уникальной анимацией","Анимация карточек с помощью интерполяции","Конечный результат"],"type":"OL"}},{"type":"header","cover":false,"hidden":false,"anchor":"1","data":{"style":"h2","text":"1. Выбор библиотеки для слайдера: почему Swiper"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Хотя существует множество библиотек со слайдерами и модификаций для Tilda, я не нашел готового решения, которое бы подходило под мою задачу.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Из-за этого было решено взять за основу популярную библиотеку Swiper (почти 40 тыс. звезд на GitHub) для создания слайдеров и реализовать собственную анимацию на базе нее.
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Swiper имеет множество вариантов слайдеров из коробки, в случае необходимости его можно расширить с помощью плагинов (Источник: Swiper)","image":{"type":"image","data":{"uuid":"d998346a-fb0d-5458-8915-c6915bee3ad1","width":1486,"height":694,"size":570830,"type":"gif","color":"25272e","hash":"","external_service":[],"duration":11.633333,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Новые версии этой библиотеки не требуют jQuery, что убирает лишние зависимости. Также важно отметить то, что в ней предусмотрена система плагинов. Поэтому свой уникальный функционал можно оформить в виде отдельного файла и поделиться им с другими пользователями.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"2","data":{"style":"h2","text":"2. Преобразование Zero-block в интерактивный слайдер"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В проекте, для которого мне нужно было реализовать слайдер, уже были оформлены карточки с отзывами в виде Zero-block. Внутри каждая карточка представляет собой группу.
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Исходный блок с карточками отзывов","image":{"type":"image","data":{"uuid":"0c27cf2b-b05b-5b9a-8617-f45c73e48f36","width":1263,"height":572,"size":90906,"type":"png","color":"040404","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIGCf/EACQQAAEDAgQHAAAAAAAAAAAAAAECAyEABAUSUpEGERMVUZPR/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AMzHbHAHLhoG9eU64Qp4dPkEgiSImfFA3beDhBx131H5QTmdetW9AZ16jvQf/9k="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Для того, чтобы преобразовать данный блок в интерактивный слайдер, необходимо сделать несколько действий:
"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Добавить контейнер для слайдера в зеро блок","Оформить карточки, которые будут служить слайдами","Инициализация слайдера","Интеграция слайдера с существующей версткой Tilda бы"],"type":"OL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Далее опишу каждое из действий подробнее.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2.1. Добавление контейнера для слайдера
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Первым делом, в редакторе зеро блока с карточками добавим элемент с HTML-кодом:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"Размеры и положение этого элемента будут служить контейнером для слайдера. Таким образом, можно будет задать ширину контейнера для каждого брейкпоинта в зеро блоке.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Класс задавать этому элементу не нужно, потому что вставляемый HTML-код с классом swiperи так будет находиться внутри зеро блока.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2.2. Оформление групп в виде объектов
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Чтобы карточки можно было использовать как слайды, их можно сгруппировать в виде объекта (Object). Это укажет Тильде, что каждую группу нужно оформить в виде отдельного DIV элемента, с которым можно будет взаимодействовать с помощью JavaScript.
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"77b50a0e-3c34-5173-9fff-799426bfa4ca","width":601,"height":346,"size":11874,"type":"png","color":"ededee","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAACQT/xAAkEAACAQMEAAcAAAAAAAAAAAABAgMABBEFBwkxBhUhJEFi0f/EABcBAAMBAAAAAAAAAAAAAAAAAAABAgT/xAAbEQACAgMBAAAAAAAAAAAAAAAAAQIhAwQxUf/aAAwDAQACEQMRAD8An4cW0x7zdTyaKRbZk0kYd8nPuQT3kfFU22rATIRIABlvT7H8qQDZ4hYooPF+90EMaxxR31iqIowqgNdYAA6rRtVlkl6xR4JcAMdCs4z/2Q=="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Чтобы скрипт мог найти карточки, я задал CSS класс card-text для элементов с текстом отзыва. Благодаря этому классу, в коде карточки можно найти с помощью querySelectorAll:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const block = document.querySelector('.uc-review-cards')\n\nblock.querySelectorAll('.card-text')","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Хочу отметить, что я нахожу зеро блок также через класс uc-review-cards, в отличие от того, как это обычно делают в бесплатных модификациях по rec id: #rec123456789. Я так делаю потому, что при поиске через класс удобно переносить блок между страницами или переключаться между разными версиями блока — не нужно каждый раз менять rec id в коде.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2.3. Инициализация слайдера
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"После того как элементы с карточками были найдены, их нужно переместить в контейнер слайдера Swiper и добавить класс swiper-slide:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const block = document.querySelector('.uc-review-cards')\nconst sliderWrapper = block.querySelector('.swiper-wrapper')\n\nblock.querySelectorAll('.card-text').forEach(el => {\n const slide = el.closest('.tn-group')\n slide.classList.add('swiper-slide')\n sliderWrapper.append(slide)\n})","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Здесь поиск происходит по родительскому элементу с классом tn-group, он доступен из-за того, что ранее был выбран тип группы Object.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Теперь можно инициализировать слайдер:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"document.addEventListener('DOMContentLoaded', () => {\n const block = document.querySelector('.uc-review-cards')\n const sliderWrapper = block.querySelector('.swiper-wrapper')\n \n block.querySelectorAll('.card-text').forEach(el => {\n const slide = el.closest('.tn-group')\n slide.classList.add('swiper-slide')\n sliderWrapper.append(slide)\n })\n \n const swiper = new Swiper(block.querySelector('.swiper'), {\n loop: true,\n slidesPerView: 3,\n grabCursor: true,\n centeredSlides: true,\n })\n})","lang":""}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"А вот результат...","image":{"type":"image","data":{"uuid":"fab966d3-5589-5daf-9507-23cdee976423","width":1292,"height":477,"size":20538,"type":"png","color":"040404","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJVAAAA//9k="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Как видно, с первого раза слайдер завести не получилось. На самом деле он работает, но из-за особенностей верстки в Tilda он не отображается. Это можно исправить, модифицировав CSS стили.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"2.4. Интеграция слайдера с существующей версткой Tilda
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Прична, по которой слайдер не показывается вызван конфликтом со стилями Tilda. Из-за этого Swiper не может корректно рассчитать размер контейнера. Чтобы это исправить, нужно добавить дополнительные CSS стили:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":".uc-review-cards .swiper {\n width: 100%;\n position: absolute;\n overflow: visible; /* Чтобы карточки было видно за пределами контейнера */\n}\n\n.uc-review-cards .swiper-slide {\n position: initial !important;\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Данный CSS масштабирует контейнер Swiper с учетом вложенности в зеро блок Тильды. В результате получим базовый слайдер с горизонтальной прокруткой:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"643e7229-7095-5977-ad48-5ba99d2f257d","width":1270,"height":554,"size":890458,"type":"gif","color":"040404","hash":"","external_service":[],"duration":6.866667,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"На данном этапе ничего необычного — все, что я описал выше описано в других руководствах в интернете. Проблема возникает, когда нужно изменить форму слайдера: вместо горизонтального скрола сделать так, чтобы карточки перемещались по кругу, как в примере в начале статьи.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"У Swiper есть различные заготовки для переходов и одна из них позволяет гибко настраивать переходы между слайдами. Она называется creative effect, которую мы рассмотрим далее.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"3","data":{"style":"h2","text":"3. Первая попытка: анимация с помощью effect creative"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"С помощью effect creative можно задать смещение, поворот, прозрачность и масштаб следующего и предыдущего слайдов:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Примеры effect creative с различными параметрами (Источник: Swiper)","image":{"type":"image","data":{"uuid":"da676244-71ff-51ed-b168-d8a1bfe42851","width":1582,"height":954,"size":119043,"type":"gif","color":"1c1b1b","hash":"","external_service":[],"duration":9.4,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Определим собственные параметры для effect creative таким образом, чтобы это было похоже на эффект из референса:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const swiper = new Swiper(block.querySelector('.swiper'), {\n loop: true,\n grabCursor: true,\n centeredSlides: true,\n slidesPerView: 3,\n // Укажем эффект 'creative' и зададим правила анимации\n effect: 'creative',\n creativeEffect: {\n prev: {\n translate: [-150, 20, -150],\n rotate: [0, 0, -30],\n origin: 'center bottom',\n },\n next: {\n translate: [150, 65, -150],\n rotate: [0, 0, 30],\n origin: 'center bottom',\n },\n },\n})","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В результате получим следующий эффект:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"147efbde-c7cb-5533-b930-553531411f0a","width":1260,"height":624,"size":1194649,"type":"gif","color":"040404","hash":"","external_service":[],"duration":7.666667,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Как видно, эффект работает, однако только для трех карточек.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Недостатком effect creative является то, что он работает только для 3-х слайдов (следующий, предыдущий и текущий). Применить данный эффект к большему количеству карточек не получится.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Однако есть выход: можно написать собственный плагин для Swiper, который бы обновлял положение всех карточек в соответствии с любыми заданными правилами в коде.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"4","data":{"style":"h2","text":"4. Разработка собственного модуля Swiper с уникальной анимацией карточек"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Плагин для Swiper — это просто функция, которая на вход получает экземпляр слайдера, его параметры и функцию для добавления обработчиков событий:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const MySwiperModule = ({ swiper, params, on }) => {\n // Код, реализующий уникальную анимацию\n}\n\nconst swiper = new Swiper(block.querySelector('.swiper'), {\n // Конфигурация слайдера\n modules: [ MySwiperModule ], // Подключение собственного модуля\n})","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Определение функции для анимации слайдов
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Чтобы анимировать слайды, нужно при каждом обновлении слайдера обновлять положение, угол наклона и другие параметры карточки.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"К сожалению, Swiper не предоставляет обработчика события при обновлении слайдера. У него есть событие progress, которое срабатывает во время перетаскивания слайдера. Однако оно не срабатывает, когда происходит выравнивание по центру при окончании перетаскивания.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Вместо этого, можно использовать функцию Web API requestAnimationFrame, которая срабатывает на каждом кадре отрисовки изображения в браузере и с помощью которой можно реализовать плавные анимации с частотой 60 Hz и выше, в зависимости от устройства. Подробнее про то, как работает requestAnimationFrame можно прочитать на MDN.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В итоге получим заготовку для анимации слайдов:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const MySwiperModule = ({ swiper, params, on }) => {\n \n let animationFrame\n \n const animate = () => {\n swiper.slides.forEach((slide, index) => {\n // Логика для анимации каждого слайда\n })\n \n animationFrame = requestAnimationFrame(animate)\n }\n \n animationFrame = requestAnimationFrame(animate)\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Если запустить слайдер сейчас, то никакой анимации кроме перемещения, очевидно, не будет:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Swiper отвечает за логику перемещения карточек при перетаскивании, но не анимирует карточки","image":{"type":"image","data":{"uuid":"fe69f7c1-f958-58af-9d62-951a07f9aee8","width":1908,"height":554,"size":1553324,"type":"gif","color":"050505","hash":"","external_service":[],"duration":8.633333,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Чтобы анимировать слайды, необходимо динамически рассчитывать угол наклона, масштаб и другие анимируемые параметры каждой карточки. Это можно реализовать с помощью линейной интерполяции.
"}},{"type":"header","cover":false,"hidden":false,"anchor":"5","data":{"style":"h2","text":"5. Анимация карточек с помощью интерполяции"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Линейная интерполяция — это простой способ прогнозирования значения между двумя известными точками. Например, если есть данные о продажах за январь и март, то с помощью линейной интерполяции можно посчитать объем продаж в феврале, даже если нет данных за этот месяц.
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Общий принцип работы линейной интерполяции — расчет значений между точками (x0, y0) и (x1, y1)","image":{"type":"image","data":{"uuid":"1ce8f3f9-c52e-5389-bf60-133341929479","width":206,"height":193,"size":4552,"type":"png","color":"c3c2c3","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAQUGCf/EACAQAAICAAcBAQAAAAAAAAAAAAECAwQABRESITFBUWH/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQP/xAAdEQACAQQDAAAAAAAAAAAAAAAAARECAxJBEyFR/9oADAMBAAIRAxEAPwDTWJ70mX06eXKyu1eIvNIzBUUjz63B08HZ8B1tcaeVeteljqRzWTZXiTc7bUUasxJPHZPpxkG5ckKsMS35o1iQIsMe1Qo0HL9DAgKaIacBKKSY11On5gD/2Q=="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Обычно для удобства работы, значение интерполяции во время нормализуется и считается на интервале от 0.0 до 1.0.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Можно представить координаты карточек в координатной сетке слайдера по аналогии с предыдущим рисунком:
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"9ce925a6-a78b-589a-a725-44f202f2b52b","width":1273,"height":509,"size":160623,"type":"png","color":"547c8c","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAABAf/xAAmEAABAgQEBwEAAAAAAAAAAAABAhEAAwQFBwgVIRMUQUVhlLLR/8QAGQEAAQUAAAAAAAAAAAAAAAAAAQIDBAUG/8QAIhEAAQMCBgMAAAAAAAAAAAAAAQACAxEUEzJRUpGhBBUx/9oADAMBAAIRAxEAPwCQpyz4LGUtarEjZaQDxV7uC/WL21hOVqy9/wCSBVz0FeVrBArUTb6gOSWFVNb6hNizTso+zm3dBBlXy9abPVrFc4my2PMLcbHzDAkfX6VKMMdMo4RNevh7zXewv9gYr9Sjgx7Rwv/Z"}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В данном случае левое значение будет равно 0, а правое — 1. Чтобы посчитать промежуточные значения координат, нужно знать ширину контейнера слайдера, его положение на странице, а также размеры и положение карточки, для которой рассчитывается интерполяция.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"На самом деле, для нашего случая нужен немного смещенный диапазон: от -1 до 1. Это позволит поворачивать и смещать карточки как влево (-1), так и вправо (1).
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const animate = () => {\n // Размеры и положение контейнера слайдера\n const swiperRect = swiper.el.getBoundingClientRect()\n const centerX = swiperRect.x + swiperRect.width / 2\n \n swiper.slides.forEach((slide, index) => {\n // Размеры и положение одного слайда (карточки)\n const rect = slide.getBoundingClientRect()\n // Абсолютные координаты середины карточки\n const slideX = rect.x + rect.width / 2\n // Размер конкретного слайдера, который задан Swiper-ом\n const slideSize = swiper.slidesSizesGrid[index]\n // Количество карточек в одной половине слайдера\n const numberOfSlidesOnOneSide = swiper.slidesPerViewDynamic() - 1\n \n const rawInterpolation = (slideX - centerX) / slideSize / Math.max(1, numberOfSlidesOnOneSide / 2)\n \n console.log(rawInterpolation)\n })\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В результате получим значения \"сырой\" (raw) интерполяции, которая может выходить за пределы [-1, 1]. Главное здесь то, что по данной формуле рассчитываемое значение всегда симметрично середины слайдера. Вот пример значений rawInterpolation для 10 слайдов:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"[-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]\n ^^^\n | Середина слайдера |","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Ограничить значения за пределами слайдера легко, воспользовавшись математической функцией clamp:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const clampedInterpolation = Math.min(1, Math.max(-1, rawInterpolation))","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Можно посмотреть как работает полученное значение, задав трансформацию карточкам. Например, анимируем смещение по вертикали и угол наклона:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"slide.style.transform = `translateY(${-clampedInterpolation * 100}px) rotate(${90 * clampedInterpolation}deg)`","lang":""}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Значения смещения по вертикали и угол наклона симметричны относительно середины слайдера","image":{"type":"image","data":{"uuid":"f6493f94-f52e-5803-ac08-306fc83a4423","width":1910,"height":626,"size":5218511,"type":"gif","color":"050405","hash":"","external_service":[],"duration":11.333333,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Сглаживание анимации интерполируемых параметров
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Интерполируемое значение можно сгладить, если возвести полученное значение в степень. Таким образом, кстати, достигается большинство плавных анимаций в интерфейсе.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Определим два значения интерполяции — линейное и кубическое:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const linear = clampedInterpolation\nconst cubic = Math.pow(linear, 3)","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"На изображении ниже представлена разница между ними (только в нашем случае у нас всего две точки, а не множество точек, как на рисунке ниже, но принцип сглаживания один и тот же).
"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"Линейная и кубическая интерполяция. Во втором случае значение возведено в куб (третью степень). Источник: Alvaro Carnielo e Silva","image":{"type":"image","data":{"uuid":"f4594276-a385-5089-9f66-5e8807104654","width":587,"height":420,"size":12595,"type":"png","color":"7b7a79","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAACAUH/8QAKhAAAQIEBAMJAAAAAAAAAAAAAQIDBAUGEQAUIUEHCBITFhciUVaVo9L/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/EABgRAQEAAwAAAAAAAAAAAAAAAAABESFB/9oADAMBAAIRAxEAPwBIQkLWVTcrkmbkcVEPTGInC0tnMlDjiUrdJutRFhZPrtiZ6sjMvDHjodRErAOxnSLj7MU0aTlNU4KShZOJBLcgh/rTC5RvsQrzG4Rbpvqdbb4Ij9xqJ9nyT49n84D/2Q=="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Использование кубической интерполяции позволит сгладить анимацию карточек и сделать ее более \"живой\". Модифицируем анимацию, добавив масштабирование и смещение по горизонтали. Конечно же, диапазоны анимируемых значений также нужно изменить. В результате получим:
"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"slide.style.transform = `translateX(${linear * -200}px) translateY(${Math.abs(cubic) * 100}px) rotate(${45 * linear}deg) scale(${1 - 0.3 * Math.abs(cubic)})`\nslide.style.opacity = Math.abs(rawInterpolation) > 1 ? 0 : 'initial'\nslide.style.transformOrigin = 'center bottom'","lang":""}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"a6e004a1-1f95-5e6c-828f-8e525ff91ecf","width":1264,"height":684,"size":2464726,"type":"gif","color":"040404","hash":"","external_service":[],"duration":7.766667,"isVideo":false,"has_audio":false}}}]}},{"type":"header","cover":false,"hidden":false,"anchor":"6","data":{"style":"h2","text":"6. Конечный результат"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В итоге после реализации слайдера с уникальной анимацией, добавления обработчика клика по карточкам и всплывающего окна, был реализован весь функционал, который совпадает с референсом, присланным клиентом.
"}},{"type":"media","cover":true,"hidden":false,"anchor":"","data":{"items":[{"title":"Конечная реализация слайдера с всплывающим окном","image":{"type":"image","data":{"uuid":"5c3bc1b3-8f93-5bc0-b6e5-7fd1aea1bd4b","width":1236,"height":632,"size":4530329,"type":"gif","color":"040404","hash":"","external_service":[],"duration":16.5,"isVideo":false,"has_audio":false}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В общей сложности разработка подобного слайдера с учетом подводных каменей Swiper, связанных с прокруткой по клику и отображением карточек в мобильной версии заняла порядка 12 часов.
"}},{"type":"delimiter","cover":false,"hidden":false,"anchor":"","data":{"type":"default"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Поставьте лайк 🔥 или напишите комментарий если вам понравилась статья — так я пойму, что подобные посты с кейсами интересны, и буду писать их больше.
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"Если вам нужна разработка уникальной модификации или интеграции Tilda с CRM или сторонним сервисом по API: Telegram-ботом, искуственным интеллектом / ChatGPT или даже маркетплейсом — примеры моих работ и контакты доступны по ссылке: codly.cc
"}},{"type":"link","cover":false,"hidden":false,"anchor":"","data":{"link":{"type":"link","data":{"url":"https://api.vc.ru/v2.8/redirect?to=https%3A%2F%2Fcodly.cc%2F%3Futm_source%3Dvc.ru%26utm_medium%3Dblog%26utm_campaign%3D1624724&postId=1624724","title":"Codly — интеграция Tilda с любыми сервисами под ключ","description":"Разработка модификаций для Tilda, интеграция по API с CRM, ERP, платежными системами, системами складского учета","image":{"type":"image","data":{"uuid":"https://leonardo.osnova.io/ico/codly.cc","width":0,"height":0,"size":0,"type":"jpg","color":"","hash":"","external_service":[]}},"v":1,"hostname":"codly.cc"}}}},{"type":"quote","cover":false,"hidden":false,"anchor":"","data":{"text":"Пишу клон игры Flappy Bird с помощью chatGPT и новейшей текстовой модели o1 на React JS!
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"В ролике:
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"✔покажу процесс создания игры
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"✔расскажу о сложностях, с которыми я столкнулся
"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"✔дам пару советов, что бы выжимать из chatGPT максимум
"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Смотреть на YouTube:"}},{"type":"video","cover":false,"hidden":false,"anchor":"","data":{"title":"Клон Flappy Bird за 5 минут с помощью chatGPT","video":{"type":"video","data":{"thumbnail":{"type":"image","data":{"uuid":"0919a897-cfb6-5fea-ae15-fc19aebe6907","width":1280,"height":720,"size":169357,"type":"jpg","color":"27474e","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAQDAwMDAgQDAwMEBAQFBgoGBgUFBgwICQcKDgwPDg4MDQ0PERYTDxAVEQ0NExoTFRcYGRkZDxIbHRsYHRYYGRj/2wBDAQQEBAYFBgsGBgsYEA0QGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBj/wAARCAAKAAoDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAABAUGB//EACUQAAIBAgUDBQAAAAAAAAAAAAECAwQFAAYREiEHE2EWMTJCcf/EABQBAQAAAAAAAAAAAAAAAAAAAAP/xAAfEQABAgcBAQAAAAAAAAAAAAABAhEAAwQFEyFBYTH/2gAMAwEAAhEDEQA/AJy63mz5ZSNJMuGpQydkPuZto3HbwqH6qNTqfPOBkzJY6mNaj0/WHuAPqlG5B15449sKOoU88eU7M8c0is13jRirEFlLkEHx4xkx+R/cJabTRVqVqxBJB+7fvsJQGctOSZMJfjJYbI0G8j//2Q=="}},"width":800,"height":450,"time":0,"external_service":{"name":"youtube","id":"0WF6Ynuvi4w"}}}}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Смотреть в Вк:"}},{"type":"video","cover":true,"hidden":false,"anchor":"","data":{"title":"Клон Flappy Bird за 5 минут с помощью chatGPT","video":{"type":"video","data":{"thumbnail":{"type":"image","data":{"uuid":"99dd90fc-40ba-56b1-82fd-5abbf66ba055","width":1280,"height":720,"size":214899,"type":"jpg","color":"2b2d40","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAIBAQEBAQIBAQECAgICAgQDAgICAgUEBAMEBgUGBgYFBgYGBwkIBgcJBwYGCAsICQoKCgoKBggLDAsKDAkKCgr/2wBDAQICAgICAgUDAwUKBwYHCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgr/wAARCAAKAAoDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAABQcICf/EACUQAAEDAwMDBQAAAAAAAAAAAAECAwQFBhEAByEIEhMKFDJBUf/EABUBAQEAAAAAAAAAAAAAAAAAAAQF/8QAIREAAQMEAQUAAAAAAAAAAAAAAQMRIQIEBTESABNhcfD/2gAMAwEAAhEDEQA/AExvdYsnZtZl3f0wWizCk1xuBEeiWvARK8KVdzrzaFtgKRhIwfmcntxnQlm1un6e0mdSNr6+uI8kORVu1GAlSmzykke64OMcfWrd9R5btv07pB2YrVPoUNiYzcZUzLZioS6hXgzkKAyDn8Os5qMlKKRFQhIAEZAAA4A7Rq5jsIle94c6hxqADkmG9j7zPTMUsneWyVyqnS9VIcABt7l9tM7dmED/2Q=="}},"width":3840,"height":2160,"time":0,"external_service":{"name":"vk","id":"-212223166_456239192","additional_data":{"hash":"060754938aabc485","o_id":"-212223166","v_id":"456239192"}}}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"#chatgpt #javascript #reactjs
"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":1,"favorites":1,"reposts":0,"views":399,"hits":259,"reads":null,"online":0},"dateFavorite":0,"hitsCount":259,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1578538-klon-flappy-bird-za-5-minut-s-pomoshyu-chatgpt-na-react-js","author":{"id":1421040,"name":"Техноманьяк","nickname":null,"description":"Рассказываю про ИИ 👉🏻 https://t.me/tehnomaniak07","uri":"","avatar":{"type":"image","data":{"uuid":"23eaef89-51cb-5cd3-8b7b-f72876cd4527","width":1500,"height":1500,"size":588947,"type":"png","color":"1b1040","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwYI/8QAIhAAAQMEAgIDAAAAAAAAAAAAAQIDBQAEBiEREgcxI0Gh/8QAFwEBAQEBAAAAAAAAAAAAAAAABgcEBf/EACURAAEDAwIGAwAAAAAAAAAAAAECAxEAITEEBQYSE0FRsRQigf/aAAwDAQACEQMRAD8AxquKxRjxlLXd/G367y6W09GXLNmtTKEJUQtJcPAA39c7T7qr8Q6nZtFvGjaacKm0E9RKeyVQRbE2gjwZrYhTjmneJTYxymPHoUPllkk/P+GnCNq2d5IcbX9TcWODijx5xmraNy7K3MEk4ZzJ5ZUezHBtu0N64WUILm0pR26gHk6AqLbwy2l8OJSAozJi5/aQaR934ykcxiMSYzR2knqNn1TXSrV0EX7D1XDVk1//2Q=="}},"cover":{"cover":{"type":"image","data":{"uuid":"09c0fd14-6af1-52f3-9899-d848de90c704","width":3953,"height":909,"size":3121111,"type":"jpg","color":"38183e","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQIAdgB2AAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAgYHCP/EACcQAAIBAwIDCQAAAAAAAAAAAAECAwQFEQAGEiExBxYiIzNRcZGh/8QAGAEBAAMBAAAAAAAAAAAAAAAABQECAwf/xAAlEQABAwMCBgMAAAAAAAAAAAABAAIEAxEhMZEFEhMUM2Fi0uH/2gAMAwEAAhEDEQA/AJVSW3s0vu1ls1ft5qK8FwfMqFMqqSQIyVGOLPCM4x1+NdRPGKsumH1pVJt825SLY0w43sfal0htOoQym8gY1GfeRhKtPaa6Onijpdps0CoqxsKuIgoByPp+2iHwo1Rxd37M/E/ZKsntDQOid/xZzkv19kZpJL1Xsx6k1Lkn90o2HHJ8bdgiwi70bmTwruK5hRyAFXJgD71qYca/jbsFa5X/2Q=="}},"cover_y":0},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4032137,"userId":1421040,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4032137"}],"lastModificationDate":1764905263,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}}],"ogTitle":null,"ogDescription":null,"isAnonymized":true}};