Страницы

Поиск по вопросам

среда, 27 ноября 2019 г.

Как создать 3D сферу в CSS?

#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.

    Sphere styles


    Ответ 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

Комментариев нет:

Отправить комментарий