Облака на CSS3 3D Transitions

в 4:55, , рубрики: clouds, css, css3, html, javascript, не тегов, облако

Пример, как нарисовать красивые 3D облака используя CSS 3D Transformations

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

Для нетерпеливых: То, что получится в итоге

Вступление

В этой статье, автор попытаеся рассказать и показать как создать красивые 3D облака используя CSS3 Transformations. Автор так-же считает, что иметь базовое понятие о том, как работают эти самые CSS 3 Transformations не будет лишним. Ссылка на туториал здесь

Данный туториал разбит на несколько простых шагов. Каждый шаг основан на коде из предыдущего и содержит ссылки на примеры.

  • Создаем мир и камеру
  • Добавляем объекты в мир
  • Добавляем спрайты к облакам
  • Магия
  • Пролог

Поооеехааали!

1. Создаем мир и добавляем камеру

Для начала нам нужно создать два div-элемента: точку обзора (камера) и мир. Все остальные элементы мы будем создавать динамически используя JavaScript.

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

Мир — это div-блок, который находится внутри блока камеры и в котором будут находится облака.

Это пример HTML разметки, которая должна у нас получится

<div id="viewport" >
	<div id="world" ></div>
</div>

И добавим стили к нашему миру и камере. Очень важно поместить блок, содержащий нашу сцену (мир), по середине внутри камеры, иначе вся сцена будет рисоватся со смещением.

#viewport {
	bottom: 0;
	left: 0;
	overflow: hidden;
	perspective: 400; 
	position: absolute;
	right: 0;
	top: 0;
}

#world {
	height: 512px;
	left: 50%;
	margin-left: -256px;
	margin-top: -256px;
	position: absolute;
	top: 50%;
	transform-style: preserve-3d;
	width: 512px;
}

И немножко кода: мы инициализируем наши объекты, прицепимся к mousemove событию и создадим метод updateView()

/*
    Определим переменные
        world и viewport как DOM элементы
        worldXAngle и worldYAngle с плавающей точкой, которые будут отвечать за вращение мира
        d число, которое отвечает за расстояние между миром и камерой
*/
var world = document.getElementById( 'world' ),
	viewport = document.getElementById( 'viewport' ),
	worldXAngle = 0,
	worldYAngle = 0,
	d = 0;

/*
    Следим за передвижениями мыши и трансформируем координаты в углы, на которые мир будет поворачиватся
    от -180 до 180 градусов, вертикально и горизонтально
*/
window.addEventListener( 'mousemove', function( e ) {
	worldYAngle = -( .5 - ( e.clientX / window.innerWidth ) ) * 180;
	worldXAngle = ( .5 - ( e.clientY / window.innerHeight ) ) * 180;
	updateView();
} );

/*
    Следим, если пользователь крутит мышь за колесо и изменяем расстояние d
*/
window.addEventListener( 'mousewheel', onContainerMouseWheel );
window.addEventListener( 'DOMMouseScroll', onContainerMouseWheel ); 
function onContainerMouseWheel( event ) {	
	event = event ? event : window.event;
	d = d - ( event.detail ? event.detail * -5 : event.wheelDelta / 8 );
	updateView();
		
}

/*
    Изменяем CSS3 свойство transform для мира
    
    Changes the transform property of world to be
    двигаем Z ось на расстояние d
    поворачиваем ось X на worldXAngle градусов
    и поворачиваем ось Y на worldYAngle градусов
*/
function updateView() {
	world.style.transform = 'translateZ( ' + d + 'px ) 
		rotateX( ' + worldXAngle + 'deg) 
		rotateY( ' + worldYAngle + 'deg)';
}

Мир у нас будет красным. А у камеры будет градиент, симулирующий небесный свод.
Так-же мы будем следить за передвижениями мыши и менять углы обзора и расстоние между камерой и миром в зависимости от положения этой самой мыши.
%username% подвигай мышкой и узри как мир меняет свою ориентацию в пространстве.

Пример

2. Добавляем объекты к миру

Теперь нам нужно добавить div-блоки, внутри которых будут рисоватся наши облака. Каждый новый блок — новое облако. Мы будем создавать абсолютно спозиционированые блоки относительно мира, но используя CSS3 Transformations вместо left и top.
Изначально все эти объкты будут находися по центру мира. Размер этих объектов значения не имеет, так-как это только обертки для облаков. И лучше бы их поместить по центру относительно мира (установить margin-left и margin-top на минус половину высоты и ширины).

.cloudBase {
	height: 20px;
	left: 256px;
	margin-left: -10px;
	margin-top: -10px;
	position: absolute;
	top: 256px;
	width: 20px;
}

И добавим функции createCloud(), которая будет создавать обертку для облака и добавлять ее в DOM и функцию createCloud(), которая будет собственно генерировать несколько облаков.
Еще мы добавим переменную p, которая отвечает за перспективу камеры (свойство заставляющее объекты, которые находятся дальше от камеры выглядеть меньшими по размеру)

	d = 0,
	p = 400, // Перспектива
	worldXAngle = 0,
	worldYAngle = 0;

	viewport.style.webkitPerspective = p;
	viewport.style.MozPerspective = p;
	viewport.style.oPerspective = p;

