Страницы

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

четверг, 28 ноября 2019 г.

Как создать эффект затвора камеры с помощью divs

#javascript #css #html5 #svg #svg_animation


Я попытался сделать круговую камеру затвора фотоаппарата, но у меня проблема с тем,
чтобы заставить его выглядеть реалистично.

Вот как это должно выглядеть: 



Ниже код, который я пробовал:



let partAmount = 10;
let cont = document.getElementById('cont');
let parts = [];
for(let i = 1; i <= partAmount; i++){
  let partCont = createElement('div','partCont');
  let part = createElement('div','part');
  parts.push(part);
  partCont.appendChild(part);
  cont.appendChild(partCont);
  partCont.style.transform = 'rotate('+ 360 / partAmount * i+'deg) translatey(-250px)';
}
function createElement(tag,className){
  let elem = document.createElement(tag);
  elem.classList.add(className);
  return elem;
}
#cont{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);

  border-radius: 50%;
}
.dia{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 300px;
  height: 300px;
  border-radius: 50%;
  overflow: hidden;
}
.partCont{
  position: absolute;
  transform-origin: left top;
}
.part{
  width: 500px;
  height: 100px;
  background-color: lightgray;
  border-bottom: 3px solid gray;
  box-sizing: border-box;
  transform-origin: left bottom;
  transform: rotate(60deg);
  transition-duration: 1s;
}
Первый «лепесток» должен быть ниже, чем последний, и выше, чем следующий. Как это сделать?


Ответы

Ответ 1



Сниппет в этом ответе поддерживает разное количество створок диафрагмы. В основе этого решения лежит алгоритм нахождения точек пересечения двух окружностей let r = 80, arc = (x,y,s) => `A${r},${r},0,0,${s},${x},${y}`, path = (i,d) => ``; function upd (val) { let step = Math.PI*(0.5 + 2/+count.value); let p1x = Math.cos(step)*r; let p1y = Math.sin(step)*r; let cos = Math.cos(-val); let sin = Math.sin(-val); let c1x = p1x - cos * p1x - sin * p1y; let c1y = p1y - cos * p1y + sin * p1x; let dx = - sin * r - c1x; let dy = r - cos * r - c1y; let dc = Math.sqrt(dx*dx + dy*dy); let a = Math.atan2(dy, dx) - Math.acos(dc/2/r); let x = c1x + Math.cos(a)*r; let y = c1y + Math.sin(a)*r; let edge = `M${p1x},${p1y}${arc(0,r,0)}${arc(x,y,1)}`; edges.innerHTML = bodies.innerHTML = ''; for (let i = 0; i < +count.value; i++) { edges.innerHTML += path(i, `fill=none stroke=black d='${edge}'`); bodies.innerHTML += path(i, `fill=lightgray d='${edge}${arc(p1x,p1y,0)}'`); } }; upd(0.5); addEventListener('mousemove', e => upd(e.y/innerHeight*1.04));


Ответ 2



