google 是我们的朋友,在this David DeSandro's article 你拥有所有的“成分”。是的,是 css 和 javascript,但您可以轻松转换为 Angular
我们使用 viewChild 和 ViewChildren 来获取单元格
@ViewChild('carousel') carousel:ElementRef
@ViewChildren('carousel__cell') cells:QueryList<ElementRef>
我们需要一些辅助变量:
selectedIndex = 0;
cellWidth:number;
cellHeight:number;
isHorizontal:boolean = true;
rotateFn = this.isHorizontal ? 'rotateY' : 'rotateX';
radius:number
theta:number;
get cellCount()
{
return this.cells.length;
}
现在创建一个函数 initCarousel
initCarousel() {
this.theta = 360 / this.cellCount;
const cellSize = this.isHorizontal ? this.cellWidth : this.cellHeight;
this.radius = Math.round( ( cellSize / 2) / Math.tan( Math.PI / this.cellCount ) );
this.cells.forEach((cell:ElementRef,i:number)=>
{
if (i<this.cellCount)
{
cell.nativeElement.style.opacity=1
const cellAngle=this.theta*i;
cell.nativeElement.style.transform = this.rotateFn + '(' + cellAngle + 'deg) translateZ(' + this.radius + 'px)';
}
else
{
cell.nativeElement.style.opacity = 0;
cell.nativeElement.style.transform = 'none';
}
})
this.rotateCarousel();
}
我们在 ngAfterViewInit 中调用
ngAfterViewInit()
{
this.cellWidth = this.carousel.nativeElement.offsetWidth;
this.cellHeight = this.carousel.nativeElement.offsetHeight;
this.initCarousel()
}
你可以在stackblitz看到
更新问题确实是关于“pseudo-3d”的,而且更简单。我们需要应用变换一个比例和一个位置,所以,我们可以有这样的功能
getStyle(index:number)
{
if (!this.cellCount)
return null;
const angle=(index-this.selectedIndex)*2*Math.PI/this.cellCount
const scale=((75)+25*Math.cos(angle))/100
return {
left:-75+150*Math.sin(angle)+'px',
transform:'scale('+scale+')',
position:'absolute',
"z-index":Math.floor(100*scale)
}
}
并应用于轮播
<div class="carousel" style="position:relative">
<div *ngFor="let i of [0,1,2,3,4,5,6]" class="carousel__cell"
[ngStyle]="getStyle(i)">{{i}}
</div>
</div>
Update 2 使用 Angular 动画。好吧,最后一个轮播看起来像是我们想要实现的。对于特定情况,我们可以,例如在位置 0,2 和 7 定义了 9 个位置和 3 个图像
在构造函数中注入 AnimationBuilder
constructor(private builder: AnimationBuilder) {}
并定义为变量
private player: AnimationPlayer;
timer = 1000;
animates = [0, 2, 7];
我们定义一个辅助变量
movements = [
{ pos: 0, right: [1, 2], left: [8, 7] },
{ pos: 2, right: [3, 4, 5, 6, 7], left: [1, 0] },
{ pos: 7, right: [8, 0], left: [6, 5, 4, 3, 2] }
];
这个想法是手动创建动画,所以如果例如一个元素在 pos=0 中,我们点击“右”我们创建 2 个动画以到达位置 1,2
是一些复杂的函数,我放了cmets
animateViews(direction: string) {
//with each element of the array [0,2,7]
this.animates.forEach((x: number, index: number) => {
//get the item and the mov
const mov = this.movements.find(m => m.pos == x);
const item = this.itemsView.find((_x, i) => i == index);
//create an array of animations
const animations = mov[direction].map(m => {
const angle = (m * 2 * Math.PI) / 9;
const scale = (75 + 25 * Math.cos(angle)) / 100;
const applystyle = {
left: -75 + 150 * Math.sin(angle) + "px",
transform: "scale(" + scale + ")",
position: "absolute",
"z-index": Math.floor(100 * scale)
};
return animate(
this.timer / mov[direction].length + "ms",
style(applystyle)
);
});
//we create the animation with builder.build
const myAnimation = this.builder.build(animations);
//the animation is applied to the element
this.player = myAnimation.create(item.nativeElement);
//when finished, change the value of the array [0,2,7]
this.player.onDone(
() =>
(this.animates[index] = mov[direction][mov[direction].length - 1])
);
this.player.play();
});
}
new stackblitz
更新 3*:要理解公式,请拿纸和铅笔并使用一些数学:想象一个半径为“r”的圆周。从中心画一条垂直线并在圆周上标记一个点。将“角度”称为从垂直到连接点与圆周中心的直线顺时针方向的角度。
left=r*Math.sin(angle)-width_of_img/2
对于比例,我们需要记住,当“角度”为 0 时,比例为 1,当“角度”为 180 度时,比例为 0.5(或 1 和 0 之间的另一个数字 - 越少,深度越大)。将此值称为“minScale”。这意味着 Math.cos(0)=1 比例为 1,Math.cos(180degrees)=-1 比例为 minScale。所以
scale=(1+minScale)/2+(1-minScale)/2*Math.cos(angle)