iT邦幫忙

第 11 屆 iThome 鐵人賽

0
Modern Web

Angular新手村學習筆記(2019)系列 第 32

Day31_Angular and CSS Grid(ng conf 2019)

  • 分享至 

  • xImage
  •  

最後一篇,雖然已經過30天了,我來加碼我最喜歡的CSS
個人CSS非常爛,CSS最讓人討厭的是各別語法都很簡單,難在組合
只好努力惡補囉@@~

先推金魚大大「金魚都能懂的網頁切版」

https://ithelp.ithome.com.tw/users/20112550/ironman/2623
https://www.youtube.com/watch?v=rwTMBmnIHcY&list=PLqivELodHt3hxeuLX8PYaI8u1GcDaBoJo

我的CSS預計學習計畫

參考書:

  • CSS 大全, 4/e (CSS: The Definitive Guide: Visual Presentation for the Web)
    https://www.tenlong.com.tw/products/9789864769919?list_name=srh
    雖然很大一本,但2019-01-25才出版的,內容蠻完整的(例如排版的部分有flexbox、grid)
    適合當字點,也適合針對自己不懂的部分惡補(例如:selector)
    大神的文章分享雖然很好,但通常因為篇幅有限,只教精華
    我會把大神的分享想像成(學習重點、這個東西值得學習)

CSS Grid在本書的第13章-網格排版
建議先啃一啃無聊的基礎(網路文章較片段)

學習SOP

  1. 看六角學院的大神們的鐵人賽文章(廖洧杰、卡斯伯…)
  2. 觀摩其他UI的open source的css或scss專案(學習專案架構)
    例如:
    早期的pure css
    https://purecss.io/
    Material Design for Bootstrap 4
    https://github.com/mdbootstrap/bootstrap-material-design
    primeng
    https://github.com/primefaces/primeng
    ngx-admin
    https://github.com/akveo/ngx-admin
  3. 再來看國內大大切版影片,是否看得懂
    例如:youtube搜尋「切版 css」
  4. 至少學一套最好的layout系統(相容性、先進)
    例如:網路有看到flex相容性不太好的說法

線上教學課程的部分,我有買udemy
Jonas Schmedtmann老師的
Advanced CSS and Sass: Flexbox, Grid, Animations and More!
有帶3個專案,過程中還會重構,不過英文不好聽不懂老師的講解會很辛苦(看圖google)
小弟英文不好,打算上面1~4學好後再來挑戰

最後…
既然 CSS Grid 可以出現在 ng conf 2019,代表應該是值得學習吧

Angular and CSS Grid: Get ready to fall in love | Bill Odom
https://www.youtube.com/watch?v=lh6n0JxXD_g&list=PLOETEcp3DkCpimylVKTDe968yNmNIajlR&index=36
原始碼
https://github.com/wnodom/spacewalk

ng serve後,開啟
http://localhost:4200/menu
才會到選單,可以一項一項看,重點在css檔

先看一下專案唯二的service

