Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to create a camera shutter effect with divs

I was trying to make a circle camera shutter, but I have a problem getting it to look right.

That's how it should look:

enter image description here

The first 'petal' should be lower than the last and upper than next. How to do that?

Below what I have tried:

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;
}
<div class="dia">
  <div id="cont">
  </div>
</div>
like image 569
Artem Brezhnev Avatar asked Jun 05 '19 20:06

Artem Brezhnev


2 Answers

it is much simpler to implement with svg

let r = 80, 
    arc = (x,y,s) => `A${r},${r},0,0,${s},${x},${y}`,
    path = (i,d) => `<path transform='rotate(${i/+count.value*360})' ${d}></path>`;

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));
<svg viewbox=-100,-100,200,200 style="height:90vh" id=svg>
    <g id=bodies></g><g id=edges></g>
</svg><br>
<input id=count type=range min=2 max=13 value=5 style="position:absolute;top:2px">
like image 81
Stranger in the Q Avatar answered Sep 18 '22 22:09

Stranger in the Q


The trick here is to consider the fact that you have a symmetrical shape, so you can build it using two different elements where you apply the same thing then you rotate one of them to create the illusion of one shape.

I will consider the same idea in a previous question and rely on multiple background and linear-gradient to create this:

.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;
}
<div class="camera">
</div>

As you can see above we are almost close and there is two missing lines that we can add using an extra gradient like below:

.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;
}
<div class="camera">
</div>

Some maths

In case we need accurate calculation, we should consider that the shape drawn inside is an Octagon:

CSS Octagon Shape ref

From this we can identify the angle of rotation. The first one will be 45deg/2 = 22.5deg. Then we increment by 45deg to find the others:

The code will then become:

.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;
}
<div class="camera">
</div>

You can notice that we will need 2 extra gradients because will have more missing lines.

To control the shape you have to adjust the values of the color stops (--p1 and --p2) and correct the dimension of the extra gradients (still need to find a relation between those values)

.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;
}
<div class="camera">
</div>

<div class="camera" style="--p1:65%;--p2:55%;  --e1:0;--e3:40%">
</div>

<div class="camera" style="--p1: 46%;--p2: 29%;  --e1: 26%;--e2: 35%;--e3: 62%;">
</div>

CSS camera shutter effect Octagon


We can easily move to any polygone shape by adding more layers and correctly calculate the degree of rotation.

Example with a Decagon:

.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;
}
<div class="camera">
</div>
<div class="camera" style="--p1: 66.5%;--p2: 56%;--p3: 51%;  --e3: 51%;--e2: 8%;--e1: 13%;">
</div>

<div class="camera" style="--p1: 50%;--p2: 37%;--p3: 15%; --e3: 68%;--e2: 41%;--e1: 50%;">
</div>

CSS camera shutter effect Decagon

Since we are dealing with background we can add an extra layer for an image:

#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;
}
<div id="camera">
</div>
<div id="camera" style="--p1: 66.5%;--p2: 56%;--p3: 51%;  --e3: 51%;--e2: 8%;--e1: 13%;">
</div>

<div id="camera" style="--p1: 50%;--p2: 37%;--p3: 15%; --e3: 68%;--e2: 41%;--e1: 50%;">
</div>

In case you want to switch the direction of the shutters, simply multiply all the angle by -1 and switch some left/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;*/ right:0;
  height:100%;
  width:50%;
  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:right;*/transform-origin:left;
}
<div class="camera">
</div>
<div class="camera" style="--p1: 66.5%;--p2: 56%;--p3: 51%;  --e3: 51%;--e2: 8%;--e1: 13%;">
</div>

<div class="camera" style="--p1: 50%;--p2: 37%;--p3: 15%; --e3: 68%;--e2: 41%;--e1: 50%;">
</div>

CSS camera shutter effect

With animation

And here is an idea to create an open/close animation of the shutters:

.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%;
}
<div class="camera">
</div>

We simply need to increase/decrease the size of the pseudo element by keeping the same position.


Alternative solution

We can combine your code and the idea of the two symmetrical shape and create it like below:

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;
}
<div class="dia">
  <div class="cont">
  </div>
</div>
like image 34
Temani Afif Avatar answered Sep 21 '22 22:09

Temani Afif