Хитрость заключается в том, чтобы учесть тот факт, что у вас симметричная форма, поэтому вы можете построить ее, используя два разных элемента, где вы применяете одну и ту же вещь, а затем поворачиваете один из элементов, чтобы создать иллюзию одной формы. Я буду использовать множественный фон и линейный градиент, чтобы создать это: .camera{ width:200px; height:200px; margin:auto; border-radius: 50%; border:1px solid; overflow:hidden; position:relative; --c1: transparent 55%,#000 calc(55% + 1px) calc(55% + 4px),grey calc(55% + 5px); --c2: transparent 40%,#000 calc(40% + 1px) calc(40% + 4px),grey calc(40% + 5px); } .camera::before, .camera::after{ content:""; position:absolute; top:0; left:0; height:100%; width:50%; background: linear-gradient(-153deg,var(--c1)), linear-gradient(-107deg,var(--c2)), linear-gradient(-73deg ,var(--c2)), linear-gradient(-27deg ,var(--c1)); } .camera::after { transform:rotate(180deg); transform-origin:right; }
Как вы можете видеть выше, мы почти близки к результату, и есть две пропущенные строки, которые мы можем добавить, используя дополнительный градиент, как показано ниже: .camera{ width:200px; height:200px; margin:auto; border-radius: 50%; border:1px solid; overflow:hidden; position:relative; --c1: transparent 55%,#000 calc(55% + 1px) calc(55% + 4px),grey calc(55% + 5px); --c2: transparent 40%,#000 calc(40% + 1px) calc(40% + 4px),grey calc(40% + 5px); } .camera::before, .camera::after{ content:""; position:absolute; top:0; left:0; height:100%; width:50%; background: linear-gradient( 153deg,var(--c1)) bottom/100% 43.5% no-repeat, linear-gradient(-153deg,var(--c1)), linear-gradient(-107deg,var(--c2)), linear-gradient(-73deg ,var(--c2)), /* 180 - 107 = 73deg*/ linear-gradient(-27deg ,var(--c1)); /* 180 - 153 = 27deg*/ } .camera::after{ transform:rotate(180deg); transform-origin:right; }
Немного математики В случае, если нам нужен точный расчет, мы должны учитывать, что нарисованная внутри фигура является восьмиугольником: Из этого мы можем определить угол поворота. Первый будет - 45 градусов / 2 = 22,5 градусов. Затем мы увеличиваем на 45 градусов, чтобы найти остальные: Код станет: .camera{ width:200px; height:200px; margin:auto; border-radius: 50%; border:1px solid; overflow:hidden; position:relative; --p1:55%; --p2:40%; --c1: transparent var(--p1),#000 calc(var(--p1) + 1px) calc(var(--p1) + 4px),grey calc(var(--p1) + 5px); --c2: transparent var(--p2),#000 calc(var(--p2) + 1px)calc(var(--p2) + 4px),grey calc(var(--p2) + 5px); } .camera::before, .camera::after{ content:""; position:absolute; top:0; left:0; height:100%; width:50%; background: linear-gradient( 112.5deg,var(--c1)) bottom right/10% 14% no-repeat, linear-gradient( 157.5deg,var(--c1)) bottom /100% 54% no-repeat, linear-gradient(-157.5deg,var(--c1)), /* -135deg */ linear-gradient(-112.5deg,var(--c2)), /* -90deg */ linear-gradient(-67.5deg ,var(--c2)), /* -45deg */ linear-gradient(-22.5deg ,var(--c1)); } .camera::after{ transform:rotate(180deg); transform-origin:right; }
Вы можете заметить, что нам понадобятся 2 дополнительных градиента, потому что будет больше пропущенных линий. Чтобы управлять формой, вы должны отрегулировать значения color stops (--p1 и --p2) и скорректировать размер дополнительных градиентов (все еще нужно найти связь между этими значениями) .camera{ width:200px; height:200px; display:inline-block; border-radius: 50%; border:1px solid; overflow:hidden; position:relative; --p1:55%; --p2:40%; --c1: transparent var(--p1),#000 calc(var(--p1) + 1px) calc(var(--p1) + 4px),grey calc(var(--p1) + 5px); --c2: transparent var(--p2),#000 calc(var(--p2) + 1px)calc(var(--p2) + 4px),grey calc(var(--p2) + 5px); } .camera::before, .camera::after{ content:""; position:absolute; top:0; left:0; height:100%; width:50%; background: linear-gradient( 112.5deg,var(--c1)) bottom right/var(--e1,10%) var(--e2,14%) no-repeat, linear-gradient( 157.5deg,var(--c1)) bottom /100% var(--e3,54%) no-repeat, linear-gradient(-157.5deg,var(--c1)), /* -135deg */ linear-gradient(-112.5deg,var(--c2)), /* -90deg */ linear-gradient(-67.5deg ,var(--c2)), /* -45deg */ linear-gradient(-22.5deg ,var(--c1)); } .camera::after{ transform:rotate(180deg); transform-origin:right; }
Мы можем легко перейти к любой форме многоугольника, добавив больше слоев и правильно рассчитав степень вращения. Пример с декагоном: .camera{ width:200px; height:200px; display:inline-block; border-radius: 50%; border:1px solid; overflow:hidden; position:relative; --p1:60%; --p2:48%; --p3:38%; --c1: transparent var(--p1),#000 calc(var(--p1) + 1px) calc(var(--p1) + 4px),grey calc(var(--p1) + 5px); --c2: transparent var(--p2),#000 calc(var(--p2) + 1px) calc(var(--p2) + 4px),grey calc(var(--p2) + 5px); --c3: transparent var(--p3),#000 calc(var(--p3) + 1px) calc(var(--p3) + 4px),grey calc(var(--p3) + 5px); } .camera::before, .camera::after{ content:""; position:absolute; top:0; left:0; height:100%; width:50%; background: linear-gradient( 126deg,var(--c1)) bottom right/var(--e1,40%) var(--e2,20%) no-repeat, linear-gradient( 162deg,var(--c1)) bottom /100% var(--e3,60%) no-repeat, linear-gradient(-162deg,var(--c1)), linear-gradient(-126deg,var(--c2)), linear-gradient(-90deg, var(--c3)), linear-gradient(-54deg ,var(--c2)), linear-gradient(-18deg ,var(--c1)); /* 36deg/2 then we increment by 36deg*/ } .camera::after{ transform:rotate(180deg); transform-origin:right; }
Поскольку мы имеем дело с background, мы можем добавить дополнительный слой для изображения: #camera{ width:200px; height:200px; display:inline-block; border-radius: 50%; border:1px solid; overflow:hidden; position:relative; background:url(https://picsum.photos/id/155/800/800) center/80% 80%; --p1:60%; --p2:48%; --p3:38%; --c1: transparent var(--p1),#000 calc(var(--p1) + 1px) calc(var(--p1) + 3px),grey calc(var(--p1) + 4px); --c2: transparent var(--p2),#000 calc(var(--p2) + 1px) calc(var(--p2) + 3px),grey calc(var(--p2) + 4px); --c3: transparent var(--p3),#000 calc(var(--p3) + 1px) calc(var(--p3) + 3px),grey calc(var(--p3) + 4px); } #camera::before, #camera::after{ content:""; position:absolute; top:0; left:0; height:100%; width:50%; background: linear-gradient( 126deg,var(--c1)) bottom right/var(--e1,40%) var(--e2,20%) no-repeat, linear-gradient( 162deg,var(--c1)) bottom /100% var(--e3,60%) no-repeat, linear-gradient(-162deg,var(--c1)), linear-gradient(-126deg,var(--c2)), linear-gradient(-90deg, var(--c3)), linear-gradient(-54deg ,var(--c2)), linear-gradient(-18deg ,var(--c1)); /* 36deg/2 then we increment by 36deg*/ } #camera::after{ transform:rotate(180deg); transform-origin:right; }
Анимация затвора И вот идея, как создать анимацию открытия / закрытия затвора: .camera{ width:200px; height:200px; display:inline-block; border-radius: 50%; border:1px solid; overflow:hidden; position:relative; background:url(https://picsum.photos/id/155/800/800) center/cover; --p1:60%; --p2:48%; --p3:38%; --c1: transparent var(--p1),#000 calc(var(--p1) + 1px) calc(var(--p1) + 4px),grey calc(var(--p1) + 5px); --c2: transparent var(--p2),#000 calc(var(--p2) + 1px) calc(var(--p2) + 4px),grey calc(var(--p2) + 5px); --c3: transparent var(--p3),#000 calc(var(--p3) + 1px) calc(var(--p3) + 4px),grey calc(var(--p3) + 5px); } .camera::before, .camera::after{ content:""; position:absolute; top:-50%; left:50%; height:200%; width:100%; transition:.5s all linear; background: linear-gradient(-126deg,var(--c1)) bottom left/var(--e1,40%) var(--e2,20%) no-repeat, linear-gradient(-162deg,var(--c1)) bottom /100% var(--e3,60%) no-repeat, linear-gradient(162deg,var(--c1)), linear-gradient(126deg,var(--c2)), linear-gradient(90deg, var(--c3)), linear-gradient(54deg ,var(--c2)), linear-gradient(18deg ,var(--c1)); /* 36deg/2 then we increment by 36deg*/ } .camera::after{ transform:rotate(180deg); transform-origin:left; } .camera:hover::before, .camera:hover::after { top:0; left:50%; height:100%; width:50%; }
Альтернативное решение Мы можем объединить код автора вопроса и идею двух симметричных фигур и создать его, как показано ниже: let partAmount = 10; let cont = document.querySelector('.cont'); let parts = []; for(let i = 1; i <= partAmount ; i++){ let partCont = createElement('div','partCont'); let part = createElement('div','part'); parts.push(part); partCont.appendChild(part); cont.appendChild(partCont); partCont.style.transform = 'rotate('+ 360 / partAmount * i+'deg) translatey(-250px)'; } function createElement(tag,className){ let elem = document.createElement(tag); elem.classList.add(className); return elem; } /*added*/ let alt = cont.cloneNode(true); document.querySelector('.dia').appendChild(alt); .cont{ position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); border-radius: 50%; clip-path: polygon(0 -150px, 0 150px, -150px 150px,-150px -150px); /*added*/ } .cont:last-child { transform:rotate(180deg); /*added*/ } .dia{ width: 300px; height: 300px; border-radius: 50%; overflow: hidden; position:relative; } .partCont{ position: absolute; transform-origin: left top; } .part{ width: 300px; height: 100px; background-color: lightgray; border-bottom: 3px solid gray; box-sizing: border-box; transform-origin: left bottom; transform: rotate(60deg); transition-duration: 1s; }


Ответ 3



Специально посмотрел видео, как устроена диафрагма. На HTML+CSS сделать реалистично намного сложнее чем на SVG. Вся сложность данного примера в подсчете координат конечной точки дуги, образующей границу створки диафрагмы. Здесь я воспользовался одним наблюдением, не проверял работает ли оно в общем случае, для 6 створок работает идеально. Эта точка лежит на окружности, построенной через точку крепления соседней по часовой стрелке створки, с таким же радиусом как и у ограничивающей окружности. UPD: уменьшено кол-во изменений структуры документа, теперь фигуры имеют корректные форму и им добавлена заливка, соответственно они могут выполнять роль диафрагмы - закрывая собой другое изображение let r = 80, value = 0.5; let arc = (x, y, sweep) => `A${r},${r},0,0,${sweep},${x},${y}`; let paths = document.querySelectorAll('path'); paths.forEach((path, i) => path.setAttribute('fill', `hsl(${i*60},75%,55%)`)) requestAnimationFrame(draw); function draw() { upd(); requestAnimationFrame(draw); } function upd() { value = Math.min(0.98, Math.max(value, 0)); paths.forEach((path, i) => { let rot = Math.PI*(0.5 + 2/paths.length), cx = Math.cos(rot)*r, cy = Math.sin(rot)*r, radians = Math.PI*value*2/paths.length, cos = Math.cos(radians), sin = Math.sin(radians), x = cx - cos*cx - sin * cy, y = cy - cos*cy + sin * cx; path.setAttribute('d', `M0,${r}${arc(x,y,1)}${arc(cx,cy,0)}${arc(0,r,0)}`); path.setAttribute('transform', `rotate(${i/paths.length*360})`); }); }; addEventListener('mousemove', e => value = e.y/innerHeight*1.2-0.1); addEventListener('touchmove', e => value = e.touches[0].pageY/innerHeight*1.2-0.2); body, svg { margin: 0; height: 100vh; }

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

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