imageItems: Observable<SpaceImage[]>;
/* 定義在space-imageBaseUrl.service.ts
export interface SpaceImage {
  label: string;
  description: string;
  infoUrl: string;
  imageFilename: string;
  imageUrl?: string;
  imageThumbnailUrl?: string;
} 

space-images 的資料寫死在 space-images-data.ts

因為 SpaceImagesService 有使用的 RxJS 寫法
如果把靜態資料改寫到資料庫應該有使用價值
SpaceImage資料型別 對應到 table欄位
圖片             放在 assets/space
Service         寫在 space-images.service.ts (含SpaceImage資料型別的定義)
SpaceImage的資料 寫在 space-images-data.ts  

再來開始看專案的每一選項

Best Feature Ever!!1! (menu的選項)

http://localhost:4200/example-centering

  • centering
    HTML
<img src="/assets/misc/satellite-emoji.png">

CSS

:host {
  display: grid; // 重點是這一行,使用css grid
  height: 100vh; // 100% viewport hight
  place-items: center;

  background-size: contain;
  background-image:
    radial-gradient(circle at 100%, #000, #333 65%, #eee 75%, transparent 75%),
    url("/assets/misc/starfield.png")
  ;
}

Playing With Blocks

http://localhost:4200/example-blocks
HTML

<div *ngFor="let label of labels" [title]="label">
  {{ label }}
</div>
<!--
    labels: string[] = [];
    for (let i = 1; i < 10; i++) {
      this.labels.push('' + i); // 1~9 的blocks
    }
-->

CSS

:host {
  padding: 20px;
  display: grid;
  
  // https://developer.mozilla.org/zh-TW/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout
  // 格線軌道(Grid Track)
  // 定義行、列
  // fr 單位
  // 格線軌道可以使用任何單位定義,不過格線引入了額外的單位,以助於建立有彈性的格線軌道
  // 新的單位 fr 代表格線容器內,可用空間的分塊(fraction)
  grid-template-columns: repeat(5, 100px);
                      vvvvvvvvvvvvvvvv
  grid-template-rows: repeat(5, 100px);
                                ^^^^^ 可以用任何單位
  // repeat(5, 100px); 等於
  // grid-template-columns: 100px 100px 100px 100px 100px;
  
  grid-gap: 20px;
  ^^^^^^^^
  // 每個grid的間隔
  // 可以拆成grid-column-gap、grid-row-gap properties
}

div {
  // 置中 label
  display: grid;
  place-content: center;
  
  font-family: sans-serif;
  font-size: 200%;
  border: 3px solid darkgreen;
  border-radius: 10px;
  background-color: rgba(0, 100, 0, 0.25);
}

Bill Odom 特別強調

  1. grid is not an HTML construct
  2. grid doesn't really exist in HTML

可以用開發者工具看Elements
(所以Angular Elements不等於HTML?)

<!--可以看到grid的layout-->
<example-blocks _nghost-dhy-c1></example-blocks>
  • Firefox在Inspector/Layout有個Grid panel,可以看Grid

Some Assembly Required

http://localhost:4200/example-sandbox
github上的sandbox跟demo的程式有不少差異

HTML

<div class="sandbox">
  <header>
    <h1>Pale Blue Dot</h1>
  </header>

  <main>
    <p>
      Consider...
    </p>

    <p>
      The Earth ... 
    </p>

    <p>
      Our posturings, ... 
    </p>

    <p>
      The Earth ... 
    </p>

    <p>
      It has been said that ...
    </p>
  </main>

  <img class="carl" src="/assets/misc/Carl_Sagan_-_1980.jpg">

  <img class="pbd" src="/assets/misc/Pale_Blue_Dot.png">

  <aside>
    <h2>
      If we crave some cosmic purpose, then let us find
      ourselves a worthy goal.
    </h2>
  </aside>

  <footer>
    <h3>
      Text from
      <em>Pale Blue Dot: A Vision of the Human Future in Space</em>
      by Carl Sagan (1994)
    </h3>
  </footer>
</div>

CSS

/* See comments at the bottom for changes to try. */

// sandbox 裡面的,背景淺藍
.sandbox {
  display:grid;
  grid-gap:50px; // 每個grid相距50px
  
  // 變成2欄,欄寬分別是500px 800px
  grid-template-columns:500px 800px
  
  background-color: lightblue;
  padding: 20px;
}

// sanbox 底下的element,背景白色
.sandbox > * {
  background-color: white;
  padding: 20px;
}

img {
  width: 100%;
}

header {
    font-size:200%;
    font-weight:bold;
    
    grid-column: span 2; // 合併2欄的空間
}

aside {
}

main {
}

.carl {
    // 移到第1欄位(會導致其他grid位置順序改變)
    grid-column-start: 1; 
    grid-row-stat: 1; // 移到第1列
    width: 100%;
}

.pbd {
}

footer {
    grid-column: span 2; // 合併2欄的空間
}

/*

.sandbox {
  display: grid;
  grid-gap: 20px;
  grid-template:
    " header header header "
    " aside  main   carl   "
    " aside  main   pbd    "
    " footer footer footer "
    / 200px  2fr    1fr
  ;

  background-color: lightblue;
  padding: 20px;
}

@media (max-width: 600px) {

  .sandbox {
    grid-template:
      " carl   "
      " header "      
      " aside  "
      " main   "
      " pbd    "
      " footer "
    ;
  }
}

.sandbox > * {
  background-color: white;
  padding: 20px;
}

img {
  width: 100%;
}

header { grid-area: header; }
aside  { grid-area: aside;  }
main   { grid-area: main;   }
.carl  { grid-area: carl;   }
.pbd   { grid-area: pbd;    }
footer { grid-area: footer; }
*/

