#javascript #css #html5 #анимация #css_animation
Я пытался создать 3D сферу, используя только чистый CSS, но я не смог сгенерировать требуемую форму. Я видел цилиндр , но я не могу найти ссылку на создание реальной сферы. .red { background-color: red; } .green { background-color: green; } .blue { background-color: blue; } .yellow { background-color: yellow; } .sphere { height: 200px; width: 200px; border-radius: 50%; text-align: center; vertical-align: middle; font-size: 500%; position: relative; box-shadow: inset -10px -10px 100px #000, 10px 10px 20px black, inset 0px 0px 10px black; display: inline-block; margin: 5%; } .sphere::after { background-color: rgba(255, 255, 255, 0.3); content: ''; height: 45%; width: 12%; position: absolute; top: 4%; left: 15%; border-radius: 50%; transform: rotate(40deg); } тем не мение: A: это просто 2D круги, а не 3D фигуры B: Я не могу повернуть их в 3d (я хочу иметь вращающееся изображение), подобное изображению шара.
Ответы
Ответ 1
Я пытался. Не совсем понял о каких неокрашенных сегментах шла речь. Сама карта, которая накладывалась в том примере имела альфа-канал по бокам. Очень сложно (настолько, что можно сказать, невозможно) будет написать алгоритм накладывания овальной карты, но это все я заметил слишком поздно и к этому моменту уже написал бОльшую часть кода сферы. Не сказать, что получилось чем-то лучше, но хоть геометрию вспомнил :) Код полностью мой, к @ZulNs не подглядывал, если необходимо - дам комментарий по любой интересующей строке. Для веселья добавил выбор карт :) Собственно вот: class Sphere { constructor(props) { props = (props.constructor.name === "Object" ? props : {}); this.element = (props.element instanceof HTMLElement ? props.element : document.body); this.radius = (typeof props.radius === "number" ? props.radius : 100); this.polygonsPerMeridian = (typeof props.polygonsPerMeridian === "number" ? props.polygonsPerMeridian : 15); this.texture = (typeof props.texture === "string" ? props.texture : "none"); this.rotate = (typeof props.rotate === "boolean" ? props.rotate : true); this.rotationTime = (typeof props.rotationTime === "number" ? props.rotationTime : 10); this.diameter = this.radius * 2; this.polygonSize = Math.ceil(this.radius * (2 * Math.tan(Math.PI / ((this.polygonsPerMeridian - 1) * 2)))); this.parts = { sphere: null, meridians: [], polygons: [] }; this.handlers = { createSphereElement: () => { let sphere = document.createElement("div"); sphere.classList.add("sphere"); sphere.style.width = `${this.diameter}px`; sphere.style.height = `${this.diameter}px`; this.rotate && (sphere.style.animation = `rotate linear ${this.rotationTime}s infinite`); this.parts.sphere = sphere; return sphere; }, createMeridianElement: () => { let meridian = document.createElement("div"); meridian.classList.add("meridian"); this.parts.meridians.push(meridian) return meridian; }, createPolygonElement: (m, p) => { let x = this.radius * Math.cos((p * (Math.PI * 2)) / (this.polygonsPerMeridian - 1)); let scaleXK = (1 - (.2 * (100 - (((this.radius - x) * 100) / (this.radius * 2))) / 100)); let polygon = document.createElement("div"); polygon.classList.add("polygon"); polygon.style.backgroundImage = `url('${this.texture}')`; polygon.style.backgroundPosition = `${-m * this.polygonSize}px ${-(p * this.polygonSize)}px`; polygon.style.backgroundSize = `${((this.polygonsPerMeridian - 1) * 2) * this.polygonSize}px ${this.polygonsPerMeridian * this.polygonSize}px`; polygon.style.backgroundColor = `rgb(${0}, ${0}, ${(p * 255) / this.polygonsPerMeridian})`; polygon.style.transformOrigin = `center center ${-this.radius}px`; polygon.style.width = `${this.polygonSize}px`; polygon.style.height = `${this.polygonSize}px`; polygon.style.transition = "all 1s ease-in-out"; polygon.style.transform = `translateX(${((this.diameter / 2) - (this.polygonSize / 2))}px) translateY(${((this.diameter / 2) - (this.polygonSize / 2))}px) translateZ(${this.radius}px) rotateY(${(m * (180 * 2)) / ((this.polygonsPerMeridian - 1) * 2)}deg) rotateZ(${((p * 180) / (this.polygonsPerMeridian - 1)) - 90}deg) rotate3d(0, 1, 0, 90deg) scaleX(${scaleXK})`; return polygon; }, renderSphere: () => { let sphere = this.handlers.createSphereElement(); for (let m = 0; m < ((this.polygonsPerMeridian - 1) * 2); m++) { let meridian = this.handlers.createMeridianElement(); for (let p = 0; p < this.polygonsPerMeridian; p++) { let polygon = this.handlers.createPolygonElement(m, p); this.parts.polygons.push(polygon); meridian.appendChild(polygon); sphere.appendChild(meridian); } } this.element.appendChild(sphere); } } this.handlers.renderSphere(); } setTexture(texture) { this.texture = (typeof texture === "string" ? texture : "none"); this.parts.polygons.forEach(item => item.style.backgroundImage = `url('${texture}')`); } } new Sphere({ element: document.body, radius: 150, polygonsPerMeridian: 13, texture: "https://cdn.thinglink.me/api/image/743786736932356097/1240/10/scaletowidth", rotate: true, rotationTime: 10, }); new Sphere({ element: document.body, radius: 50, polygonsPerMeridian: 13, texture: "https://static-2.gumroad.com/res/gumroad/5387571460549/asset_previews/b455aaa72d1482e171f0558c2766cd48/retina/Mars_2k_Color_Preview_v001.jpg", rotate: true, rotationTime: 20, }); new Sphere({ element: document.body, radius: 30, polygonsPerMeridian: 13, texture: "https://static-2.gumroad.com/res/gumroad/5387571460549/asset_previews/d7c3b214de778aa83aa0a0ab32eec4c1/retina/Moon_2k_Preview.jpg", rotate: true, rotationTime: 5, }); body { padding: 10px; display: flex; flex-wrap: wrap; } .sphere { position: relative; transform-style: preserve-3d; } .meridian { transform-style: preserve-3d; } .polygon { position: absolute; margin: auto; background-repeat: no-repeat; backface-visibility: hidden; } @keyframes rotate { from { transform: rotate3d(1, 1, 0, 0deg); } to { transform: rotate3d(1, 1, 0, 360deg); } } На вход радиус, кол-во полигонов на один меридиан и картинка, желательно прямоугольная, без альфа-каналов. Если на вход будет квадратная картинка то она растянется до нужных размеров. Минусы: У полюсов, там, где искажение карты наиболее сильное, полигоны накладываются друг на друга ибо не имеют "меридианной" перспективы. В коде это частично решается при помощи сложно формулы расчета scaleX полигона ближе к полюсам (scaleXK), но и это не помогает в полной мере. Это самый основной минус, но думаю я продолжу работать над скриптом и поправлю это дело. Иногда появляются щели между полигонами. Скорей всего это проблема позиционирования элементов браузером, т.к. формулы расчета размера полигонов правильные. Хотфикс: добавление нескольких пикселей к размеру полигона. Плюсы: Код довольно компактный, если опустить кучу строк, которые уходят на создание селекта и на хранение ссылок карт в параметрах. Масштабируемость Анимацией занимается графический ускоритель В идеале можно превратить весь код в класс, чтобы можно было создавать кучу сфер с разными параметрами. Буду рад, если более опытные программисты тоже помогут в устранение минусов. UPD 10.02.2019: Реализовал классы. Теперь удобно создавать новые сферы, можете оценить на примере 3-х планет с разными временем вращения, размером и детализацией. UPD 11.02.2019: Добавил новы метод setTexture(textureSrc). С его помощью можно плавно менять текстуру "на лету". Оформил небольшой репозиторий на GitHub.Ответ 2
Чисто в CSS никак, но из той же серии, что Вы прислали с цилиндром как-то так (источник): $(function() { var el = document.createElement('div'), transformProps = 'transform WebkitTransform MozTransform OTransform msTransform'.split(' '), transformProp = support(transformProps); function support(props) { for (var i = 0, l = props.length; i < l; i++) { if (typeof el.style[props[i]] !== "undefined") { return props[i]; } } } var $sphere = $('#sphere'), sphere = { rounds: 8, panels: 24, panelWidth: 100, el: $sphere.find('.container'), build: function(p, r) { var panels = p || this.panels, rounds = r || this.rounds, rotationPerPanel = 360 / panels, rotationPerRound = 360 / 2 / rounds, yRotation, xRotation, width = this.panelWidth, zTranslate = (width / 2) / Math.tan(rotationPerPanel * Math.PI / 180), $container = this.el, $ul, $li, i, j; this.el.html(''); for (i = 0; i < rounds; i++) { $ul = $('
- ');
xRotation = rotationPerRound * i;
$ul[0].style[transformProp] = "rotateX(" + xRotation + "deg)";
for (j = 0; j < panels; j++) {
$li = $('
- ');
yRotation = rotationPerPanel * j;
$li[0].style[transformProp] = "rotateY(" + yRotation + "deg) translateZ("
+ zTranslate + "px)";
$ul.append($li);
}
$container.append($ul);
}
}
},
mouse = {
start: {}
},
touch = document.ontouchmove !== undefined,
viewport = {
x: 0,
y: 0,
el: $('#sphere .container')[0],
move: function(coords) {
if (coords) {
if (typeof coords.x === "number") this.x = coords.x;
if (typeof coords.y === "number") this.y = coords.y;
}
this.el.style[transformProp] = "rotateX(" + this.x + "deg) rotateY(" + this.y
+ "deg)";
},
reset: function() {
this.move({
x: 0,
y: 0
});
}
};
sphere.build();
$(document).keydown(function(evt) {
switch (evt.keyCode) {
case 37: // left
viewport.move({
y: viewport.y - 90
});
break;
case 38: // up
evt.preventDefault();
viewport.move({
x: viewport.x + 90
});
break;
case 39: // right
viewport.move({
y: viewport.y + 90
});
break;
case 40: // down
evt.preventDefault();
viewport.move({
x: viewport.x - 90
});
break;
case 27: //esc
viewport.reset();
break;
default:
break;
};
}).bind('mousedown touchstart', function(evt) {
delete mouse.last;
evt.originalEvent.touches ? evt = evt.originalEvent.touches[0] : null;
mouse.start.x = evt.pageX;
mouse.start.y = evt.pageY;
$(document).bind('mousemove touchmove', function(event) {
// Only perform rotation if one touch or mouse (e.g. still scale with pinch
and zoom)
if (!touch || !(event.originalEvent && event.originalEvent.touches.length > 1)) {
event.preventDefault();
// Get touch co-ords
event.originalEvent.touches ? event = event.originalEvent.touches[0] : null;
$sphere.trigger('move-viewport', {
x: event.pageX,
y: event.pageY
});
}
});
$(document).bind('mouseup touchend', function() {
$(document).unbind('mousemove touchmove');
});
});
$sphere.bind('move-viewport', function(evt, movedMouse) {
// Reduce movement on touch screens
var movementScaleFactor = touch ? 4 : 1;
if (!mouse.last) {
mouse.last = mouse.start;
} else {
if (forward(mouse.start.x, mouse.last.x) != forward(mouse.last.x, movedMouse.x)) {
mouse.start.x = mouse.last.x;
}
if (forward(mouse.start.y, mouse.last.y) != forward(mouse.last.y, movedMouse.y)) {
mouse.start.y = mouse.last.y;
}
}
viewport.move({
x: viewport.x + parseInt((mouse.start.y - movedMouse.y) / movementScaleFactor),
y: viewport.y - parseInt((mouse.start.x - movedMouse.x) / movementScaleFactor)
});
mouse.last.x = movedMouse.x;
mouse.last.y = movedMouse.y;
function forward(v1, v2) {
return v1 >= v2;
}
});
/* Change sphere style */
$('#controls').bind('submit change', function(evt) {
evt.preventDefault();
$sphere.attr('class', '').addClass($(evt.target).val());
});
});
#sphere {
width: 100px;
height: 100px;
margin: 200px auto;
-webkit-perspective: 800px;
-moz-perspective: 800px;
perspective: 800px;
}
.container {
width: 100px;
height: 100px;
-webkit-transition: -webkit-transform 200ms linear;
-webkit-transform-style: preserve-3d;
-moz-transition: -moz-transform 200ms linear;
-moz-transform-style: preserve-3d;
transition: transform 200ms linear;
transform-style: preserve-3d;
}
.container>ul {
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
width: 100%;
height: 100%;
position: absolute;
}
.container li {
width: 98px;
height: 98px;
position: absolute;
display: block;
background: #000;
border: 1px solid #fff;
opacity: 0.1;
border-radius: 50px;
}
/* Different spheres */
/*************************************************/
/* Square */
#sphere.square li {
border-radius: 0;
}
/* Kaleidoscope */
#sphere.kaleidoscope li {
opacity: 0.9;
-webkit-animation: borderRadius 5s linear infinite alternate;
-moz-animation: borderRadius 5s linear infinite alternate;
animation: borderRadius 5s linear infinite alternate;
}
/* Eye */
#sphere.eye li {
opacity: 0.5;
background: #fff;
}
#sphere.eye li:nth-child(7),
#sphere.eye li:nth-child(8),
#sphere.eye li:nth-child(6) {
background: #000;
opacity: 1;
}
/* Single */
#sphere.single ul:nth-child(n+2) {
opacity: 0;
}
/* Contact */
#sphere.contact ul {
opacity: 0;
}
#sphere.contact ul:nth-child(1),
#sphere.contact ul:nth-child(4) {
opacity: 0.8;
}
#sphere.contact .rotator {
-webkit-animation: spinHorizontal 2s linear infinite;
-moz-animation: spinHorizontal 2s linear infinite;
animation: spinHorizontal 2s linear infinite;
}
#sphere.contact ul:nth-child(1) {
-webkit-animation: spinVerticalReverse 5s linear infinite;
-moz-animation: spinVerticalReverse 5s linear infinite;
animation: spinVerticalReverse 5s linear infinite;
}
#sphere.contact ul:nth-child(4) {
-webkit-animation: spinVertical 5s linear infinite;
-moz-animation: spinVertical 5s linear infinite;
animation: spinVertical 5s linear infinite;
}
.contact .rotator {
-webkit-transition: -webkit-transform 500ms linear;
-webkit-transform-style: preserve-3d;
-moz-transition: -moz-transform 500ms linear;
-moz-transform-style: preserve-3d;
transition: transform 500ms linear;
transform-style: preserve-3d;
}
/* Half */
#sphere.half li {
background: #000;
}
#sphere.half li:nth-child(n+12),
#sphere.half ul:nth-child(n+4) li:nth-child(1) {
display: none;
}
/* Animations */
/*************************************************/
@-webkit-keyframes spinHorizontal {
from {
-webkit-transform: rotateY(0deg);
}
to {
-webkit-transform: rotateY(360deg);
}
}
@-webkit-keyframes spinVertical {
from {
-webkit-transform: rotateX(0deg);
}
to {
-webkit-transform: rotateX(360deg);
}
}
@-webkit-keyframes spinVerticalReverse {
from {
-webkit-transform: rotateX(360deg);
}
to {
-webkit-transform: rotateX(0deg);
}
}
@-webkit-keyframes borderRadius {
from {
border-radius: 5px;
}
to {
border-radius: 50px;
}
}
@-moz-keyframes spinHorizontal {
from {
-moz-transform: rotateY(0deg);
}
to {
-moz-transform: rotateY(360deg);
}
}
@-moz-keyframes spinVertical {
from {
-moz-transform: rotateX(0deg);
}
to {
-moz-transform: rotateX(360deg);
}
}
@-moz-keyframes spinVerticalReverse {
from {
-moz-transform: rotateX(360deg);
}
to {
-moz-transform: rotateX(0deg);
}
}
@-moz-keyframes borderRadius {
from {
border-radius: 5px;
}
to {
border-radius: 50px;
}
}
@keyframes spinHorizontal {
from {
transform: rotateY(0deg);
}
to {
transform: rotateY(360deg);
}
}
@keyframes spinVertical {
from {
transform: rotateX(0deg);
}
to {
transform: rotateX(360deg);
}
}
@keyframes spinVerticalReverse {
from {
transform: rotateX(360deg);
}
to {
transform: rotateX(0deg);
}
}
@keyframes borderRadius {
from {
border-radius: 5px;
}
to {
border-radius: 50px;
}
}
.test p {
text-align: center;
}
#controls {
position: absolute;
left: 48px;
top: 48px;
}
#controls fieldset {
border: 1px dotted #000;
padding: 0.5em 1em 0 0.5em;
}
#controls label {
font-weight: normal;
}
#controls div {
margin: 0 0 0.5em;
}
#controls input {
vertical-align: baseline;
}
A sphere built with CSS 3D transforms — Paul Hayes Click and drag, use touch gestures or arrow keys.
Ответ 3
Ответ ниже не является реальной 3D-формой. Это дает лишь небольшую иллюзию трехмерности, однако, в зависимости от вашего варианта использования, вы можете его подрегулировать: html,body{margin:0;padding:0;background:#222;} div{ height:300px; width:300px; background:url(http://lorempixel.com/300/300); border-radius:50%; animation:spin 3s linear infinite; transform:rotate(-15deg); position:relative; } div:before{ content:""; position:absolute; bottom:-50px; border-radius:50%; left:0; height:10%; width:100%; transform:rotate(15deg); background:rgba(0,0,0,0.6); box-shadow: 0 0 10px 2px rgba(0,0,0,0.6); } div:after{ content:""; position:absolute;z-index:12; top:0;left:0;height:100%;width:100%;border-radius:50%; box-shadow:inset -20px -20px 20px 2px #222, inset 20px 20px 20px 5px rgba(200,200,200,0.4); } @keyframes spin{ to{background-position:-300px 0;} } Анимируя background-position div, и используя тени box shadows, вы можете «имитировать» затенение трехмерной фигуры.Ответ 4
Решение JS Я использую JavaScript для построения сферы, которая состоит из множества элементов div. Чтобы поддерживать производительность браузера, элементы div делаются как можно меньше. var DIAMETER = 200; var CELLS_PER_CIRCLE = 26; var IMG_CELL = 'https://sites.google.com/site/zulnasibu/sphere/earth.png'; var NAME = 'sphere'; var WRAP = NAME + '-wrapper'; var _cssRules = ''; var _cellW; var _cellAmount = 0; var _imgW; var _imgH; function createFace(w, h, rx, ry, tz, ts, tsx, tsy, cname) { var face = document.createElement("div"); var css; var cssText = 'width: ' + w.toFixed(2) + 'px;' + 'height: ' + h.toFixed(2) + 'px;' + 'margin-left: ' + (-w / 2).toFixed(2) + 'px;' + 'margin-top: ' + (-h / 2).toFixed(2) + 'px;' + 'background: url("' + ts + '") ' + tsx.toFixed(2) + 'px ' + tsy.toFixed(2) + 'px;'; css = 'transform: rotateY(' + ry.toFixed(2) + 'rad) rotateX(' + rx.toFixed(2) + 'rad) translateZ(' + tz.toFixed(2) + 'px);'; cssText += addVendorPrefix(css); face.className = cname; face.style.cssText = cssText; return face; } function createModel() { var wrap = document.createElement("div"); var model = document.createElement("div"); wrap.className = WRAP; model.className = NAME; if (CELLS_PER_CIRCLE % 2 != 0) CELLS_PER_CIRCLE++; if (CELLS_PER_CIRCLE < 4) CELLS_PER_CIRCLE = 4; var baseAngle = Math.PI / CELLS_PER_CIRCLE; var cellAngle = 2 * baseAngle; _cellW = DIAMETER * Math.tan(baseAngle); _imgW = _cellW * CELLS_PER_CIRCLE; _imgH = CELLS_PER_CIRCLE / 2; if (CELLS_PER_CIRCLE % 4 == 0) _imgH++; _imgH *= _cellW; var xc = Math.ceil(CELLS_PER_CIRCLE / -4); var yc, rx, ry, tx, ty = -_imgH, tw, cang, cdia, cw; for (var x = xc; x <= -xc; x++) { rx = x * cellAngle; cw = _cellW; yc = CELLS_PER_CIRCLE; if (Math.abs(rx) == Math.PI / 2) yc = 1; else if (Math.abs(x) != 1) { cang = rx - Math.sign(x) * cellAngle / 2; cdia = DIAMETER * Math.cos(cang); cw = cdia * Math.tan(baseAngle); } _cellAmount += yc; tw = cw * yc; tx = (tw - _imgW) / 2; ty += _cellW; for (var y = 0; y < yc; y++) { ry = y * cellAngle; model.appendChild(createFace(cw + 1, _cellW + 1, rx, ry, DIAMETER / 2, IMG_CELL, tx, ty, 'cell' + x.toString() + y.toString())); tx -= cw; } } wrap.appendChild(model); var style = document.createElement('style'); style.type = 'text/css'; if (style.styleSheet) style.styleSheet.cssText = _cssRules; else style.innerHTML = _cssRules; document.head.appendChild(style); return wrap; } function addVendorPrefix(property) { return '-webkit-' + property + '-moz-' + property + '-o-' + property + property; } function showGeometry(elm) { if (elm.checked) document.querySelector('.sphere').classList.add('show-geometry'); else document.querySelector('.sphere').classList.remove('show-geometry'); } document.body.appendChild(createModel()); .sphere-wrapper { position: absolute; top: 50%; left: 50%; -webkit-perspective: 1000px; -moz-perspective: 1000px; -o-perspective: 1000px; perspective: 1000px; } .sphere { position: absolute; -webkit-transform-style: preserve-3d; -moz-transform-style: preserve-3d; -o-transform-style: preserve-3d; transform-style: preserve-3d; -webkit-transform-origin: center center -100px; -moz-transform-origin: center center -100px; -o-transform-origin: center center -100px; transform-origin: center center -100px; -webkit-animation: spin 60s infinite linear; -moz-animation: spin 60s infinite linear; -o-animation: spin 60s infinite linear; animation: spin 60s infinite linear; } .sphere div { position: absolute; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; backface-visibility: hidden; } @-webkit-keyframes spin { 010.00% {-webkit-transform: rotateX( 0deg) rotateY( 360deg) rotateZ( 0deg);} 020.00% {-webkit-transform: rotateX( 360deg) rotateY( 360deg) rotateZ( 0deg);} 030.00% {-webkit-transform: rotateX( 720deg) rotateY( 720deg) rotateZ( 0deg);} 100.00% {-webkit-transform: rotateX(2880deg) rotateY(3240deg) rotateZ(2520deg);} } @-moz-keyframes spin { 010.00% {-moz-transform: rotateX( 0deg) rotateY( 360deg) rotateZ( 0deg);} 020.00% {-moz-transform: rotateX( 360deg) rotateY( 360deg) rotateZ( 0deg);} 030.00% {-moz-transform: rotateX( 720deg) rotateY( 720deg) rotateZ( 0deg);} 100.00% {-moz-transform: rotateX(2880deg) rotateY(3240deg) rotateZ(2520deg);} } @-o-keyframes spin { 010.00% {-o-transform: rotateX( 0deg) rotateY( 360deg) rotateZ( 0deg);} 020.00% {-o-transform: rotateX( 360deg) rotateY( 360deg) rotateZ( 0deg);} 030.00% {-o-transform: rotateX( 720deg) rotateY( 720deg) rotateZ( 0deg);} 100.00% {-o-transform: rotateX(2880deg) rotateY(3240deg) rotateZ(2520deg);} } @keyframes spin { 010.00% {transform: rotateX( 0deg) rotateY( 360deg) rotateZ( 0deg);} 020.00% {transform: rotateX( 360deg) rotateY( 360deg) rotateZ( 0deg);} 030.00% {transform: rotateX( 720deg) rotateY( 720deg) rotateZ( 0deg);} 100.00% {transform: rotateX(2880deg) rotateY(3240deg) rotateZ(2520deg);} } input, input~ label { cursor: pointer; } input:checked~ label { color: #f77; } .show-geometry div { background: rgba(160, 160, 160, 0.5) !important; border: 1px solid #333; -webkit-backface-visibility: visible; -moz-backface-visibility: visible; -o-backface-visibility: visible; backface-visibility: visible; } Источник ответа: @ZulNs
Комментариев нет:
Отправить комментарий