недавно учусьthree.jsПопрактиковавшись с проектом в примере, я целый день имитировал крутую периодическую таблицу элементов и внес некоторые изменения в исходную основу. Ниже я объясню этот проект шаг за шагом, что является углублением понимания и позволяет вам делать комментарии.
Потому что я не построил персональный сервер. Вырежьте несколько изображений, чтобы показать вам эффект, который я сделал (большинство из них такие же, как оригинал). Возможно, кто-то уже видел эту классическую анимацию. (Вот оригинальный адрес проекта:три JS.org/examples/ на данный момент…)
Помимо оптимизации макета исходных форм HELIX и GRID, я также создал два пользовательских макета другим способом. Поделюсь со всеми.
Ниже приведен адрес репозитория GitHub.Файл очень простой, просто HTML-файл. Если вы хотите реализовать это вручную или использовать его, вы можете взглянуть. Если вам это нравится, дайте ему звезду, очень признательна (пожалуйста, игнорируйте комментарии в коде, ха-ха).
GitHub.com/Одним предложением try/pro является…
Приступим к разбору этого небольшого проекта
стек технологий
- HTML, CSS3, Javascript
- three.js, tween.js
- Тригонометрические функции
Принцип реализации
- использоватьthree.jsкоторый предоставилCSS3DRendererвизуализатор, черезCSS3Свойства преобразования применяют многоуровневые 3D-преобразования кDOMэлемент. По сути, это обертка элемента DOM, которым можно управлять как объектом Mesh в three.js.DOMэлемент. по существу использоватьCSS3Свойства 3D-анимации. Этот элемент является операцией после преобразованияDOMэлементальpositionиrotationзначения свойств для создания анимации
- Используйте легкую библиотеку анимацииtween«Твин» контрольDOMэлементpositionиrotationПереход значения атрибута.
- Определите каждый из различных наборовDOMэлементальpositionиrotation(Часть макета должна быть определенаrotation) и сохраните его вTHREE.Object3ребенок Дpositionсвойства (или набор воображаемых массивов, которые я подробно объясню позже), а затем используйте 'tween' дляDOMэлементальpositionиrotationПереходам нравятся их сохраненные соответствующие значения свойств.
Нечего сказать, сразу к коду.
HTML-структура
<div id="container">
<!-- 选中菜单结构 start-->
<div id="menu">
<button id="table">TABLE</button>
<button id="sphere">SPHERE</button>
<button id="sphere2">SPHERE2</button>
<button id="plane">PLANE</button>
<button id="helix">HELIX</button>
<button id="grid">GRID</button>
</div>
<!-- end -->
</div>
HTML-часть очень проста, просто панель выбора с шестью кнопками, управляющими переходами, давайте посмотрим, как они выглядят.
#menu {
position: absolute;
z-index: 100;
width: 100%;
bottom: 50px;
text-align: center;
font-size: 32px
}
button {
border: none;
background-color: transparent;
color: rgba( 127, 255, 255, 0.75 );
padding: 12px 24px;
cursor: pointer;
outline: 1px solid rgba( 127, 255, 255, 0.75 );
}
button:hover {
background-color: rgba( 127, 255, 255, 0.5 )
}
button:active {
background-color: rgba( 127, 255, 255, 0.75 )
}
Сначала расположите полосу выбора абсолютно в нижней части окна.50pxздесь, обратите вниманиеz-индекс: 100, установка максимального уровня предотвращаетСобытия наведения и клика перехватываются другими элементами. Затем очистите стиль кнопки по умолчанию и добавьте к нему псевдоклассы :hover и :active, чтобы сделать взаимодействие более ярким.
Эффект следующий:
Затем идет структура и стиль 118 элементов DOM, потому что они динамически создаются в коде JavaScript, здесь я написал структуру одного элемента отдельно.
<div class="element">
<div class="number">1</div>
<div class="symbol">H</div>
<div class="detail">Hydrogen<br>1.00794</div>
</div>
CSS-стили
.element {
width: 120px;
height: 160px;
cursor: default;
text-align: center;
border: 1px solid rgba( 127, 255, 255, 0.25 );
box-shadow: 0 0 12px rgba( 0, 255, 255, 0.5 );
}
.element:hover{
border: 1px solid rgba( 127, 255, 255, 0.75 );
box-shadow: 0 0 12px rgba( 0, 255, 255, 0.75 );
}
.element .number {
position: absolute;
top: 20px;
right: 20px;
font-size: 12px;
color: rgba( 127, 255, 255, 0.75 );
}
.element .symbol {
position: absolute;
top: 40px;
left: 0px;
right: 0;
font-size: 60px;
font-weight: bold;
color: rgba( 255, 255, 255, 0.75 );
text-shadow: 0 0 10px rgba( 0, 255, 255, 0.95 );
}
.element .detail {
position: absolute;
left: 0;
right: 0;
bottom: 15px;
font-size: 12px;
color: rgba( 127, 255, 255, 0.75 );
}
Уведомлениеbox-shadowитекст-тень.Ниже представлен рендеринг
пройти черезbox-shadowиtext-shadowЭлемент DOM имеет трехмерный эффект.
Раздел JavaScriptСначала определяется структура хранения данных из 118 элементов, и здесь используется массив (из-за большого количества других элементов я взял только первые двадцать пять, а полные данные есть в коде на гитхабе)
const table = [
"H", "Hydrogen", "1.00794", 1, 1,
"He", "Helium", "4.002602", 18, 1,
"Li", "Lithium", "6.941", 1, 2,
"Be", "Beryllium", "9.012182", 2, 2,
"B", "Boron", "10.811", 13, 2,
"C", "Carbon", "12.0107", 14, 2,
"N", "Nitrogen", "14.0067", 15, 2,
"O", "Oxygen", "15.9994", 16, 2,
"F", "Fluorine", "18.9984032", 17, 2,
"Ne", "Neon", "20.1797", 18, 2,
"Na", "Sodium", "22.98976...", 1, 3,
"Mg", "Magnesium", "24.305", 2, 3,
"Al", "Aluminium", "26.9815386", 13, 3,
"Si", "Silicon", "28.0855", 14, 3,
"P", "Phosphorus", "30.973762", 15, 3,
"S", "Sulfur", "32.065", 16, 3,
"Cl", "Chlorine", "35.453", 17, 3,
"Ar", "Argon", "39.948", 18, 3,
"K", "Potassium", "39.948", 1, 4,
"Ca", "Calcium", "40.078", 2, 4,
"Sc", "Scandium", "44.955912", 3, 4,
"Ti", "Titanium", "47.867", 4, 4,
"V", "Vanadium", "50.9415", 5, 4,
"Cr", "Chromium", "51.9961", 6, 4,
"Mn", "Manganese", "54.938045", 7, 4
]
Давайте сначала проанализируем эту структуру данных
"H", "Hydrogen", "1.00794", 1, 1,
Всего имеется 118 элементов, и каждый элемент определяет пять частей данных в массиве таблицы, а именно символ (символ), полное английское имя, качество (подробности), столбец (столбец) и строку (строка). ), где элемент расположен в макете таблицы.Я объясню, как его использовать, когда буду создавать доску формы.
let scene, camera, renderer, controls;
const objects = [];
const targets = {
grid: [],
helix: [],
table: [],
sphere: []
};
Здесь определяются некоторые глобальные переменные. сцена, камера, рендерер — это объекты среды three.js, камеры и рендереры. Controls — это библиотека управления, предоставляемая three.js для взаимодействия с пользователем, она очень проста. объекты используются для хранения 118 элементов DOM. Целевой объект содержит четыре значения свойства типа массива, которые используются для хранения подобъектов Object3D с различными набранными целевыми позициями.
Создание и анимация элементов управляютсяВыполняется функция инициализации, и для ее использования используется следующее основное пространство
function init() {
const felidView = 40;
const width = window.innerWidth;
const height = window.innerHeight;
const aspect = width / height;
const nearPlane = 1;
const farPlane = 10000;
const WebGLoutput = document.getElementById('container');
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( felidView, aspect, nearPlane, farPlane );
camera.position.z = 3000;
renderer = new THREE.CSS3DRenderer();
renderer.setSize( width, height );
renderer.domElement.style.position = 'absolute';
WebGLoutput.appendChild( renderer.domElement );
(Возможно, мой код имеет странный отступ, я в основном для развлечения, ха-ха) Этот код создает три основных компонента three.js, сцену, камеру (perspectiveCamera), рендерер. Здесь следует отметить, что значение настройки дальней плоскости отсечения здесь относительно велико, если вы делаете это самостоятельно, вы можете установить его на меньшее значение, чтобы уменьшить потери производительности. Обратите внимание, что здесь используется средство визуализации CSS3D.
Перспектива усеченной камеры
Часть между плоскостями называется усеченным конусом, это просто область съемки камеры. FOV (поле зрения) на картинке — это первый параметр камеры, определяющий размер дальности съемки камеры, аналогичный горизонтальному полю зрения человеческого глаза (более 180 градусов). Параметр аспекта управляет соотношением сторон плоскости проекции камеры (обычно соотношением сторон холста), в основном для предотвращения деформации изображения, потому что изображение на плоскости проецирования в конечном итоге будет отображаться через холст. Обратите внимание, что при использовании модуля рендеринга CSS3D область просмотра дисплея представляет собой элемент div.
let i = 0;
let len = table.length;
for ( ; i < len; i += 5 ) {
const element = document.createElement('div');
element.className = 'element';
element.style.backgroundColor = `rgba( 0, 127, 127, ${ Math.random() * 0.5 + 0.25 } )`;
const number = document.createElement('div');
number.className = 'number';number.textContent = i / 5 + 1;
element.appendChild( number );
const symbol = document.createElement('div');
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );
const detail = document.createElement('div');
detail.className = 'detail';
detail.innerHTML = `${ table[ i + 1 ] }<br/>${ table[ i + 2 ] }`;
element.appendChild( detail );
const object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add( object );
objects.push( object );
}
Этот код создает структуру HTML, которая отображает элементы периодической таблицы и заключает каждый элемент DOM в трехмерный объект с помощью класса THREE.CSS3DObject. Затем случайным образом назначьте положение объекта в интервале (-2000, 2000). Наконец, добавьте объект на сцену и сохраните его в массиве объектов, чтобы позже подготовить анимацию.
Вышеупомянутое завершило создание 118 элементов для части отображения случайным образом назначенного положения. Приступим к созданию данных, необходимых для централизованного набора текста.
макет таблицы
function createTableVertices() {
let i = 0;
for ( ; i < len; i += 5 ) {
const object = new THREE.Object3D();
// [ clumn 18 ]
object.position.x = table[ i + 3 ] * 140 - 1260;
object.position.y = -table[ i + 4 ] * 180 + 1000;
object.position.z = 0;
targets.table.push( object );
}
}
Эта верстка относительно проста.Используя четвертые данные (столбец) и пятые данные (строку) каждого элемента в табличном массиве, вы можете напрямую получить информацию о позиции верстки таблицы, соответствующей каждому элементу, а затем назначить их соответствующему элементу. Он хранится в свойстве object.position (это не обязательно, если это данные типа THREE.Vector3). Наконец, сохраните объект в соответствующем массиве для использования в анимации.
сфера
const objLength = objects.length;
function createSphereVertices() {
let i = 0;
const vector = new THREE.Vector3();
for ( ; i < objLength; ++i ) {
let phi = Math.acos( -1 + ( 2 * i ) / objLength );
let theta = Math.sqrt( objLength * Math.PI ) * phi;
const object = new THREE.Object3D();
object.position.x = 800 * Math.cos( theta ) * Math.sin( phi );
object.position.y = 800 * Math.sin( theta ) * Math.sin( phi );
object.position.z = -800 * Math.cos( phi );
// rotation object
vector.copy( object.position ).multiplyScalar( 2 );
object.lookAt( vector );
targets.sphere.push( object );
}
}
Честно говоря, я не очень хорошо понимаю этот код, мне всегда кажется, что алгоритм оригинального автора сложен, пожалуйста, выложите код и попросите большого парня проанализировать его. Позже я использовал другие методы, чтобы реализовать своего рода «круг», который не очень красив, но его легко понять. Я начну с того, что скажуvectorРоль этой переменной, которая используется как «целевое местоположение», заключается в использованииobject.lookAt( vector )
Этот метод заставляет объект в этом положении смотреть наvectorНаправление, в котором это происходит, внутри three.js будетobjectПоверните, чтобы «смотреть в сторонуvector’. получит повернутое значение и сохранит его вobjectобъектrotationВ свойстве свойство вращения объекта элемента преобразуется в соответствующее значение в анимации, чтобы заставить его вращаться.
спиральный набор текста
function createHelixVertices() {
let i = 0;
const vector = new THREE.Vector3();
for ( ; i < objLength; ++i ) {
let phi = i * 0.213 + Math.PI;
const object = new THREE.Object3D();
object.position.x = 800 * Math.sin( phi );
object.position.y = -( i * 8 ) + 450;
object.position.z = 800 * Math.cos( phi + Math.PI );
object.scale.set( 1.1, 1.1, 1.1 );
vector.x = object.position.x * 2;
vector.y = object.position.y;
vector.z = object.position.z * 2;
object.lookAt( vector );
targets.helix.push( object );
}
}
Эту верстку легко понять.Прежде всего, давайте посмотрим на алгоритм, который ось Y принимает один за другим, спускаясь в направлении Y. Если оси X и Z не обработаны, он будет располагаться в ряд по оси Y. Потом расскажу как берется этот 0.213
Поскольку всего элементов 118, если вы хотите расположить эти элементы в круг, вы можете использовать две функции на рисунке выше.Я использую функцию синуса.Из рисунка видно, что 118 элементов расположены в четыре круги.Только нужно датьКаждый элемент имеет соответствующий угол.После расчета с помощью Math.sin(угол) или Math.cos(угол), получается четыре набора периодических значений, и элементы располагаются по кругу. 0,213 получается путем вычисления формулы 4 * Math.PI * 2 / 118, так что положение каждого элемента в периодической таблице (здесь, начиная с 0.) умножается на 0,213, чтобы получить соответствующий угол. Используйте этот угол, чтобы получить положение в круге с помощью функции Ортокосм.
сетка типография
function createGridVertices() {
let i = 0;
for ( ; i < objLength; ++i ) {
const object = new THREE.Object3D();
object.position.x = 360 * ( i % 5) - 800;
object.position.y = -360 * ( ( i / 5 >> 0 ) % 5 ) + 700;
object.position.z = -700 * ( i / 25 >> 0 );
targets.grid.push( object );
}
}
Макет сетки в основном использует идею группировки, которая представляет собой сетку 5*5. Компоновка по оси X использует остаток, так что элементы делятся на пять столбцов, а ось Y делится на 5, а затем округляется (мне нравится использовать здесь битовый оператор >>, и Math.floor имеет эффект) . Это делается путем деления элементов на строки, а затем переноса остатка в столбцы. Когда плоскость заполнена 5 * 5 рядами, определите, к какой стороне принадлежит элемент по оси Z.
Вышеуказанные четыре макета являются оригинальными классическими макетами. Оригинальный автор использует для сохранения позиции, где каждый элемент будет слишком низким. Есть также два макета, которые я расширил с помощью этой идеи, ленивый и простой. Давайте сначала посмотрим, как использовать библиотеку анимации движения для выполнения перехода позиции элемента.
const gridBtn = document.getElementById('grid');
const tableBtn = document.getElementById('table');
const helixBtn = document.getElementById('helix');
const sphereBtn = document.getElementById('sphere');
gridBtn.addEventListener( 'click', function() { transform( targets.grid, 2000 )}, false );
tableBtn.addEventListener( 'click', function() { transform( targets.table, 2000 ) }, false );
helixBtn.addEventListener( 'click', function() { transform( targets.helix, 2000 ) }, false );
sphereBtn.addEventListener( 'click', function() { transform( targets.sphere, 2000 ) }, false );
function transform( targets, duration ) {
TWEEN.removeAll();
for ( let i = 0; i < objLength; ++i ) {
let object = objects[ i ];
let target = targets[ i ];
new TWEEN.Tween( object.position )
.to( { x: target.position.x, y: target.position.y, z: target.position.z },
Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
new TWEEN.Tween( object.rotation )
.to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z },
Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
}
// 这个补间用来在位置与旋转补间同步执行,通过onUpdate在每次更新数据后渲染scene和camera
new TWEEN.Tween( {} )
.to( {}, duration * 2 )
.onUpdate( render )
.start();
}
Из обратного вызова привязки событий видно, что при срабатывании другого набора мы передаем соответствующие данные. Затем извлеките данные и передайте данные через tween.js для создания анимации. Вот подробное введение в использование tween.jsGitHub.com/tween is/сумма вопроса…
Этот «твин» вне цикла используется для выполнения функции рендеринга страницы во время переходов анимации. следующее
function render() {
renderer.render( scene, camera );
}
onWindowResizeФункция используется для обновления параметров камеры, размера сцены и повторного рендеринга экрана при масштабировании страницы.
animationпройти черезrequestAnimationFrameЭтот артефакт анимации обновляет «все данные анимации движения» и обновляет контроллер трекбола.
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
render();
}
function animation() {
TWEEN.update();
controls.update();
requestAnimationFrame( animation );
}
Наконец, позвольте мне рассказать о двух видах «оппортунистической типографики», которые я расширил.
const sphere2Btn = document.getElementById('sphere2');
sphere2Btn.addEventListener( 'click', function() { transformSphere2( 2000 ) }, false );
function transformSphere2(duration) {
TWEEN.removeAll();
const sphereGeom = new THREE.SphereGeometry( 800, 12, 11 );
const vertices = sphereGeom.vertices;
const vector = new THREE.Vector3();
for ( let i = 0; i < objLength; ++i ) {
const target = new THREE.Object3D();
target.position.copy(vertices[i]);
vector.copy( target.position ).multiplyScalar( 2 );
target.lookAt( vector );
let object = objects[ i ];
new TWEEN.Tween( object.position )
.to( vertices[i],
Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
new TWEEN.Tween( object.rotation )
.to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
}
new TWEEN.Tween( this )
.to( {}, duration * 2 )
.onUpdate( render )
.start();
}
Принцип всей анимации: создайте целевую позицию для каждого элемента, и верстка, сгенерированная комбинацией этих позиций, является окончательной версткой элемента посредством преобразования положения перехода «анимации». Поэтому я использовал встроенную геометрию three.js напрямую, используя позицию в ее свойстве vertices в качестве целевой позиции (с небольшим ограничением, количество вершин (позиций) в vertices желательно близко к 118). Таким образом, мы можем напрямую создавать интересную типографику с помощью встроенной геометрии, не выполняя математических вычислений.
Это почти то же самое, что я написал здесь.Я новичок, который только начал фронтенд.Я приветствую все советы и критику! Студенты, которым это нравится, могут поставить лайк!