hidden

github那個專案裡沒有,我手打出來
HTML

                                           VVVVV label值會是1~5,可在css下title="3"
<div *ngFor="let label of labels" [title]="label">
                                  ^^^^^^^^^^^^^^ property
    {{ label }}
</div>

TS

...
labels: string[] = [];
constructor(){
    for(let i=1; i<5; i++){
        this.labels.push(''+i);
    }
}

CSS

:host{
    display: grid;
    padding: 20px;
    
    grid-template-columns: repeat(3, 200px);
    grid-gap: 20px;
}

div{
    display: grid;
    place-content: center;
    font-family: sans-serif;
    font-size: 200%;
    border: 3px solid darkblue;
    border-radius: 10px;
    background-color: rgba(0,0,100,0.25);
}

// div裡面,有property title=3的
div[title="3"]{
    // 1.先確定選到了
    border: 4px solid red;
    background-color: lightgreen;
    // 2.試玩語法
    // 讓這個div跑到grid的第2列
    grid-row: 2; 
    // 從column 1 到 last column
    grid-column: 1 / -1 
    // -1 從尾端數回來
}

grid-template-area

以上單一頁看起來很簡單,但若堆疊起來呢?(stack)
遇到完整專案怎麼辦?
使用grid-template-area可以比其他排板系統更省tag

這邊的程式碼不完整,
我覺得這一節較難
建議看卡司伯大大的文章
https://wcc723.github.io/css/2017/03/22/css-grid-layout/

此處影片裡的workshop-1(時間1:02:00)其實是github的example-sandbox
http://localhost:4200/example-sandbox
標題為Pale Blue Dot

.sandbox{
    background-color:lightblue;
    padding:20px;
    
    display:grid;
    grid-gap:50px; // 下面5個areas之間會隔50px
    
    grid-template-areas:
        " header " // 在.sandbox底下定義5個areas,而且依序顯示
        " main   "
        " footer "
        " carl   "
        " pbd    "
        // 如果改成
        // " main   "
        // " header "
        // 則 main 就會顯示在 header 前面
        
        // 也可以寫成
        " header header "
        " carl   main   " // carl 會排在 main 左邊,即時放大也一樣
        " footer footer "
        " pbd    pbd    "

    grid-template: // 不知道為何要去掉-areas
        // 可以設定area的高度
        " header header " 300px
        " carl   main   " auto // 其他沒特別設定的要加auto,不然會跑板
        " footer footer " auto
        " pbd    pbd    " auto
        / 200px  500px (欄位的寬度)
        // 也可以改成用fr
        // fr 代表格線容器內,可用空間的分塊(fraction)
        / 200px  1fr (前面200px 後面1fr)
        / 1fr    1fr (一半一半,正確寫法)
        / 50%    50% (也是一半一半,但grid-gap會跟1fr 1fr不一樣)
    ;

}

header { grid-area:header; } 
                   ^^^^^^ 指定grid-template-areas:設定的區域(area)
main   { grid-area: main;   }
footer { grid-area: footer; }   
.carl  { grid-area: carl;   }
.pbd   { grid-area: pbd;    }     
aside  { grid-area: aside;  } 

example-image-gallery(時間 1:16:21)

