在内容轮播方面,前端开发人员有无数的选择。我可以推荐 Flickity 吗? (备注:这是原作者的推荐,不是译者加)既然我们的浏览器具有 3D 功能,为什么不尝试创建一个真正的 3D 轮播呢?
此HTML结构采用与盒子、立方体和卡片相同的形式。让我们让它变得有趣并有一个带有 9 个面板的旋转木马。
<div class="scene"> <div class="carousel"> <div class="carousel__cell">1</div> <div class="carousel__cell">2</div> <div class="carousel__cell">3</div> <div class="carousel__cell">4</div> <div class="carousel__cell">5</div> <div class="carousel__cell">6</div> <div class="carousel__cell">7</div> <div class="carousel__cell">8</div> <div class="carousel__cell">9</div> </div> </div>
现在应用基本布局样式。让我们给每个单元格彼此之间设置 20px 的间隙,在这里使用left: 10px
. 每个面板的有效宽度保持为 210px。
.scene { width: 210px; height: 140px; position: relative; perspective: 1000px; } .carousel { width: 100%; height: 100%; position: absolute; transform-style: preserve-3d; } .carousel__cell { position: absolute; width: 190px; height: 120px; left: 10px; top: 10px; }
下一步:旋转各个面。这个旋转木马有 9 个面。如果每个面在旋转木马上均等分布,则每个面将比上一个面多旋转 40 度 ( 360 / 9 )。
.carousel__cell:nth-child(1) { transform: rotateY( 0deg); } .carousel__cell:nth-child(2) { transform: rotateY( 40deg); } .carousel__cell:nth-child(3) { transform: rotateY( 80deg); } .carousel__cell:nth-child(4) { transform: rotateY(120deg); } .carousel__cell:nth-child(5) { transform: rotateY(160deg); } .carousel__cell:nth-child(6) { transform: rotateY(200deg); } .carousel__cell:nth-child(7) { transform: rotateY(240deg); } .carousel__cell:nth-child(8) { transform: rotateY(280deg); } .carousel__cell:nth-child(9) { transform: rotateY(320deg); }
现在向外转移。回到我们创建立方体和盒子的时候,这个translate值很容易计算,因为它等于对象的宽度、高度或深度的一半。现在有了旋转木马,我们无法立即参考尺寸。我们将通过其他方式计算移位的距离。
根据这个轮播画一个简图,我们目前知道两件事:每个面板的宽度为210px,每个面板从下一个旋转40度。如果我们将这些三角形中的一个沿其中心分开,我们就会得到一个直角三角形。
我们可以用一个基本的切线方程来确定这个图中r的长度。
这288px就是在 3D 空间中将面板平移出来的距离。
.carousel__cell:nth-child(1) { transform: rotateY( 0deg) translateZ(288px); } .carousel__cell:nth-child(2) { transform: rotateY( 40deg) translateZ(288px); } .carousel__cell:nth-child(3) { transform: rotateY( 80deg) translateZ(288px); } .carousel__cell:nth-child(4) { transform: rotateY(120deg) translateZ(288px); } .carousel__cell:nth-child(5) { transform: rotateY(160deg) translateZ(288px); } .carousel__cell:nth-child(6) { transform: rotateY(200deg) translateZ(288px); } .carousel__cell:nth-child(7) { transform: rotateY(240deg) translateZ(288px); } .carousel__cell:nth-child(8) { transform: rotateY(280deg) translateZ(288px); } .carousel__cell:nth-child(9) { transform: rotateY(320deg) translateZ(288px); }
如果我们决定改变面板的宽度或面板的数量,我们只需要将这两个变量插入到我们的方程中以获得适当的 translateZ 值。用JavaScript计算,该等式将是:
var tz = Math.round( ( cellSize / 2 ) / Math.tan( ( ( Math.PI * 2 ) / numberOfCells ) / 2 ) ); // or simplified to var tz = Math.round( ( cellSize / 2 ) / Math.tan( Math.PI / numberOfCells ) );
就像我们之前的 3D 对象一样,要显示任何一个面板,我们只需要在旋转木马上应用反向变换。
/* show fifth cell */ .carousel { transform: translateZ(-288px) rotateY(-160deg); }
完整代码:
<div class="scene"> <div class="carousel"> <div class="carousel__cell">1</div> <div class="carousel__cell">2</div> <div class="carousel__cell">3</div> <div class="carousel__cell">4</div> <div class="carousel__cell">5</div> <div class="carousel__cell">6</div> <div class="carousel__cell">7</div> <div class="carousel__cell">8</div> <div class="carousel__cell">9</div> </div> </div> <p style="text-align: center;"> <button class="previous-button">Previous</button> <button class="next-button">Next</button> </p> <style> * { box-sizing: border-box; } body { font-family: sans-serif; } .scene { border: 1px solid #CCC; margin: 40px 0; position: relative; width: 210px; height: 140px; margin: 40px auto; perspective: 1000px; } .carousel { width: 100%; height: 100%; position: absolute; transform: translateZ(-288px); transform-style: preserve-3d; transition: transform 1s; } .carousel__cell { position: absolute; width: 190px; height: 120px; left: 10px; top: 10px; border: 2px solid black; line-height: 116px; font-size: 80px; font-weight: bold; color: white; text-align: center; } .carousel__cell:nth-child(9n+1) { background: hsla( 0, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+2) { background: hsla( 40, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+3) { background: hsla( 80, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+4) { background: hsla(120, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+5) { background: hsla(160, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+6) { background: hsla(200, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+7) { background: hsla(240, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+8) { background: hsla(280, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+0) { background: hsla(320, 100%, 50%, 0.8); } .carousel__cell:nth-child(1) { transform: rotateY( 0deg) translateZ(288px); } .carousel__cell:nth-child(2) { transform: rotateY( 40deg) translateZ(288px); } .carousel__cell:nth-child(3) { transform: rotateY( 80deg) translateZ(288px); } .carousel__cell:nth-child(4) { transform: rotateY(120deg) translateZ(288px); } .carousel__cell:nth-child(5) { transform: rotateY(160deg) translateZ(288px); } .carousel__cell:nth-child(6) { transform: rotateY(200deg) translateZ(288px); } .carousel__cell:nth-child(7) { transform: rotateY(240deg) translateZ(288px); } .carousel__cell:nth-child(8) { transform: rotateY(280deg) translateZ(288px); } .carousel__cell:nth-child(9) { transform: rotateY(320deg) translateZ(288px); } </style> <Script> var carousel = document.querySelector('.carousel'); var cellCount = 9; var selectedIndex = 0; function rotateCarousel() { var angle = selectedIndex / cellCount * -360; carousel.style.transform = 'translateZ(-288px) rotateY(' + angle + 'deg)'; } var prevButton = document.querySelector('.previous-button'); prevButton.addEventListener( 'click', function() { selectedIndex--; rotateCarousel(); }); var nextButton = document.querySelector('.next-button'); nextButton.addEventListener( 'click', function() { selectedIndex++; rotateCarousel(); }); </Script>
到现在为止,您可能在想为每个面板重新编写变换样式是多么无趣。你是完全正确的。3D 对象的重复性使其适合编写JavaScript脚本。我们可以将所有单调的变换样式转移到我们的 JavaScript 中,如果处理得当,这将比写在CSS里怼硬编码版本要更灵活。
完整代码
<div class="scene"> <div class="carousel"> <div class="carousel__cell">1</div> <div class="carousel__cell">2</div> <div class="carousel__cell">3</div> <div class="carousel__cell">4</div> <div class="carousel__cell">5</div> <div class="carousel__cell">6</div> <div class="carousel__cell">7</div> <div class="carousel__cell">8</div> <div class="carousel__cell">9</div> <div class="carousel__cell">10</div> <div class="carousel__cell">11</div> <div class="carousel__cell">12</div> <div class="carousel__cell">13</div> <div class="carousel__cell">14</div> <div class="carousel__cell">15</div> </div> </div> <div class="carousel-options"> <p> <label> Cells <input class="cells-range" type="range" min="3" max="15" value="9" /> </label> </p> <p> <button class="previous-button">Previous</button> <button class="next-button">Next</button> </p> <p> Orientation: <label> <input type="radio" name="orientation" value="horizontal" checked /> horizontal </label> <label> <input type="radio" name="orientation" value="vertical" /> vertical </label> </p> </div> <style> * { box-sizing: border-box; } body { font-family: sans-serif; text-align: center; } .scene { border: 1px solid #CCC; margin: 40px 0; position: relative; width: 210px; height: 140px; margin: 80px auto; perspective: 1000px; } .carousel { width: 100%; height: 100%; position: absolute; transform: translateZ(-288px); transform-style: preserve-3d; transition: transform 1s; } .carousel__cell { position: absolute; width: 190px; height: 120px; left: 10px; top: 10px; border: 2px solid black; line-height: 116px; font-size: 80px; font-weight: bold; color: white; text-align: center; transition: transform 1s, opacity 1s; } .carousel__cell:nth-child(9n+1) { background: hsla( 0, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+2) { background: hsla( 40, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+3) { background: hsla( 80, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+4) { background: hsla(120, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+5) { background: hsla(160, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+6) { background: hsla(200, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+7) { background: hsla(240, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+8) { background: hsla(280, 100%, 50%, 0.8); } .carousel__cell:nth-child(9n+0) { background: hsla(320, 100%, 50%, 0.8); } .carousel__cell:nth-child(1) { transform: rotateY( 0deg) translateZ(288px); } .carousel__cell:nth-child(2) { transform: rotateY( 40deg) translateZ(288px); } .carousel__cell:nth-child(3) { transform: rotateY( 80deg) translateZ(288px); } .carousel__cell:nth-child(4) { transform: rotateY(120deg) translateZ(288px); } .carousel__cell:nth-child(5) { transform: rotateY(160deg) translateZ(288px); } .carousel__cell:nth-child(6) { transform: rotateY(200deg) translateZ(288px); } .carousel__cell:nth-child(7) { transform: rotateY(240deg) translateZ(288px); } .carousel__cell:nth-child(8) { transform: rotateY(280deg) translateZ(288px); } .carousel__cell:nth-child(9) { transform: rotateY(320deg) translateZ(288px); } .carousel-options { text-align: center; position: relative; z-index: 2; background: hsla(0, 0%, 100%, 0.8); } </style> <Script> var carousel = document.querySelector('.carousel'); var cells = carousel.querySelectorAll('.carousel__cell'); var cellCount; // cellCount set from cells-range input value var selectedIndex = 0; var cellWidth = carousel.offsetWidth; var cellHeight = carousel.offsetHeight; var isHorizontal = true; var rotateFn = isHorizontal ? 'rotateY' : 'rotateX'; var radius, theta; // console.log( cellWidth, cellHeight ); function rotateCarousel() { var angle = theta * selectedIndex * -1; carousel.style.transform = 'translateZ(' + -radius + 'px) ' + rotateFn + '(' + angle + 'deg)'; } var prevButton = document.querySelector('.previous-button'); prevButton.addEventListener( 'click', function() { selectedIndex--; rotateCarousel(); }); var nextButton = document.querySelector('.next-button'); nextButton.addEventListener( 'click', function() { selectedIndex++; rotateCarousel(); }); var cellsRange = document.querySelector('.cells-range'); cellsRange.addEventListener( 'change', changeCarousel ); cellsRange.addEventListener( 'input', changeCarousel ); function changeCarousel() { cellCount = cellsRange.value; theta = 360 / cellCount; var cellSize = isHorizontal ? cellWidth : cellHeight; radius = Math.round( ( cellSize / 2) / Math.tan( Math.PI / cellCount ) ); for ( var i=0; i < cells.length; i++ ) { var cell = cells[i]; if ( i < cellCount ) { // visible cell cell.style.opacity = 1; var cellAngle = theta * i; cell.style.transform = rotateFn + '(' + cellAngle + 'deg) translateZ(' + radius + 'px)'; } else { // hidden cell cell.style.opacity = 0; cell.style.transform = 'none'; } } rotateCarousel(); } var orientationRadios = document.querySelectorAll('input[name="orientation"]'); ( function() { for ( var i=0; i < orientationRadios.length; i++ ) { var radio = orientationRadios[i]; radio.addEventListener( 'change', onOrientationChange ); } })(); function onOrientationChange() { var checkedRadio = document.querySelector('input[name="orientation"]:checked'); isHorizontal = checkedRadio.value == 'horizontal'; rotateFn = isHorizontal ? 'rotateY' : 'rotateX'; changeCarousel(); } // set initials onOrientationChange(); </Script>
我们不仅可以改变单元格的数量,我们甚至可以将旋转木马的方向从水平改为垂直。