Разработка сайтов в Петровском, ЛНР. Создайте 3D-принтер CSS, который действительно печатает!

 
 

Каждая демонстрация — это возможность попробовать что-то новое или разработать способы работы с CSS. Одна вещь, которую я часто делаю, — это принимаю предложения о том, что мы должны попробовать и сделать на стриме. Недавним предложением был принтер, который печатает в «3D». И вот что я собрал!

Создание вещей в 3D с помощью CSS

Я уже писал о создании 3D с помощью CSS. Общий смысл в том, что большинство сцен представляют собой композицию кубоидов.

Чтобы создать прямоугольный параллелепипед, мы можем использовать CSS-преобразования для позиционирования сторон прямоугольного параллелепипеда — волшебное свойство — transform-style. Установка этого preserve-3dпараметра позволяет нам преобразовывать элементы в третьем измерении:

* {

transform-style: preserve-3d;

}

Как только вы создадите несколько таких сцен, вы начнете искать способы ускорить процесс. Мне нравится использовать Pug в качестве препроцессора HTML. Возможность миксина дает мне возможность быстрее создавать кубоиды. В примерах разметки в этой статье используется Pug. Но для каждой демонстрации CodePen вы можете использовать опцию «Просмотр скомпилированного HTML», чтобы увидеть вывод HTML:

mixin cuboid ()

.cuboid (class≠attributes.class)

— let s = 0

while s < 6

.cuboid__side

— s++

Использование +cuboid () (class="printer__top") приведет к этому:

Затем у меня есть набор блоков CSS, которые я использую для размещения кубоидов. Радость здесь в том, что мы можем использовать пользовательские свойства CSS для определения свойств прямоугольного параллелепипеда (как показано в видео выше):

.cuboid {

// Defaults

—width: 15;

—height: 10;

—depth: 4;

height: calc (var (—depth) * 1vmin) ;

width: calc (var (—width) * 1vmin) ;

transform-style: preserve-3d;

position: absolute;

font-size: 1rem;

transform: translate3d (0, 0, 5vmin) ;

}

.cuboid > div: nth-of-type (1) {

height: calc (var (—height) * 1vmin) ;

width: 100%;

transform-origin: 50% 50%;

position: absolute;

top: 50%;

left: 50%;

transform: translate (-50%, -50%) rotateX (-90deg) translate3d (0, 0, calc ((var (—depth) / 2) * 1vmin));

}

.cuboid > div: nth-of-type (2) {

height: calc (var (—height) * 1vmin) ;

width: 100%;

transform-origin: 50% 50%;

transform: translate (-50%, -50%) rotateX (-90deg) rotateY (180deg) translate3d (0, 0, calc ((var (—depth) / 2) * 1vmin));

position: absolute;

top: 50%;

left: 50%;

}

.cuboid > div: nth-of-type (3) {

height: calc (var (—height) * 1vmin) ;

width: calc (var (—depth) * 1vmin) ;

transform: translate (-50%, -50%) rotateX (-90deg) rotateY (90deg) translate3d (0, 0, calc ((var (—width) / 2) * 1vmin));

position: absolute;

top: 50%;

left: 50%;

}

.cuboid > div: nth-of-type (4) {

height: calc (var (—height) * 1vmin) ;

width: calc (var (—depth) * 1vmin) ;

transform: translate (-50%, -50%) rotateX (-90deg) rotateY (-90deg) translate3d (0, 0, calc ((var (—width) / 2) * 1vmin));

position: absolute;

top: 50%;

left: 50%;

}

.cuboid > div: nth-of-type (5) {

height: calc (var (—depth) * 1vmin) ;

width: calc (var (—width) * 1vmin) ;

transform: translate (-50%, -50%) translate3d (0, 0, calc ((var (—height) / 2) * 1vmin));

position: absolute;

top: 50%;

left: 50%;

}

.cuboid > div: nth-of-type (6) {

height: calc (var (—depth) * 1vmin) ;

width: calc (var (—width) * 1vmin) ;

transform: translate (-50%, -50%) translate3d (0, 0, calc ((var (—height) / 2) * -1vmin)) rotateX (180deg) ;

position: absolute;

top: 50%;

left: 50%;

}

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

