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

1,00
р.
Я пытался создать 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 (я хочу иметь вращающееся изображение), подобное изображению шара.

Ответ
Я пытался.
Не совсем понял о каких неокрашенных сегментах шла речь. Сама карта, которая накладывалась в том примере имела альфа-канал по бокам. Очень сложно (настолько, что можно сказать, невозможно) будет написать алгоритм накладывания овальной карты, но это все я заметил слишком поздно и к этому моменту уже написал бОльшую часть кода сферы.
Не сказать, что получилось чем-то лучше, но хоть геометрию вспомнил :) Код полностью мой, к @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/10caletowidth", rotate: true, rotationTime: 10, }) new Sphere({ element: document.body, radius: 50, polygonsPerMeridian: 13, texture: "https:/tatic-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:/tatic-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.