如何实现Angular角丝带组件的动态尺寸?
动态尺寸角丝带Angular组件实现问题
背景
参考纯CSS角丝带效果,我开发了一个Angular组件。现在需要通过暴露@Input size(单位如px)实现丝带尺寸的动态调整。最初考虑使用CSS transform scale属性,但缩放后需要调整位置以贴合父元素角落,相关数学计算十分繁琐。后来通过calc计算transform-origin的值实现了缩放锚定,现在需要将四个位置的计算公式简化为可返回对应transform-originX、Y值的函数。
当前代码实现
Angular组件代码
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, Renderer2 } from '@angular/core'; @Component({ selector: 'ribbon', template: ` <div class="ribbon ribbon-{{position}}" [style.cursor]="isClickable ? 'pointer' : 'default'" (click)="this.click.emit()"> <div class="ribbon-label"><span>{{ label }}</span></div> </div>`, styleUrls: ['ribbon.component.scss'] }) export class RibbonComponent implements AfterViewInit { @Input() position: 'top-left' | 'top-right' | 'bottom-right' | 'bottom-left' = 'top-left'; /** Default value is a No-Break Space so as to not break the ribbon shape when no label is set. */ @Input() label = '\u00A0'; /** Can be set to one of the scss color variables defined from the theme.scss * or a custom CSS color value such as `rgba` , HEX, HSL, or a CSS predefined color name. */ @Input() color: 'primary' | 'secondary' | 'warning' | 'danger' | string = 'danger'; @Output() click = new EventEmitter<void>(); get isClickable(): boolean { return !!this.click.observers.length; } constructor(private renderer: Renderer2, private el: ElementRef) { } ngAfterViewInit() { // Force parent/container element to have position relative // so that the ribbon's offset using position absolute is secured const parent = this.el.nativeElement.parentNode; this.renderer.setStyle(parent, 'position', 'relative'); } }
SCSS样式代码
$primary-color: #3498db; // base color of the ribbon $secondary-color: darken($primary-color, 10%); // darkened base color for the folded part $label-color: #fff; // TODO: Make side of the square dynamic. This can be done by binding transform scale but offset calculation will be tricky $main-width: 150px; // ribbon is wrapped by a square $ribbon-width: $main-width * 1.5; // 225px $ribbon-underside-length: $main-width * 0.033; // ~5px, How much of the underside of the folded ribbon is exposed. $ribbon-padding: $main-width * 0.1; // ~15px $label-min-height: $main-width *0.12; // ~18px $offset: $ribbon-underside-length * -2; // ~-10px .ribbon { width: $main-width; height: $main-width; overflow: hidden; position: absolute; &::before, &::after { position: absolute; z-index: -1; content: ""; display: block; border: $ribbon-underside-length solid $secondary-color; } .ribbon-label { position: absolute; display: block; width: $ribbon-width; padding: $ribbon-padding 0; background-color: $primary-color; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); color: $label-color; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); text-transform: uppercase; text-align: center; line-height: $label-min-height !important; // lock in so the ribbon's thickness is unaffected by the label's font-size } } @mixin ribbon-position($positionY, $positionX, $angle) { .ribbon-#{$positionY}-#{$positionX} { #{$positionY}: $offset; // top or bottom #{$positionX}: $offset; // left or right // #region folded sides of the ribbon &::before, &::after { border-#{$positionY}-color: transparent; border-#{$positionX}-color: transparent; } &::before { #{$positionY}: 0; // top or bottom #{if($positionX == 'left', 'right', 'left')}: 0; // anchor on the opposite side } &::after { #{if($positionY == 'top', 'bottom', 'top')}: 0; // anchor on the opposite side #{$positionX}: 0; // left or right } // #endregion .ribbon-label { #{if($positionX == 'left', 'right', 'left')}: -25px; // offset on opposite side #{$positionY}: 30px; // top or bottom transform: rotate(#{$angle}deg); // Corrective centering of label for top-right and bottom-left ribbon @if ($positionY == 'top' and $positionX == 'right' ) or ($positionY == 'bottom' and $positionX == 'left') { span { padding-left: 1.5ch; // add padding equivalent to 150% width of "0" in current font-size } } } } } @include ribbon-position(top, left, -45); @include ribbon-position(top, right, 45); @include ribbon-position(bottom, left, 225); @include ribbon-position(bottom, right, -225);
已取得的进展
通过calc计算transform-origin实现了缩放锚定,其中undersideWidth代表丝带折叠部分的外露宽度,amount为缩放值(取值范围0-1)。目前四个位置的计算公式如下:
- top-left
transform: scale(amount); transform-origin: calc(0% + (((1 - amount) * undersideWidth)) + (1 + amount) * undersideWidth) calc((0% + ((1 - amount) * undersideWidth)) + (1 + amount) * undersideWidth);
- top-right
transform: scale(amount); transform-origin: calc((100% - ((1 - amount) * undersideWidth)) + (1 + amount) * undersideWidth* -1) calc((0% + ((1 - amount) * undersideWidth)) + (1 + amount) * undersideWidth);
- bottom-left
transform: scale(amount); transform-origin: calc((0% + ((1 - amount) * undersideWidth)) + (1 + amount) * undersideWidth) calc(((100% - ((1 - 0.5) * undersideWidth)) - ((1 - amount) * undersideWidth)) + (1 + amount) * undersideWidth* -1);
- bottom-right
transform: scale(amount); transform-origin: calc((100% - ((1 - amount) * undersideWidth)) + (1 + amount) * undersideWidth* -1) calc((100% - ((1 - amount) * undersideWidth)) + (1 + amount) * undersideWidth* -1);
这些公式可按上下/左右位置拆分逻辑:Y轴参考点top-*为0%、bottom-*为100%;X轴参考点*-left为0%、*-right为100%,其余部分基于缩放后的外露宽度计算偏移。现在需要将这些公式简化为可返回对应transform-originX、Y值的函数。
内容的提问来源于stack exchange,提问作者Alex Pappas
相关产品推荐
相关产品推荐

