Как на чистом CSS сверстать граммофонную иглу и рупор?
У меня уже давно созрела идея разработать аудиоплеер для сайтов в виде граммофона. Вращающуюся грампластинку я уже создал, за корпусом тоже особой сложности не предвижу, а вот хотел бы ещё сверстать на CSS иглу с рупором, при чём, так, чтобы с помощью мыши можно было хватать, поднимать и переставлять иглу с одного места на пластинке в другое. Хотелось бы по примерному образцу, как на этих картинках:

Будут ли у кого-то какие-то идеи по этому поводу? Если да, то буду очень благодарен. Прилагаю код эскиза вращающейся пластинки:
let garmony = [1, 2, 5, 7, 9, 17];
for(i = 0; i < 2 * Math.PI; i += 0.015){
let translateZ = 0;
for(j = 0; j < garmony.length; j ++){
translateZ += 0.7 * Math.sin(i * garmony[j]);
}
let d = document.createElement('span'),
x = 100 + Math.floor(100 * Math.sin(i)),
y = 100 + Math.floor(100 * Math.cos(i));
d.style.transform = 'translateX(' + x + 'px) translateY(' + y + 'px) translateZ(' + translateZ + 'px)';
s.append(d);
}
*{
transition: 2s;
}
#c{
transform: translateX(0px) rotateX(70deg) rotateZ(0deg);
transform-style: preserve-3d;
}
#s{
width: 200px;
height: 200px;
transform-style: preserve-3d;
animation: progress 10s linear infinite;
}
span{
position: absolute;
width: 2px;
height: 2px;
border-style: solid;
border-width: 0px;
background: black;
}
@keyframes progress{
from{transform: rotateZ(0deg);}
to{transform: rotateZ(360deg);}
}
<div id=c><div id=s></div></div>
Ответы (3 шт):
по части перемещаемой иглы, или даже головы должен подойти
<label><input type='range' min=1 max=9/></label>
где max=9 лимит доступных к включению треков. в таком случае перетаскивание иглы будет переключать между треками. для поднятия головы конечно чекбокс обернутый в label. либо если охота именно перетаскиванием, снова range но с диапазоном 1-2. по части визуальной верстки головы, хз даже... лень всматриваться, но тут явно ничего сложного, рупор сложнее будет.
расширяющаяся часть рупора - 3+ блока в едином контейнере, повернутом эдак градусов на 40 45 в 2D. + слегка поиграйтесь с перспективой https://webref.ru/css/value/perspective можно еще попробовать таблицу, к каждому столбцу которой будет применена разная перспектива.
выход рупора, с левой картинки - облепить конец перспективной таблицы например скруглёнными кнопками. с правой... опять куча блоков с разными углами поворота и перспективой. либо просто закруглённый блок с фоновой картинкой.
я это пока как то так представляю.
кстати если иглой охота "мотать" трек, можно попробовать привязать её к прогрессбару тега
<audio>
попробуй
<label **for=''**>
Возникло желание покрутить пластинку.
Ваш подход крайне неэффективный, так как создается более 400 элементов. На самом деле, можно просто нарисовать пластинку при помощи CSS и градиентов. Чтобы при вращении было ощущение, что пластинка крутится, можно прибегнуть к ряду хитростей:
- Добавить шумы, чтобы визуально больше текстурных деталек вращалось.
- Немного наклонить пластинку, чтобы она слегка болталась.
- Оформить этикету пластинки.
Ну и конечно, нужно оформить сцену, в которой можно позиционировать 3D-объекты. У меня получилось так (лучше развернуть на всю страницу):
.viewport,
.viewport * {
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.viewport {
width: 100%;
height: 350px;
perspective: 1200px;
position: relative;
overflow: hidden;
}
.scene {
user-select: none;
transform: rotateX(50deg);
}
.scene,
.scene * {
position: absolute;
transform-style: preserve-3d;
backface-visibility: hidden;
}
.table {
width: 900px;
height: 400px;
background-color: #d9a35c;
transform: translate3d(0, 0, -10px);
}
.record {
width: 400px;
height: 400px;
font-family: Times;
}
.first { transform: translate3d(-51%, 0, 0); }
.second { transform: translate3d(51%, 0, 0); }
.vinyl-wrap {
width: 100%;
height: 100%;
}
.vinyl {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: black;
background-image: url(data:image/png;base64,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),
repeating-radial-gradient(#2a292866, #2a292866 1px, #444 2px, #2a292866 3px);
background-size: 20% 20%, 100% 100%;
transform: rotateX(0.25deg);
}
.light {
border-radius: 50%;
width: 100%;
height: 100%;
background: conic-gradient(from 10deg, transparent 0deg, #ffffff44 30deg, transparent 60deg, transparent 180deg, #ffffff66 210deg, transparent 240deg);
-webkit-mask-image: radial-gradient(transparent 25%, black 25.8%);
mask-image: radial-gradient(transparent 25%, black 25.8%);
transform: translate3d(0, 0, 1px);
}
.label {
border: solid 2px #d9a388;
width: 33%;
height: 33%;
border-radius: 50%;
box-shadow: 0 0 0 4px #da5b33;
background: #da5b33;
color: white;
text-align: center;
}
.label:after {
transform-style: preserve-3d;
position: absolute;
border-radius: 50%;
background: black;
width: 14%;
height: 14%;
content: '';
}
.label h1 { top: -6%; }
.label p {
top: 53%;
font-size: 83%;
}
.label.rock {
background: #007ac5;
box-shadow: 0 0 0 8px #ffffff66;
border: none;
}
.record:hover .vinyl-wrap {
transition: 5s;
transform: rotateZ(5turn);
}
<div class="viewport">
<div class="scene">
<div class="table"></div>
<div class="record first">
<div class="vinyl-wrap">
<div class="vinyl">
<div class="label jazz">
<h1>Jazz</h1>
<p>Greatest Hits<br>vol 1</p>
</div>
</div>
</div>
<div class="light"></div>
</div>
<div class="record second">
<div class="vinyl-wrap">
<div class="vinyl">
<div class="label rock">
<h1>Rock</h1>
<p>Greatest Hits<br>vol 2</p>
</div>
</div>
</div>
<div class="light"></div>
</div>
</div>
</div>
UPD
Надоело смотреть, как вертится пластинка. Хочу иголкой управлять...
Конструкция суппорта, который держит иглу, имеет две степени свободы. Это как рука робота. Имея 3д-сцену, реализовать это очень просто. Главное, хорошо представлять себе точки, относительно которых вращаются объекты. Чтобы сочленить "фаланги" нужно просто вкладывать одну в другую, тогда дочерний элемент будет учитывать трансформации родителя.

Первый угол задаёт положение иглы на пластинке, второй позволяет поднять иглу над ней.
Осталось реализовать простой drag-n-drop, а значение первого угла синхронизировать с позицией воспроизведения тега <audio>, не забыв события play и pause.
Я нарисовал это очень примитивно, обычными дивами. Можно все элементы построить на базе призм, которые при помощи CSS создаются относительно несложно.
Оставил стандартный плеер в углу, чтобы можно было проверить синхронизацию. Иголку можно перемещать мышкой. Наслаждайтесь:
let needleRange = [-29.5, -57.5];
let needleDrag = false;
let prevX = 0;
let dragSens = 0.2;
const rangeLimit = (v, a, b) => v < a ? a : (v > b ? b : v);
const rangePercentByValue = (v, a, b) => (v - a) / (b - a);
const rangeValueByPercent = (p, a, b) => (b - a) * p + a;
const elRecord = document.querySelector('.record');
const elSupport = document.querySelector('.support');
const elSeg2 = document.querySelector('.seg2');
const setNeedlePos = (pos) => elSupport.style.setProperty('--angle', pos + "deg");
const getNeedlePos = () => parseFloat(getComputedStyle(elSupport).getPropertyValue('--angle'));
const needleUp = () => {
elSeg2.classList.remove('jumping');
void elSeg2.offsetHeight;
elSeg2.classList.add('up');
}
const needleDown = () => {
elSeg2.classList.remove('up');
setTimeout(() => {
if (!audio.paused) elSeg2.classList.add('jumping')
}, 300);
}
holder.onmousedown = (e) => {
audio.pause();
needleDrag = true;
prevX = e.pageX;
}
window.onmousemove = (e) => {
if (needleDrag) {
let da = (e.pageX - prevX) * dragSens;
let r = needleRange;
let a = getNeedlePos();
let aNew = rangeLimit(a - da, r[1], r[0]);
setNeedlePos(aNew);
audio.currentTime = (1 - rangePercentByValue(aNew, r[1], r[0])) * audio.duration;
prevX = e.pageX;
}
}
window.onmouseup = (e) => {
needleDrag = false;
audio.play();
}
audio.ontimeupdate = function(e) {
if (!needleDrag) {
let percent = 1 - audio.currentTime / audio.duration;
let aNew = rangeValueByPercent(percent, needleRange[1], needleRange[0]);
setNeedlePos(aNew);
}
};
audio.addEventListener("pause", needleUp);
audio.addEventListener("ended", needleUp);
audio.addEventListener("play", needleDown);
#audio {
position: absolute;
right: 20px;
top: 20px;
}
.viewport,
.viewport * {
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.viewport {
width: 100%;
height: 350px;
perspective: 1200px;
position: relative;
overflow: hidden;
}
.scene {
user-select: none;
transform: rotateX(50deg);
}
.scene,
.scene * {
position: absolute;
transform-style: preserve-3d;
backface-visibility: hidden;
}
.table {
width: 500px;
height: 400px;
background-color: #d9a35c;
transform: translate3d(0, 0, -10px);
}
.table .back-rack {
border-radius: 25% 25% 0 0;
bottom: 100%;
width: 170px;
height: 100px;
background-color: #a9a35c;
}
.table .back-rack .base {
width: 30px;
height: 80px;
background-color: black;
transform: translateZ(40px) rotateX(-90deg);
}
.support {
--angle: -29.5deg;
right: 0%;
width: 250px;
height: 20px;
transform-origin: 100% 50%;
transform: translate3d(0, -250px, 30px) rotateZ(var(--angle)) rotateX(-90deg);
background: green;
}
.seg2 {
right: 100%;
width: 70px;
height: 20px;
transform-origin: 100% 50%;
transform: rotateY(90deg) rotateZ(0deg);
background: blue;
backface-visibility: visible;
transition: 0.2s;
}
.seg2.up {
transition: 0.5s;
animation: none;
transform: rotateY(90deg) rotateZ(45deg);
}
.seg2.jumping {
animation: 0.2s jumping infinite alternate;
}
.seg3 {
border-radius: 50%;
right: calc(100% - 19px);
width: 40px;
height: 40px;
transform-origin: 50% 50%;
transform: rotateY(90deg) rotateZ(0deg);
background: darkred;
backface-visibility: visible;
}
.seg3:hover {
outline: 3px solid yellow;
}
.needle {
top: 100%;
height: 10px;
width: 3px;
background: yellow;
backface-visibility: visible;
}
.record {
width: 400px;
height: 400px;
font-family: Times;
}
.record.rotating .vinyl-wrap {
animation: 1s rotation infinite linear;
}
.vinyl-wrap {
width: 100%;
height: 100%;
}
.vinyl {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: black;
background-image: url(data:image/png;base64,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), repeating-radial-gradient(#2a292866, #2a292866 1px, #444 2px, #2a292866 3px);
background-size: 20% 20%, 100% 100%;
transform: rotateX(0.25deg);
}
.light {
border-radius: 50%;
width: 100%;
height: 100%;
background: conic-gradient(from 10deg, transparent 0deg, #ffffff44 30deg, transparent 60deg, transparent 180deg, #ffffff66 210deg, transparent 240deg);
-webkit-mask-image: radial-gradient(transparent 25%, black 25.8%);
mask-image: radial-gradient(transparent 25%, black 25.8%);
transform: translate3d(0, 0, 1px)
}
.label {
border: solid 2px #d9a388;
width: 33%;
height: 33%;
border-radius: 50%;
box-shadow: 0 0 0 4px #da5b33;
background: #da5b33;
color: white;
text-align: center;
}
.label:after {
transform-style: preserve-3d;
position: absolute;
border-radius: 50%;
background: black;
width: 14%;
height: 14%;
content: '';
}
.label h1 {
top: -6%;
}
.label p {
top: 53%;
font-size: 83%;
}
@keyframes rotation {
100% {
transform: rotateZ(1turn);
}
}
@keyframes jumping {
100% {
transform: rotateY(90deg) rotateZ(0.7deg);
}
}
<div class="viewport">
<div class="scene">
<div class="table" id="table">
<div class="back-rack">
<div class="base"></div>
</div>
</div>
<div class="record rotating">
<div class="vinyl-wrap">
<div class="vinyl">
<div class="label jazz">
<h1>Jazz</h1>
<p>Greatest Hits<br>vol 1</p>
</div>
</div>
</div>
<div class="light"></div>
</div>
<div class="support">
<div class="seg2 up">
<div class="seg3" id="holder">
<div class="needle"></div>
</div>
</div>
</div>
</div>
</div>
<audio id="audio" controls src="https://www.therosenbergtrio.info/wp-content/uploads/2016/04/02-Ive-Got-Rhythm-Featuring-Mozes-Johnny-Rosenberg.mp3?_=1">
Однозначно, WebGL и Three.js дают гораздо большую перспективу в реализации данного проекта.
Это - реализация подключения простого плейлиста к варианту победителя:
let sounds = 'track1, track2, track3, track4, track5'.split(', ');
let url = 'https://mysynthesizer.github.io/index.hetemeel/';
let index = 0;
audio.src = url + sounds[index] + '.mp3';
function playTrack(){
audio.src = url + sounds[index] + '.mp3';
audio.play();
for (j = 0; j < sounds.length; j ++) document.getElementById('td' + j).style.background = 'white';
document.getElementById('td' + index).style.background = 'gold';
}
function changeTrack(direction){
index += direction;
if (index >= sounds.length) index = 0;
if (index < 0) index = sounds.length - 1;
playTrack();
}
document.onkeydown = e => {
if(e.keyCode == 38) changeTrack(-1);
if(e.keyCode == 40) changeTrack(1);
return false;
}
for(i = 0; i < sounds.length; i ++){
let tr = document.createElement('tr');
let td = document.createElement('td');
td.id = 'td' + i;
td.textContent = sounds[i];
td.onclick = function(){
index = parseInt(this.id.substring(2));
playTrack();
}
tr.append(td);
tr.append(td);
table1.append(tr);
}
let needleRange = [-29.5, -57.5];
let needleDrag = false;
let prevX = 0;
let dragSens = 0.2;
const rangeLimit = (v, a, b) => v < a ? a : (v > b ? b : v);
const rangePercentByValue = (v, a, b) => (v - a) / (b - a);
const rangeValueByPercent = (p, a, b) => (b - a) * p + a;
const elRecord = document.querySelector('.record');
const elSupport = document.querySelector('.support');
const elSeg2 = document.querySelector('.seg2');
const setNeedlePos = (pos) => elSupport.style.setProperty('--angle', pos + "deg");
const getNeedlePos = () => parseFloat(getComputedStyle(elSupport).getPropertyValue('--angle'));
const needleUp = () => {
elSeg2.classList.remove('jumping');
void elSeg2.offsetHeight;
elSeg2.classList.add('up');
}
const needleDown = () => {
elSeg2.classList.remove('up');
setTimeout(() => {
if (!audio.paused) elSeg2.classList.add('jumping')
}, 300);
}
holder.onmousedown = (e) => {
audio.pause();
needleDrag = true;
prevX = e.pageX;
}
window.onmousemove = (e) => {
if (needleDrag) {
let da = (e.pageX - prevX) * dragSens;
let r = needleRange;
let a = getNeedlePos();
let aNew = rangeLimit(a - da, r[1], r[0]);
setNeedlePos(aNew);
audio.currentTime = (1 - rangePercentByValue(aNew, r[1], r[0])) * audio.duration;
prevX = e.pageX;
}
}
window.onmouseup = (e) => {
needleDrag = false;
audio.play();
}
audio.ontimeupdate = function(e) {
if (!needleDrag) {
let percent = 1 - audio.currentTime / audio.duration;
let aNew = rangeValueByPercent(percent, needleRange[1], needleRange[0]);
setNeedlePos(aNew);
}
};
audio.addEventListener("pause", needleUp);
audio.addEventListener("ended", needleUp);
audio.addEventListener("play", needleDown);
.viewport,
.viewport * {
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.viewport {
width: 100%;
height: 350px;
perspective: 1200px;
position: relative;
overflow: hidden;
}
.scene {
user-select: none;
transform: rotateX(50deg);
}
.scene,
.scene * {
position: absolute;
transform-style: preserve-3d;
backface-visibility: hidden;
}
.table {
width: 500px;
height: 400px;
background-color: #d9a35c;
transform: translate3d(0, 0, -10px);
}
.table .back-rack {
border-radius: 25% 25% 0 0;
bottom: 100%;
width: 170px;
height: 100px;
background-color: #a9a35c;
}
.table .back-rack .base {
width: 30px;
height: 80px;
background-color: black;
transform: translateZ(40px) rotateX(-90deg);
}
.support {
--angle: -29.5deg;
right: 0%;
width: 250px;
height: 20px;
transform-origin: 100% 50%;
transform: translate3d(0, -250px, 30px) rotateZ(var(--angle)) rotateX(-90deg);
background: green;
}
.seg2 {
right: 100%;
width: 70px;
height: 20px;
transform-origin: 100% 50%;
transform: rotateY(90deg) rotateZ(0deg);
background: blue;
backface-visibility: visible;
transition: 0.2s;
}
.seg2.up {
transition: 0.5s;
animation: none;
transform: rotateY(90deg) rotateZ(45deg);
}
.seg2.jumping {
animation: 0.2s jumping infinite alternate;
}
.seg3 {
border-radius: 50%;
right: calc(100% - 19px);
width: 40px;
height: 40px;
transform-origin: 50% 50%;
transform: rotateY(90deg) rotateZ(0deg);
background: darkred;
backface-visibility: visible;
}
.seg3:hover {
outline: 3px solid yellow;
}
.needle {
top: 100%;
height: 10px;
width: 3px;
background: yellow;
backface-visibility: visible;
}
.record {
width: 400px;
height: 400px;
font-family: Times;
}
.record.rotating .vinyl-wrap {
animation: 2s rotation infinite linear;
}
.vinyl-wrap {
width: 100%;
height: 100%;
}
.vinyl {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: black;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAAG3RSTlNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAvEOwtAAAFVklEQVR4XpWWB67c2BUFb3g557T/hRo9/WUMZHlgr4Bg8Z4qQgQJlHI4A8SzFVrapvmTF9O7dmYRFZ60YiBhJRCgh1FYhiLAmdvX0CzTOpNE77ME0Zty/nWWzchDtiqrmQDeuv3powQ5ta2eN0FY0InkqDD73lT9c9lEzwUNqgFHs9VQce3TVClFCQrSTfOiYkVJQBmpbq2L6iZavPnAPcoU0dSw0SUTqz/GtrGuXfbyyBniKykOWQWGqwwMA7QiYAxi+IlPdqo+hYHnUt5ZPfnsHJyNiDtnpJyayNBkF6cWoYGAMY92U2hXHF/C1M8uP/ZtYdiuj26UdAdQQSXQErwSOMzt/XWRWAz5GuSBIkwG1H3FabJ2OsUOUhGC6tK4EMtJO0ttC6IBD3kM0ve0tJwMdSfjZo+EEISaeTr9P3wYrGjXqyC1krcKdhMpxEnt5JetoulscpyzhXN5FRpuPHvbeQaKxFAEB6EN+cYN6xD7RYGpXpNndMmZgM5Dcs3YSNFDHUo2LGfZuukSWyUYirJAdYbF3MfqEKmjM+I2EfhA94iG3L7uKrR+GdWD73ydlIB+6hgref1QTlmgmbM3/LeX5GI1Ux1RWpgxpLuZ2+I+IjzZ8wqE4nilvQdkUdfhzI5QDWy+kw5Wgg2pGpeEVeCCA7b85BO3F9DzxB3cdqvBzWcmzbyMiqhzuYqtHRVG2y4x+KOlnyqla8AoWWpuBoYRxzXrfKuILl6SfiWCbjxoZJUaCBj1CjH7GIaDbc9kqBY3W/Rgjda1iqQcOJu2WW+76pZC9QG7M00dffe9hNnseupFL53r8F7YHSwJWUKP2q+k7RdsxyOB11n0xtOvnW4irMMFNV4H0uqwS5ExsmP9AxbDTc9JwgneAT5vTiUSm1E7BSflSt3bfa1tv8Di3R8n3Af7MNWzs49hmauE2wP+ttrq+AsWpFG2awvsuOqbipWHgtuvuaAE+A1Z/7gC9hesnr+7wqCwG8c5yAg3AL1fm8T9AZtp/bbJGwl1pNrE7RuOX7PeMRUERVaPpEs+yqeoSmuOlokqw49pgomjLeh7icHNlG19yjs6XXOMedYm5xH2YxpV2tc0Ro2jJfxC50ApuxGob7lMsxfTbeUv07TyYxpeLucEH1gNd4IKH2LAg5TdVhlCafZvpskfncCfx8pOhJzd76bJWeYFnFciwcYfubRc12Ip/ppIhA1/mSZ/RxjFDrJC5xifFjJpY2Xl5zXdguFqYyTR1zSp1Y9p+tktDYYSNflcxI0iyO4TPBdlRcpeqjK/piF5bklq77VSEaA+z8qmJTFzIWiitbnzR794USKBUaT0NTEsVjZqLaFVqJoPN9ODG70IPbfBHKK+/q/AWR0tJzYHRULOa4MP+W/HfGadZUbfw177G7j/OGbIs8TahLyynl4X4RinF793Oz+BU0saXtUHrVBFT/DnA3ctNPoGbs4hRIjTok8i+algT1lTHi4SxFvONKNrgQFAq2/gFnWMXgwffgYMJpiKYkmW3tTg3ZQ9Jq+f8XN+A5eeUKHWvJWJ2sgJ1Sop+wwhqFVijqWaJhwtD8MNlSBeWNNWTa5Z5kPZw5+LbVT99wqTdx29lMUH4OIG/D86ruKEauBjvH5xy6um/Sfj7ei6UUVk4AIl3MyD4MSSTOFgSwsH/QJWaQ5as7ZcmgBZkzjjU1UrQ74ci1gWBCSGHtuV1H2mhSnO3Wp/3fEV5a+4wz//6qy8JxjZsmxxy5+4w9CDNJY09T072iKG0EnOS0arEYgXqYnXcYHwjTtUNAcMelOd4xpkoqiTYICWFq0JSiPfPDQdnt+4/wuqcXY47QILbgAAAABJRU5ErkJggg==), repeating-radial-gradient(#2a292866, #2a292866 1px, #444 2px, #2a292866 3px);
background-size: 20% 20%, 100% 100%;
transform: rotateX(0.25deg);
}
.light {
border-radius: 50%;
width: 100%;
height: 100%;
background: conic-gradient(from 10deg, transparent 0deg, #ffffff44 30deg, transparent 60deg, transparent 180deg, #ffffff66 210deg, transparent 240deg);
-webkit-mask-image: radial-gradient(transparent 25%, black 25.8%);
mask-image: radial-gradient(transparent 25%, black 25.8%);
transform: translate3d(0, 0, 1px)
}
.label {
border: solid 2px #d9a388;
width: 33%;
height: 33%;
border-radius: 50%;
box-shadow: 0 0 0 4px #da5b33;
background: #da5b33;
color: white;
text-align: center;
}
.label:after {
transform-style: preserve-3d;
position: absolute;
border-radius: 50%;
background: black;
width: 14%;
height: 14%;
content: '';
}
.label h1 {
top: -6%;
}
.label p {
top: 53%;
font-size: 83%;
}
@keyframes rotation {
100% {
transform: rotateZ(1turn);
}
}
@keyframes jumping {
100% {
transform: rotateY(90deg) rotateZ(0.7deg);
}
}
#table1{
width: 100px;
text-align: center;
font-family: arial;
}
td{
cursor: pointer;
border-radius: 5px;
transition: 0.5s;
}
td:hover{
background: pink;
transition: 0.5s;
}
<div class="viewport">
<div class="scene">
<div class="table" id="table">
<div class="back-rack">
<div class="base"></div>
</div>
</div>
<div class="record rotating">
<div class="vinyl-wrap">
<div class="vinyl">
<div class="label jazz">
<h1>Мелодия</h1>
<p>Фабрика граммпластинок<br>vol 1</p>
</div>
</div>
</div>
<div class="light"></div>
</div>
<div class="support">
<div class="seg2 up">
<div class="seg3" id="holder">
<div class="needle"></div>
</div>
</div>
</div>
</div>
</div>
<center>
<button onclick='changeTrack(-1)' title='предыдущий трек'> << </button>
<button onclick='changeTrack(1)' title='следующий трек'> >> </button><br /><br />
<table id=table1 border=0 cellspacing=5 cellpadding=0></table>
</center>
<audio id="audio" onended='changeTrack(1)'></audio>
Переключать треки можно кликом по названию трека, клавишами со стрелками вверх и вниз, так же кнопками << и >>.