—width: ширина прямоугольного параллелепипеда на плоскости

—height: высота прямоугольного параллелепипеда на плоскости

—depth: глубина прямоугольного параллелепипеда на плоскости

—x: положение X на плоскости

—y: положение Y на плоскости

Это не очень впечатляет, пока мы не поместим прямоугольный параллелепипед в сцену и не повернем его. Опять же, я использую пользовательские свойства для управления сценой, пока я работаю над чем-то. Dat.GUI здесь очень удобен.

Если вы просматриваете демонстрацию, с помощью панели управления обновляются пользовательские свойства CSS на сцене. Такая область видимости пользовательских свойств CSS позволяет сэкономить много повторяющегося кода и делает вещи СУХИМИ.

Больше, чем один способ

Как и многие вещи в CSS, это можно сделать несколькими способами. Часто вы можете составить сцену из прямоугольных параллелепипедов и расположить их так, как вам нужно. Хотя управлять им может быть сложно. Часто возникает необходимость сгруппировать вещи или добавить какой-нибудь контейнер.

Рассмотрим этот пример, где стул является собственной подсценой, которую можно перемещать.

Многие недавние примеры не такие сложные. Я тянулся к экструзии. Это означает, что я могу отобразить все, что я делаю, в 2D-элементах. Например, вот вертолет, который я недавно создал:

.helicopter

.helicopter__rotor

.helicopter__cockpit

.helicopter__base-light

.helicopter__chair

.helicopter__chair-back

.helicopter__chair-bottom

.helicopter__dashboard

.helicopter__tail

.helicopter__fin

.helicopter__triblade

.helicopter__tail-light

.helicopter__stabilizer

.helicopter__skids

.helicopter__skid—left.helicopter__skid

.helicopter__skid—right.helicopter__skid

.helicopter__wing

.helicopter__wing-light.helicopter__wing-light—left

.helicopter__wing-light.helicopter__wing-light—right

.helicopter__launchers

.helicopter__launcher.helicopter__launcher—left

.helicopter__launcher.helicopter__launcher—right

.helicopter__blades

Затем мы можем поместить кубоиды во все контейнеры с помощью миксина. Затем примените необходимую «толщину» к каждому параллелепипеду. Толщина определяется пользовательскими свойствами области действия. Эта демонстрация переключает —thicknessсвойства кубоидов, из которых состоит вертолет. Это дает представление о том, как изначально выглядело 2D-отображение.

Это суть того, как создавать 3D-объекты с помощью CSS. Покопавшись в коде, вы наверняка обнаружите некоторые хитрости. Но, в общем, создайте сцену, заполните кубоидами и раскрасьте кубоиды. Вам часто понадобятся разные оттенки цвета, чтобы мы могли различать стороны прямоугольного параллелепипеда. Любые дополнительные детали — это либо вещи, которые мы можем добавить к стороне прямоугольного параллелепипеда, либо преобразования, которые мы можем применить к прямоугольному параллелепипеду. Например, вращение и перемещение по оси Z.

Рассмотрим урезанный пример:

.scene

.extrusion

+cuboid () (class="extrusion__cuboid")

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

.cuboid {

width: 100%;

height: 100%;

position: relative;

}

.cuboid__side: nth-of-type (1) {

background: var (—shade-one) ;

height: calc (var (—thickness) * 1vmin) ;

width: 100%;

position: absolute;

top: 0;

transform: translate (0, -50%) rotateX (90deg) ;

}

.cuboid__side: nth-of-type (2) {

background: var (—shade-two) ;

height: 100%;

width: calc (var (—thickness) * 1vmin) ;

position: absolute;

top: 50%;

right: 0;

transform: translate (50%, -50%) rotateY (90deg) ;

}

.cuboid__side: nth-of-type (3) {

background: var (—shade-three) ;

width: 100%;

height: calc (var (—thickness) * 1vmin) ;

position: absolute;

bottom: 0;

transform: translate (0%, 50%) rotateX (90deg) ;

}

.cuboid__side: nth-of-type (4) {

background: var (—shade-two) ;

height: 100%;

width: calc (var (—thickness) * 1vmin) ;

position: absolute;

left: 0;

top: 50%;

transform: translate (-50%, -50%) rotateY (90deg) ;

}