localhost:4200/example-image-gallery
TS

  static label = 'Ooooh Pretty';

  imageItems: Observable<SpaceImage[]>;

  constructor(svc: SpaceImagesService) {
    this.imageItems = svc.load('planetary-nebulae');
    // const imageBaseUrl = '/assets/space';
    // 去讀/assets/space/planetary-nebulae裡的jpg檔,值得看原始碼學習

HTML

<img
  *ngFor="let imageItem of imageItems | async"
  [src]="imageItem.imageThumbnailUrl"
  [title]="imageItem.label"
>

CSS

:host {
  display: grid;                           VVVVVV grid寬度
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  grid-auto-rows: 150px;        ^^^^^^^^^ 自動補滿可用空間
  grid-gap: 20px;

  // vh:view height,螢幕可視範圍高度,讓圖片可以隨著螢幕大小調整
  min-height: 100vh; 
  padding: 20px;
  background-color: darkgrey;
}

img {
  width: 100%;
  height: 100%;
  vvvvvvvvvv // tag 的 src 載入內容後,會取代原本元素
  object-fit: cover; 
              ^^^^^  // 填滿元素的寬度及高度(維持原比例),通常會剪掉部分的物件
  // https://blog.camel2243.com/2017/01/21/css-object-fit-object-position-%E8%AA%BF%E6%95%B4%E7%BD%AE%E6%8F%9B%E5%85%83%E7%B4%A0img-%E7%AD%89%E7%9A%84%E5%85%A7%E5%AE%B9/
  // IE、EDGE好像不支援
}

example-debugging(時間 1:21:15)

localhost:4200/example-debugging

這個範例只是展示grid可以很簡單的排板

幾個用到的語法如下:

定義 5欄4列
grid-template-columns: repeat(5, 100px);
grid-template-rows: repeat(5, 100px);
grid-gap: 20px;

grid-column: 1; // 1欄
grid-row: 1 / -1; // 第1列到最後1列
grid-row-end: -2; // 到倒數第2列
grid-column-end: -2; // 倒倒數第2欄

原始碼(沒有註解)
TS

  static label = 'Good Grids Gone Bad';

  labels: string[] = [];

  constructor() {
    for (let i = 1; i < 10; i++) {
      this.labels.push('' + i);
    }
  }

HTML

<div *ngFor="let label of labels" [title]="label">
  {{ label }}
</div>

CSS

:host {
  padding: 20px;
  display: grid;

  min-height: 100vh;
  place-content: center;
  
  grid-template-columns: repeat(5, 100px);
  grid-template-rows: repeat(5, 100px);
  grid-gap: 20px;
}

div {
  display: grid;
  place-content: center;
  font-family: sans-serif;
  font-size: 200%;
  border: 3px solid darkgreen;
  border-radius: 10px;
  background-color: rgba(0, 100, 0, 0.25);
}

[title="3"] {
  background: lightblue;
  grid-row: 3;
  grid-column: 1 / span 3;
}

[title="4"] {
  background: pink;
  grid-row: span 2;
}

[title="5"] {
  background: cornsilk;
  grid-row-end: -1;
  grid-column-end: -1;
}

[title="7"] {
  grid-column: 1;
  grid-row: 1 / -1;
}

/*
:host > * {
  outline: 3px solid red;
  outline-offset: 5px;
}
*/

[title="9"] {
  border: unset;
  color: transparent;
  background: transparent;
  grid-row-end: -2;
  grid-column-end: -2;
}

example-image-gallery(時間 1:23:00)

label : When Worlds Collide
localhost:4200/example-image-gallery
展示 圖片 + grid排板

CSS的部分很簡單,重點在要看懂 SpaceImagesService 裡的程式

TS

imageItems: Observable<SpaceImage[]>;
/* 定義在space-imageBaseUrl.service.ts
export interface SpaceImage {
  label: string;
  description: string;
  infoUrl: string;
  imageFilename: string;
  imageUrl?: string;
  imageThumbnailUrl?: string;
} 

space-images 的資料寫死在 space-images-data.ts

因為 SpaceImagesService 有使用的 RxJS 寫法
如果把靜態資料改寫到資料庫應該有使用價值
SpaceImage資料型別 對應到 table欄位
圖片             放在 assets/space
Service         寫在 space-images.service.ts (含SpaceImage資料型別的定義)
SpaceImage的資料 寫在 space-images-data.ts  
*/

constructor(svc: SpaceImagesService) {
  // assets/space/plants 裡的照片的Array
  this.imageItems = svc.load('planets');
}

HTML

<img *ngFor="let imageItem of imageItems | async"
  [src]="imageItem.imageThumbnailUrl"
  [title]="imageItem.label" <!--用title來select-->
>

CSS

:host {
  display: grid;
  grid-template-columns: repeat(5, 100px);
  grid-template-rows: repeat(5, 100px);
  grid-gap: 20px;
  align-items: start;
  // 很容易會google到其他排板系統
  // https://developer.mozilla.org/zh-CN/docs/Web/CSS/align-items
  // start 是 Positional alignment : Pack items from the start
  // The item is packed flush to each other toward the start edge of the alignment container in the appropriate axis.
  
  padding: 20px;
  background-color: cornsilk;
}

img {
  width: 100%;
  outline: 3px solid red;
}

[title=Mercury] {
       ^^^^^^^ 資料來源 space-images-data.ts
  grid-row-end: -1;
  grid-column-end: -1;
}

[title=Earth] {
  grid-row: 4;
  grid-column: 4;
  z-index: 1;
}

[title=Jupiter] {
  grid-row: 3 / span 2;
  grid-column: span 2;
  // 改成 grid-column: 3       / span 2; 
  // 觀察grid會從      第3欄開始   span2欄
}

[title=Saturn] {
  grid-row: 2 / span 2;
  grid-column: 3 / span 3;
}

Playing With a Full Deck(時間 1:27:00)

localhost:4200/example-playing-cards
TS

  // 四種花色
  suits: string[] = [
    'spades',
    'hearts',
    'clubs',
    'diamonds',
  ];

  ranks: string[] =
    [ 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K' ];

HTML

<ng-container *ngFor="let suit of suits"> <!--花色-->
  <ng-container *ngFor="let rank of ranks"> <!--A~K-->
    <playing-card [rank]="rank" [suit]="suit"></playing-card>
     ^^^^^^^^^^^^  ^^^^ @Input   ^^^^ @Input           
  </ng-container>
</ng-container>

CSS

:host {
  display: grid;
  grid-template-columns: repeat(auto-fill, 2.5in);
  grid-gap: 0.25in;
  padding: 0.25in;
  background-color: green;
}

再來看<playing-card [rank]="rank" [suit]="suit"></playing-card>
牌的設計在另一個Component裡,樣式是用scss

看不懂,等我看得懂再回來補註解(超不負責任)

TS

import { Component, OnInit, Input } from '@angular/core';

const imageDir = '/assets/card-faces';

const pips: Record<string, string> = {
  clubs:    '\u2663',
  diamonds: '\u2666',
  hearts:   '\u2665',
  spades:   '\u2660',
};

/* tslint:disable object-literal-key-quotes */
const rankToNumberOfPips: Record<string, number> = {
  'A' :  1,
  '2' :  2,
  '3' :  3,
  '4' :  4,
  '5' :  5,
  '6' :  6,
  '7' :  7,
  '8' :  8,
  '9' :  9,
  '10': 10,
  'J' :  0,
  'Q' :  0,
  'K' :  0,
};

@Component({
  selector: 'playing-card',
  templateUrl: './playing-card.component.html',
  styleUrls: ['./playing-card.component.scss']
})
export class PlayingCardComponent implements OnInit {
  @Input() suit = '';
  @Input() rank = '';

  pip = '';
  designPips: string[] = [];
  cardImageUrl = '';

  ngOnInit() {
    const numPips: number = rankToNumberOfPips[this.rank];

    this.pip = pips[this.suit];

    if (numPips > 0) {
      this.designPips = Array(numPips).fill(this.pip);
    } else {
      this.cardImageUrl = imageDir + '/' + this.rank + '-' + this.suit + '.png';
    }
  }
}

HTML

<div class="playing-card front" [ngClass]="suit">
  <div class="rank">{{ rank }}</div> <!--右下角的A~K-->
  <div class="corner-pip">{{ pip }}</div><!--右下角的花色-->
  <div class="design" [ngClass]=" 'rank-' + rank ">
    <img *ngIf="cardImageUrl" [src]="cardImageUrl">
    <div *ngFor="let designPip of designPips; index as i"
      [ngClass]=" 'p' + (i + 1) "
    >
      {{ pip }}
    </div>
  </div>
  <div class="inverted corner-pip">{{ pip }}</div><!--右下角的花色-->
  <div class="inverted rank">{{ rank }}</div><!--右下角的A~K-->
</div>

CSS

:host {
  display: block; // 區塊元素
}

.playing-card {
  font-family: Helvetica, sans-serif;
  font-weight: bold;
  font-size: 24px;

  width: 2.5in;
  height: 3.5in;
  margin: 0;
  padding: 10px;
  border: 2px solid black;
  border-radius: 10px;

  &.front {
    background-color: white;
  }

  display: grid;

  grid-template:
    " rank        .       .                   "  auto
    " corner-pip  design  .                   "  auto
    " .           design  .                   "  1fr
    " .           design  inverted-corner-pip "  auto
    " .           .       inverted-rank       "  auto
    / auto        1fr     auto
  ;
}

.rank, .corner-pip, .design {
  align-self: center;
  justify-self: center;
}

.rank {
  grid-area: rank;
}

.corner-pip {
  grid-area: corner-pip;
}

.design {
  align-items: center;
  grid-area: design;
  display: grid;
  font-size: 54px;
  height: 100%;
}

.design.rank-A {
  grid-template:
    " p1 " 1fr
    / 1fr
  ;
  font-size: 144px;
}

.design.rank-J,
.design.rank-Q,
.design.rank-K {
  grid-template:
    " p1 " 1fr
    / 1fr
  ;

  align-items: middle;
  justify-items: center;

  img {
    max-width: 100%;
    max-height: 100%;
  }
}

.design.rank-2 {
  grid-template:
    " .   .   .   " 1fr
    " .   p1  .   " 1fr
    " .   .   .   " 1fr
    " .   .   .   " 1fr
    " .   .   .   " 1fr
    " .   p2  .   " 1fr
    " .   .   .   " 1fr
    / 1fr 1fr 1fr
  ;
  .p2 {
    @extend .inverted;
  }
}

.design.rank-3 {
  grid-template:
    " .   p1  .   " 1fr
    " .   p2  .   " 1fr
    " .   p3  .   " 1fr
    / 1fr 1fr 1fr
  ;
  .p3 {
    @extend .inverted;
  }
}

.design.rank-4 {
  grid-template:
    " .   .   .   " 1fr
    " p1  .   p2  " 1fr
    " .   .   .   " 1fr
    " .   .   .   " 1fr
    " .   .   .   " 1fr
    " p3  .   p4  " 1fr
    " .   .   .   " 1fr
    / 1fr 1fr 1fr
  ;
  .p3, .p4 {
    @extend .inverted;
  }
}

.design.rank-5 {
  grid-template:
    " .   .   .   " 1fr
    " p1  .   p2  " 1fr
    " .   .   .   " 1fr
    " .   p3  .   " 1fr
    " .   .   .   " 1fr
    " p4  .   p5  " 1fr
    " .   .   .   " 1fr
    / 1fr 1fr 1fr
  ;
  .p4, .p5 {
    @extend .inverted;
  }
}

.design.rank-6 {
  grid-template:
    " .   .   .   " 1fr
    " p1  .   p2  " 1fr
    " .   .   .   " 1fr
    " p3  .   p4  " 1fr
    " .   .   .   " 1fr
    " p5  .   p6  " 1fr
    " .   .   .   " 1fr
    / 1fr 1fr 1fr
  ;
  .p5, .p6 {
    @extend .inverted;
  }
}

.design.rank-7 {
  grid-template:
    " p1  .   p2  " 1fr
    " p1  p3  p2  " 1fr
    " p4  p3  p5  " 1fr
    " p4  .   p5  " 1fr
    " p6  .   p7  " 1fr
    " p6  .   p7  " 1fr
    / 1fr 1fr 1fr
  ;
  .p6, .p7 {
    @extend .inverted;
  }
}

.design.rank-8 {
  grid-template:
    " p1  .   p2  " 1fr
    " p1  p3  p2  " 1fr
    " p4  p3  p5  " 1fr
    " p4  p7  p5  " 1fr
    " p6  p7  p8  " 1fr
    " p6  .   p8  " 1fr
    / 1fr 1fr 1fr
  ;
  .p6, .p7, .p8 {
    @extend .inverted;
  }
}

.design.rank-9 {
  grid-template:
    " p1  .   p3  " 1fr
    " p1  .   p3  " 1fr
    " p4  .   p5  " 1fr
    " p4  p2  p5  " 1fr
    " p6  p2  p7  " 1fr
    " p6  .   p7  " 1fr
    " p8  .   p9  " 1fr
    " p8  .   p9  " 1fr
    / 1fr 1fr 1fr
  ;
  .p6, .p7, .p8, .p9 {
    @extend .inverted;
  }
}

.design.rank-10 {
  grid-template:
    " p1  .   p3  " 1fr
    " p1  p2  p3  " 1fr
    " p4  p2  p5  " 1fr
    " p4  .   p5  " 1fr
    " p6  .   p7  " 1fr
    " p6  p8  p7  " 1fr
    " p9  p8  p10 " 1fr
    " p9  .   p10 " 1fr
    / 1fr 1fr 1fr
  ;
  .p6, .p7, .p8, .p9, .p10 {
    @extend .inverted;
  }
}

.p1  { grid-area: p1  };
.p2  { grid-area: p2  };
.p3  { grid-area: p3  };
.p4  { grid-area: p4  };
.p5  { grid-area: p5  };
.p6  { grid-area: p6  };
.p7  { grid-area: p7  };
.p8  { grid-area: p8  };
.p9  { grid-area: p9  };
.p10 { grid-area: p10 };
.p11 { grid-area: p11 };
.p12 { grid-area: p12 };
.p13 { grid-area: p13 };


.inverted.corner-pip {
  grid-area: inverted-corner-pip;
}

.inverted.rank {
  grid-area: inverted-rank;
}

.inverted {
  transform: rotate(180deg);
}

.hearts, .diamonds {
  color: rgb(212, 0, 0);
}

.spades, .clubs {
  color: black;
}

I Object!(時間 1:33:00)

localhost:4200/example-media-objects

這個範例一樣設計在一個componet裡
<media-object>

TS

  imageItems: Observable<SpaceImage[]>;

  constructor(svc: SpaceImagesService) {
    this.imageItems = svc.load('women-in-astronomy')
      .pipe(
        map(data => shuffleArrayInPlace([...data]))
                    ^^^^^^^^^^^^^^^^^^^ 打亂array的元素
      )
    ;
  }
  
  export function shuffleArrayInPlace(array: Array<any>) {
  let currentIndex = array.length;
  let temporaryValue: any;
  let randomIndex: number;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {
    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

HTML

<media-object
  *ngFor="let imageItem of imageItems | async" 
  [header]="imageItem.label"
  [image]="imageItem.imageThumbnailUrl"
  [description]="imageItem.description"
></media-object>

每一張卡片的排板
CSS

:host {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  grid-gap: 20px;

  min-height: 100%;
  padding: 20px;
  background-color: darkgrey;
}

用component來設計卡片
TS

  @Input() header?: string;
  @Input() image?: string;
  @Input() description?: string;

HTML

<img [src]="image">
<header>{{ header }}</header>
<div class="description">{{ description }}</div>

CSS

:host {
  display: grid;

  // https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template
  // 欄一   欄二 列一高
  // 欄一   欄二 列二高
  // 欄一寬 欄二寬
  grid-template:
      vvv area
    " img    header              " auto
    " img    description         " minmax(100px, 1fr)
    / 100px  minmax(0, 1fr)
  ;
  grid-gap: 20px;

  padding: 20px;
  border: 2px solid grey;
  border-radius: 10px; // 卡片的圓角
  background: white;
  box-shadow: 5px 5px 5px 0 rgba(0, 0, 0, 0.3);
}

img {
  grid-area: img;
  height: 150px;
  width: 100px;
  object-fit: cover; //
  border: 2px solid lightgrey;
}

header {
  grid-area: header;
  font-weight: bold;
  font-size: 125%;
  white-space: nowrap;
  overflow: hidden; // 自動隱藏超出的文字或圖片
  text-overflow: ellipsis; // 限制字數 
  // https://www.astralweb.com.tw/css-ellipsis/
}

.description {
  grid-area: description;
  overflow: hidden;
}

So Much Room for Activities!(時間 1:48:00)

localhost:4200/example-full-viewport
TS

  showNav = true;
  imageItems: Observable<SpaceImage[]>;
  constructor(svc: SpaceImagesService) {
    this.imageItems = svc.load('posters');
  }

HTML

<header>           VVVVVVV 透過 showNav 來切換
  <button (click)="showNav = !showNav" class="material-icons">
    {{ showNav ? 'close' : 'menu' }}
  </button>

  <!--
    The unusual handling of showNav ensures that the nav animations are only
    triggered at the appropriate points.
  -->
  <a routerLink="/" (click)="showNav = showNav ? false : showNav">
    <h1>{{ title }}</h1>
  </a>
</header>

<nav
  (click)="showNav = false"
  [class.showNav]="showNav"
  [class.hideNav]="showNav === false"
>
  <a routerLink="/">
    Home
  </a>
  <a routerLink="/resources">
    Resources
  </a>
  <a routerLink="/about">
    About
  </a>
</nav>

<main>
  <img *ngFor="let imageItem of imageItems | async"
    [src]="imageItem.imageUrl"
  >
</main>

<footer>
  <a target="_blank" href="http://www.billodom.com/">
    Bill
  </a>
  <a target="_blank" href="https://twitter.com/wnodom">
    Twitter
  </a>
  <a target="_blank" href="https://github.com/wnodom">
    Github
  </a>
</footer>

SCSS

:host {
  display: grid;

  grid-gap: 0;

  // 重點1
  grid-template:
                  " header            header "  auto   vvv grid-area
      [nav-start] " main              main   "  1fr   [nav-end] grid lines
                  " footer            footer "  auto   ^^^^^^^ 命名(main的尾?)
    / [nav-start]   225px [nav-end]   1fr
       ^^^^^^^^^ 用中括號 稱呼網格線
  ;

  height: 100vh;

  padding: 0;
}

header {
  grid-area: header;

  display: grid;

  grid-template:
    " navToggle  title " auto
    / auto       minmax(0, 1fr)
  ;

  background: lightgrey;
  padding: 15px;
  box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.33);
  z-index: 1;

  // 切換nav的switch
  button {
    grid-area: navToggle;
    outline: none;
    border: none;
    background-color: transparent;
    text-align: left;
    margin: 0;
    margin-right: 15px;
    padding: 0;
  }

  a {
    grid-area: title;
  }

  h1 {
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

// 重點2
nav {        vvv
  grid-area: nav;

  transform: translateX(-9999px);

  opacity: 0.95;
  background: lightgrey;
  border-right: 3px solid darkgrey;
  padding: 15px;
  overflow: auto;

  & > * {
    display: block;
    margin-bottom: 15px;
  }
}

main {
  grid-area: main;
  display: grid;
  justify-items: center;
  overflow: auto;
  padding: 15px;
  padding-left: 50px;

  img {
    width: 700px;
    margin: 50px;
    display: block;
    box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.33);
  }
}

footer {
  grid-area: footer;
  background: lightgrey;
  padding: 15px;
  box-shadow: 0 -5px 10px 0 rgba(0, 0, 0, 0.33);
  z-index: 1;

  a {
    color: grey;
  }

  // Separate footer links with bullet characters
  a:not(:last-child)::after {
    padding-left: 10px;
    padding-right: 10px;
    content: '\2022';
  }
}

header, footer, nav {
  a {
    text-decoration: none;
    color: black;
  }
}

li {
  margin-top: 10px;
  margin-bottom: 10px;
}

.currentSelection {
  font-weight: bold;
}

vvvvvvvvvv 關鍵影格
@keyframes slideIn {
  from { transform: translateX(-500px); }
  to   { transform: translateX(0); }
}

@keyframes slideOut {
  from { transform: translateX(0); }
  to   { transform: translateX(-500px); }
}

顯示 Nav
.showNav {   vvvvvvv key frame
  animation: slideIn 500ms;
  animation-fill-mode: forwards;
}

不顯示 Nav
.hideNav {
  animation: slideOut 500ms;
  animation-fill-mode: forwards;
}

1:42:00~1:48:58 快速帶過其他的範例

這一集超棒的,建議對css有興趣的朋友好好研究
angular的好處是可以用component來幫我們隔離css的效果
(例如:卡牌相關範例)

前面簡單的grid還看得懂
但後來的幾個大範例就開始看不懂了
看不懂一直po程式碼很不好意思
實際去把完囉,88~~@@~~


上一篇
Day26.5_rxjs-spy,開發 RxJS 的好工具
下一篇
Day3x_完賽心得(可以乎略)
系列文
Angular新手村學習筆記(2019)33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言