/*
    objects служит для хранения всех облаков
    layers нужен для хранения текстур (будем использовать не следующем шаге)
*/
var objects = [],
	layers = [];
/*
    Удаляем все существующие облака
    и генерируем новые
*/
function generate() {
	objects = [];
	layers = [];
	if ( world.hasChildNodes() ) {
		while ( world.childNodes.length >= 1 ) {
			world.removeChild( world.firstChild );       
		} 
	}
	for( var j = 0; j <; 5; j++ ) {
		objects.push( createCloud() );
	}
}

/*
    Создаем placeholder для облака
    который позиционирован случайным образом в мире
    Область для каждой оси от -256 до 256 пикселей
*/
function createCloud() {
var div = document.createElement( 'div'  );
	div.className = 'cloudBase';
	var x = 256 - ( Math.random() * 512 );
	var y = 256 - ( Math.random() * 512 );
	var z = 256 - ( Math.random() * 512 );
	var t = 'translateX( ' + x + 'px ) translateY( ' + y + 'px ) translateZ( ' + z + 'px )';
	div.style.webkitTransform = t;
	div.style.MozTransform = t;
	div.style.oTransform = t;
	world.appendChild( div );
		
	return div;
}

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

Пример тынц

3. Добавляем спрайты к облакам.

Чем дальше в лес, тем интересней! Мы добавим несколько абсолютно спозиционированных .cloudLayer блоков, которые являются спрайтами в каждом из облаков.

.cloudLayer {
	height: 256px;
	left: 50%;
	margin-left: -128px;
	margin-top: -128px;
	position: absolute;
	top: 50%;
	width: 256px;
}

Немножко изменим нашу generateCloud() функцию, теперь она так-же генерирует спрайты внутри облаков.

function createCloud() {
	var div = document.createElement( 'div'  );
	div.className = 'cloudBase';
	var x = 256 - ( Math.random() * 512 );
	var y = 256 - ( Math.random() * 512 );
	var z = 256 - ( Math.random() * 512 );
	var t = 'translateX( ' + x + 'px ) translateY( ' + y + 'px ) translateZ( ' + z + 'px )';
	div.style.webkitTransform = t;
	div.style.MozTransform = t;
	div.style.oTransform = t;
	world.appendChild( div );

	for( var j = 0; j < 5 + Math.round( Math.random() * 10 ); j++ ) {
		var cloud = document.createElement( 'div' );
		cloud.className = 'cloudLayer';

		var x = 256 - ( Math.random() * 512 );
		var y = 256 - ( Math.random() * 512 );
		var z = 100 - ( Math.random() * 200 );
		var a = Math.random() * 360;
		var s = .25 + Math.random();
		x *= .2; y *= .2;
		cloud.data = { 
			x: x,
			y: y,
			z: z,
			a: a,
			s: s
		};
		var t = 'translateX( ' + x + 'px ) translateY( ' + y + 'px ) translateZ( ' + z + 'px ) rotateZ( ' + a + 'deg ) scale( ' + s + ' )';
		cloud.style.webkitTransform = t;
		cloud.style.MozTransform = t;
		cloud.style.oTransform = t;

		div.appendChild( cloud );
		layers.push( cloud );
	}

	return div;
}

Пример

4. Магия

Наконец-то мы будет вершить магию! У нас есть массив layers[], который содержит ссылку на каждый спрайт в нашем мире. Так-же у нас есть worldXAngle и worldYAngle. Теперь сделаем так, что-бы перпендикуляр опущенный их поверхности камеры, был так-же перпендикуляром к любому из наших спрайтов. Другими словами спрайты всегда будут к нам лицом.

В очередной раз отредактируем функцию update()

function update (){
	for( var j = 0; j < layers.length; j++ ) {
		var layer = layers[ j ];
		layer.data.a += layer.data.speed;
		var t = 'translateX( ' + layer.data.x + 'px ) translateY( ' + layer.data.y + 'px ) translateZ( ' + layer.data.z + 'px ) rotateY( ' + ( - worldYAngle ) + 'deg ) rotateX( ' + ( - worldXAngle ) + 'deg ) scale( ' + layer.data.s + ')';
		layer.style.webkitTransform = t;
		layer.style.MozTransform = t;
		layer.style.oTransform = t;
	}

	updateView() ;
}

Пример

Пролог

Вот и все, основная работа почти выполнена. Осталось только добавить текстуры к нашим спрайтам и немножечко подчистить CSS (убрать фоны, рамки и т.д.)
Текстуры можно выбирать абсолютно на любой вкус: облака, грозовые облака, тостеры… Если использовать разные текстуры, то можно добится интереснейших эффектов!

Собственно пример к этому шагу и финальная подчищенная версия

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

Автор данного урока надеется, что потратил время не зря. А еще он разрешает использовать этот код в любых проектах без ограничений. И еще он не против писем на the.spite(at)gmail.com (на английском языке конечно-же)

От переводчика: Стоит обратить внимание на то, что в финальной версии автор использует функцию requestAnimationFrame(), которая является правильным способом рисовать анимации в цикле.

Подписывайтесь, ставьте лайк...

P.S.: Переводчик я некудышней, но я учусь. Все ошибки и замечания присылайте в личку.

Автор: max_mara

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js