.cuboid__side: nth-of-type (5) {

background: var (—shade-three) ;

height: 100%;

width: 100%;

transform: translate3d (0, 0, calc (var (—thickness) * 0.5vmin));

position: absolute;

top: 0;

left: 0;

}

.cuboid__side: nth-of-type (6) {

background: var (—shade-one) ;

height: 100%;

width: 100%;

transform: translate3d (0, 0, calc (var (—thickness) * -0.5vmin)) rotateY (180deg) ;

position: absolute;

top: 0;

left: 0;

}

В этом примере мы использовали три оттенка. Но иногда вам может понадобиться больше. Эта демонстрация объединяет это, но позволяет вам изменять настраиваемые свойства области действия. Значение «толщины» изменит экструзию прямоугольного параллелепипеда. Преобразования и размеры повлияют на содержащий элемент с классом «экструзия».

Подмости для принтера

Для начала мы можем собрать все необходимые детали. С практикой это становится более очевидным. Но общее правило состоит в том, чтобы попытаться визуализировать все в виде коробок. Это дает вам хорошее представление о том, как что-то разбить:

.scene

.printer

.printer__side.printer__side—left

.printer__side.printer__side—right

.printer__tray.printer__tray—bottom

.printer__tray.printer__tray—top

.printer__top

.printer__back

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

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

.scene

.printer

.printer__side.printer__side—left

+cuboid () (class="cuboid--side")

.printer__side.printer__side—right

+cuboid () (class="cuboid--side")

.printer__tray.printer__tray—bottom

+cuboid () (class="cuboid--tray")

.printer__tray.printer__tray—top

+cuboid () (class="cuboid--tray")

.printer__top

+cuboid () (class="cuboid--top")

.printer__back

+cuboid () (class="cuboid--back")

Обратите внимание, как мы можем повторно использовать имена классов, такие как cuboid—side. Эти кубоиды, вероятно, будут иметь одинаковую толщину и использовать одни и те же цвета. Их положение и размер определяются содержащим элементом.

Собрав все вместе, мы можем получить что-то вроде этого.

В демо-версии показаны различные кубоиды, из которых состоит принтер. Если вы отключите экструзию, вы увидите плоскость, содержащую элементы.

Добавление некоторых деталей

Теперь вы, возможно, заметили, что деталей больше, чем если бы вы просто добавляли цвета с каждой стороны. И это сводится к поиску способов добавить дополнительные детали. У нас есть разные варианты в зависимости от того, что мы хотим добавить.

Если это изображение или некоторые базовые изменения цвета, мы можем использовать background-imageградиенты для наложения слоев и так далее.

Например, верхняя часть принтера имеет детали, а также отверстие принтера. Этот код относится к верхней стороне верхнего прямоугольного параллелепипеда. Градиент обрабатывает открытие принтера и детали:

.cuboid—top {

—thickness: var (—depth) ;

shade-one: linear-gradient (#292929, #292929) 100% 50%/14% 54% no-repeat, linear-gradient (var (—p−7), var (—p−7)) 40% 50%/12% 32% no-repeat, linear-gradient (var (—p−7), var (—p−7)) 30% 50%/2% 12% no-repeat, linear-gradient (var (—p−3), var (—p−3)) 0% 50%/66% 50% no-repeat, var (—p−1) ;

}

Для логотипа медведя мы могли бы использовать псевдоэлемент background-imageили даже дотянуться до него и расположить его:

.cuboid—top > div: nth-of-type (1): after {

content: '';

position: absolute;

top: 7%;

left: 10%;

height: calc (var (—depth) * 0.12vmin) ;

width: calc (var (—depth) * 0.12vmin) ;

background: url («https: //assets.codepen.io/605876/avatar.png») ;

background-size: cover;

transform: rotate (90deg) ;

filter: grayscale (0.5) ;

}

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

.printer__top

.cuboid.cuboid—top

.cuboid__side

.cuboid__side

.cuboid__side

.cuboid__side

.screen

.screen__preview

img.screen__preview-img

.cuboid__side

.cuboid__side

Добавьте еще немного деталей, и мы готовы добавить немного бумаги!

Бумажное путешествие

Какой принтер без бумаги? Мы хотим анимировать, как бумага летит в принтер и вылетает с другого конца. Что-то вроде этой демонстрации: щелкните в любом месте, чтобы увидеть лист бумаги, загруженный в принтер и распечатанный.

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

.paper-stack.paper-stack—bottom

+cuboid () (class="cuboid--paper")

.paper-stack.paper-stack—top

.cuboid.cuboid—paper

.cuboid__side

.paper

.paper__flyer

.cuboid__side

.cuboid__side

.cuboid__side

.cuboid__side

.cuboid__side

Но анимация бумаги, летящей в принтер, требует проб и ошибок. Целесообразно поиграть с различными преобразованиями в инспекторе DevTools. Это хороший способ увидеть, как все будет выглядеть. Часто также проще использовать элементы-оболочки. Мы используем.paperэлемент для передачи, а затем используем.paper__flyerдля анимации подачи бумаги:

: root {

load-speed: 2;

}

.paper-stack—top.cuboid—paper.paper {

animation: transfer calc (var (—load-speed) * 0.5s) ease-in-out forwards;

}

.paper-stack—top.cuboid—paper.paper__flyer {

animation: fly calc (var (—load-speed) * 0.5s) ease-in-out forwards;

}

.paper-stack—top.cuboid—paper.paper__flyer: after {

animation: feed calc (var (—load-speed) * 0.5s) calc (var (—load-speed) * 0.5s) forwards;

}

@keyframes transfer {

to {

transform: translate (0, -270%) rotate (22deg) ;

}

}

@keyframes feed {

to {

transform: translate (100%, 0) ;

}

}

@keyframes fly {

0% {

transform: translate3d (0, 0, 0) rotateY (0deg) translate (0, 0) ;

}

50% {

transform: translate3d (140%, 0, calc (var (—height) * 1.2)) rotateY (-75deg) translate (180%, 0) ;

}

100% {

transform: translate3d (140%, 0, var (—height)) rotateY (-75deg) translate (0%, 0) rotate (-180deg) ;

}

}

Вы заметите, что там довольно много calcиспользования. Чтобы составить временную шкалу анимации, мы можем использовать пользовательские свойства CSS. Ссылаясь на свойство, мы можем рассчитать правильные задержки для каждой анимации в цепочке. Бумага переносится и летит одновременно. Одна анимация обрабатывает перемещение контейнера, другая — вращение бумаги. Как только эти анимации заканчиваются, бумага подается в принтер вместе с feedанимацией. Задержка анимации равна продолжительности первых двух анимаций, которые запускаются одновременно.

Запустите эту демонстрацию, где я раскрасил элементы контейнера в красный и зеленый цвета. Мы используем.paper__flyerпсевдоэлемент, чтобы представить лист бумаги. Но элементы-контейнеры выполняют тяжелую работу:

Вам может быть интересно, когда бумага выходит на другом конце. Но на самом деле бумага не является одним и тем же элементом повсюду. Мы используем один элемент для входа в принтер. И еще элемент для бумаги, когда она вылетает из принтера. Это еще один случай, когда дополнительные элементы облегчают нашу жизнь.

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

Опять же, это требует проб и ошибок, а также размышлений о том, как мы можем использовать элементы контейнера. Наличие контейнера со смещением transform-originпозволяет нам создать цикл.

Печать

У нас все на месте. Теперь дело за тем, чтобы что-то напечатать. Для этого мы добавим форму, которая позволяет пользователям передавать URL-адрес изображения:

form.customer-form

label (for="print") Print URL

input#print (type='url’ required placeholder="URL for Printing")

input (type="submit" value="Print")

С некоторой стилизацией мы получаем что-то вроде этого.

Собственное поведение форм и использование requiredи type="url"означает, что мы принимаем только URL-адрес. Мы могли бы пойти дальше patternи проверить определенные типы изображений. Но некоторые хорошие URL-адреса для случайных изображений не включают тип изображения, например https://source.unsplash.com/random.

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

Когда мы отправляем форму, мы можем запросить URL-адрес, а затем установить srcдля изображений в нашей сцене — одно изображение будет предварительным просмотром экрана на принтере, а другое — изображением на одной стороне бумаги. На самом деле, когда мы будем печатать, мы будем добавлять новый элемент для каждого напечатанного листа бумаги. Таким образом, каждый отпечаток выглядит так, как будто он добавлен в стопку. Мы можем удалить лист бумаги, который у нас есть на загрузке.

Начнем с обработки отправки формы. Мы собираемся предотвратить событие по умолчанию и вызвать PROCESSфункцию:

const PRINT = e => {

e.preventDefault ()

PROCESS ()

}

const PRINT_FORM = document.querySelector ('form’)

PRINT_FORM.addEventListener ('submit’, PRINT)

Эта функция будет обрабатывать запрос на наш источник изображения:

let printing = false

const PREVIEW = document.querySelector ('img.screen__preview-img’)

const SUBMIT = document.querySelector ('[type="submit"]')

const URL_INPUT = document.querySelector ('[type="url"]')

const PROCESS = async () => {

if (printing) return

printing = true

SUBMIT.disabled = true

const res = await fetch (URL_INPUT.value)

PREVIEW.src = res.url

URL_INPUT.value = ''

}

Мы также устанавливаем printingпеременную в true, которую мы будем использовать для отслеживания текущего состояния, и отключаем кнопку формы.

Почему мы делаем запрос на изображение вместо того, чтобы установить его на изображение? Нам нужен абсолютный URL-адрес изображения. Если мы используем упомянутый выше URL-адрес «Unsplash», а затем делимся им между изображениями, это может не сработать. Это потому, что мы можем столкнуться со сценариями, в которых у нас отображаются разные изображения.

Как только у нас есть источник изображения, мы устанавливаем источник изображения предварительного просмотра на этот URL-адрес и сбрасываем входное значение формы.

Чтобы запустить анимацию, мы можем подключиться к событию загрузки нашего изображения предварительного просмотра. Когда событие срабатывает, мы создаем новый элемент для листа бумаги для печати и добавляем его к printerэлементу. В то же время мы добавляем printingкласс к нашему принтеру. Мы можем использовать это для запуска первой части нашей бумажной анимации:

PREVIEW.addEventListener ('load’, () => {

PRINTER.classList.add ('printing’)

const PRINT = document.createElement ('div’)

PRINT.className = 'printed’

PRINT.innerHTML = `

<img class="printed__image" src=${PREVIEW.src}/>

PRINTER.appendChild (PRINT)

// After a set amount of time reset the state

setTimeout (() => {

printing = false

SUBMIT.removeAttribute ('disabled’)

PRINTER.classList.remove ('printing’)

}, 4500)

})

Через определенное время мы можем сбросить состояние. Альтернативным подходом может быть подавление всплывающего animationendсобытия. Но мы можем использовать setTimeout, так как знаем, сколько времени займет анимация.

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

.printed__image {

height: 100%;

width: 100%;

object-fit: cover;

}

Также было бы неплохо, если бы индикаторы на передней панели принтера сообщали о том, что принтер занят. Мы могли бы настроить оттенок одного из индикаторов, когда принтер печатает:

.progress-light {

background: hsla (var (—progress-hue, 104), 80%, 50%) ;

}

.printing {

progress-hue: 10; /* Equates to red */

}

Соедините это вместе, и мы получим «рабочий» принтер, созданный с помощью CSS и небольшого количества JavaScript.

Это оно!

Мы рассмотрели, как можно создать функциональный 3D-принтер с помощью CSS, небольшого количества JavaScript и использования Pug. Попробуйте добавить следующую ссылку на изображение в поле URL-адреса или любую другую по вашему выбору, и попробуйте!

Для этого мы рассмотрели множество различных вещей, в том числе следующие:

как сделать 3D вещи с помощью CSS

использование миксинов Pug

использование настраиваемых свойств CSS с ограниченной областью действия, чтобы все было СУХИМ

использование экструзии для создания 3D-сцен

обработка форм с помощью JavaScript

составление временных шкал анимации с настраиваемыми свойствами

Удовольствие от создания этих демонстраций заключается в том, что многие из них ставят различные проблемы, которые необходимо решить, например, как создавать определенные формы или создавать определенные анимации. Часто есть более одного способа сделать что-то.

Какие классные вещи вы могли бы сделать с помощью 3D CSS? Я бы хотел увидеть!

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая