ソースを参照

Article Details page transition and UI

master
kj1352 4年前
コミット
b68f64bcf1
4個のファイルの変更462行の追加40行の削除
  1. +75
    -25
      src/app/home/home.page.html
  2. +317
    -11
      src/app/home/home.page.scss
  3. +53
    -4
      src/app/home/home.page.ts
  4. +17
    -0
      src/global.scss

+ 75
- 25
src/app/home/home.page.html ファイルの表示

@@ -1,26 +1,43 @@
<ion-content>
<h2 class="main-header"> Home </h2>
<figure class="theme-bg-image">
<img src="assets/home-team/KXIP-lion-white.svg">
</figure>

<div class="heading-holder">
<h2 class="main-header"> Home </h2>
<section class="segments">
<button [ngClass]="{'active' : selectedTab === 'news'}"
(click)="selectedTab = 'news'"> News ({{ newsData.length }}) </button>
<button [ngClass]="{'active' : selectedTab === 'videos'}"
(click)="selectedTab = 'videos'"> Videos </button>
</section>
<section class="segments">
<button [ngClass]="{'active' : selectedTab === 'news'}"
(click)="selectedTab = 'news'"> News ({{ newsData.length }}) </button>
<button [ngClass]="{'active' : selectedTab === 'videos'}"
(click)="selectedTab = 'videos'"> Videos </button>
</section>
</div>

<button (click)="closeArticle()"
class="close-article-button"
[ngClass]="{'active' : selectedArticle !== null }">
<ion-icon name="close"></ion-icon>
</button>

<ion-slides [options]="slideOpts">
<ion-slide *ngFor="let news of newsData">
<ion-slides [options]="slideOpts" *ngIf="selectedTab === 'news'"
[ngClass]="{'active' : selectedArticle !== null}">

<ion-slide *ngFor="let news of newsData; let i = index">
<div class="image-holder">
<figure>
<img [src]="news.image">
</figure>

<button *ngIf="news.type === 'VIDEO'">
<button
[ngClass]="{'active' : selectedArticle !== null}"
*ngIf="news.type === 'VIDEO'">
<ion-icon name="play"></ion-icon>
</button>

<button *ngIf="news.type === 'ARTICLE'">
<button
[ngClass]="{'hide' : selectedArticle !== null}"
*ngIf="news.type === 'ARTICLE'">
<ion-icon name="newspaper"></ion-icon>
</button>
</div>
@@ -28,30 +45,63 @@
<section class="content">
<h4> {{ news.heading }} </h4>

<p>
<div class="details">
{{ news.description }}
</p>
</div>
</section>


<section class="comments" *ngIf="selectedArticle !== null">
<header> Comments </header>
<ul>
<li *ngFor="let comment of news.comments">
<p> {{ comment.comment }} <label> - {{ comment.user }} </label> </p>

<button (click)="comment.isLiked = !comment.isLiked"
[ngClass]="{'active' : comment.isLiked}">
<ion-icon *ngIf="!comment.isLiked" name="heart-outline"></ion-icon>
<ion-icon *ngIf="comment.isLiked" name="heart"></ion-icon>
<span> {{ comment.isLiked ? comment.likes + 1 : comment.likes }} </span>
</button>
</li>
<div class="input-holder" id="comment-input">
<input type="text" placeholder="Type your comment" [(ngModel)]="myComment">
<button (click)="postComment()"> <ion-icon name="send"></ion-icon> </button>
</div>
</ul>
</section>

<section class="action-buttons">
<section class="shortcuts">
<button>
<ion-icon name="share-social-outline"></ion-icon>
</button>

<button (click)="news.isLiked = !news.isLiked"
<button class="wide-button" (click)="news.isLiked = !news.isLiked"
[ngClass]="{'active' : news.isLiked}">
<ion-icon *ngIf="!news.isLiked" name="heart-outline"></ion-icon>
<ion-icon *ngIf="news.isLiked" name="heart"></ion-icon>
<span> {{ news.isLiked ? news.likes + 1 : news.likes }} </span>
</button>
</button>

<button>
<ion-icon name="share-social-outline"></ion-icon>
</button>
</section>

<button class="read-more">
<span *ngIf="news.type === 'ARTICLE'"> Read More </span>
<span *ngIf="news.type === 'VIDEO'"> Watch </span>
<ion-icon name="chevron-forward-outline"></ion-icon> </button>
<section class="shortcuts" *ngIf="selectedArticle !== null">
<button (click)="news.isBookmarked = !news.isBookmarked"
[ngClass]="{'active' : news.isBookmarked}">
<ion-icon *ngIf="!news.isBookmarked" name="bookmark-outline"></ion-icon>
<ion-icon *ngIf="news.isBookmarked" name="bookmark"></ion-icon>
</button>

<button class="wide-button" (click)="scrollToAddComment()">
<ion-icon name="chatbubble-outline"></ion-icon>
<span> {{ news.comments.length }} </span>
</button>
</section>

<button class="read-more" (click)="expandArticle(i)">
<span> More </span>
<ion-icon name="chevron-forward-outline"></ion-icon>
</button>
</section>

</ion-slide>


+ 317
- 11
src/app/home/home.page.scss ファイルの表示

@@ -1,5 +1,12 @@
ion-content {
--background: var(--brand-black);
--background: var(--brand-black);
}

.heading-holder {
position: fixed;
top: 0;
left: 0;
width: 100%;
}

.main-header {
@@ -48,9 +55,126 @@ ion-content {
}
}

.close-article-button {
width: 40px;
height: 40px;
position: fixed;
top: 10px;
right: 10px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(#ffff, 0.5);
border-radius: 50%;
z-index: 3;
opacity: 0;
transform: translateY(10px);
pointer-events: none;
transition: opacity 0.3s, transform 0.3s;

&.active {
opacity: 1;
transition-delay: 1s;
transform: translateY(0px);
pointer-events: all;
}

ion-icon {
color: white;
font-size: 20px;
}
}

ion-slides {
margin: 12% 0%;
margin-top: 40%;
height: calc(100vh - 40%);
position: relative;
left: 0;
top: 0;
transition: transform 0.5s, height 0.3s, margin 0.3s, width 0.3s;
z-index: 1;

&.active {
margin: 0px;
width: 100%;
transform: translate(0, 0%);
height: calc(100vh - 56px);

ion-slide {
background-color: white;
border-radius: 0px;
overflow: auto;

.content {
width: 90%;
height: auto;
background-color: white;
transform: translateY(-30px);
position: relative;
border-radius: 10px;
transition-delay: 0.3s;
overflow: auto;
border-width: 0px;
box-shadow: 0px 0px 15px rgba(var(--light-grey-rgb), 0.3);

h4, .details {
opacity: 0;
animation: showUpContent 0.3s forwards;
animation-delay: 0.3s;
}
}

.action-buttons {
width: 90%;
background-color: white;
border-radius: 10px;
position: sticky;
position: -webkit-sticky;
bottom: 0;
z-index: 2;
box-shadow: 0px 0px 15px rgba(var(--light-grey-rgb), 0.3);
justify-content: center;
height: 60px;

.shortcuts {
width: 50%;

button {
background-color: rgba(var(--light-grey-rgb), 0.1);
margin: 0 auto;
width: 40px;
height: 40px;
&.wide-button {
width: auto;
padding: 0 10px;
}
}
}

.read-more {
display: none;
}
}
.image-holder figure {
filter: brightness(30%);
}
}
}
@keyframes showUpContent {
0% {
opacity: 0;
transform: translateY(20px);
-webkit-line-clamp: 4;
}
100% {
opacity: 1;
-webkit-line-clamp: unset;
transform: translateY(0px);
}
}

ion-slide {
display: block;
@@ -59,10 +183,13 @@ ion-slides {
text-align: left;
align-self: start;
height: 100%;
transition: width 0.5s, transform 0.3s, margin 0.3s;
}

.image-holder {
position: relative;
position: sticky;
position: -webkit-sticky;
top: 0;
height: 40%;
overflow: hidden;

@@ -77,10 +204,41 @@ ion-slides {
justify-content: center;
background-color: rgba(#ffff, 0.5);
border-radius: 50%;
transition: transform 0.3s, background-color 0.3s, opacity 0.3s;

&.hide {
opacity: 0;
pointer-events: none;
}

&.active {
transform: translate(calc(-50vw + 40px), -15vh);
animation: ripple 1s infinite;
background-color: rgba(var(--brand-red-rgb), 0.5);

ion-icon {
color: white;
}
}

@keyframes ripple {
0% {
box-shadow: 0px 0px 0px var(--brand-red);
}
50% {
box-shadow: 0px 0px 5px var(--brand-red);
}

100% {
box-shadow: 0px 0px 0px var(--brand-red);
}
}

ion-icon {
color: white;
font-size: 20px;
transition: color 0.3s;
}
}

@@ -90,6 +248,7 @@ ion-slides {
width: 100%;
height: 100%;
filter: brightness(60%);
transition: filter 0.3s;

img {
height: 100%;
@@ -102,11 +261,18 @@ ion-slides {

.content {
background-color: white;
padding: 5%;
padding: 0px 5% 5% 5%;
width: 100%;
margin: 0 auto;
height: calc(60% - 50px);
overflow: hidden;
margin-top: -1px;
border-bottom: 1px solid rgba(var(--light-grey-rgb), 0.8);
border-radius: 0px;
transform: translateY(0px);
box-shadow: 0px 0px 0px var(--light-grey);
transition: border-radius 0.3s, transform 0.3s, width 0.3s, margin 0.3s, box-shadow 0.3s;
border-bottom: 1px solid rgba(var(--light-grey-rgb), 0.3);
z-index: 2;
}

h4 {
@@ -114,11 +280,18 @@ ion-slides {
margin: 0px;
line-height: 1.5;
font-weight: 500;
color: var(--brand-black);
color: var(--brand-black);
transition: opacity 0.1s;
background-color: white;
position: sticky;
position: -webkit-sticky;
top: 1px;
z-index: 1;
padding: 10px 0;
}
p {
margin: 5px 0px 0px 0px;
.details {
margin: 0px;
font-size: 0.9rem;
line-height: 1.5;
font-weight: 400;
@@ -127,6 +300,7 @@ ion-slides {
-webkit-box-orient: vertical;
color: var(--light-grey);
overflow: hidden;
transition: opacity 0.1s;
}

.action-buttons {
@@ -136,16 +310,31 @@ ion-slides {
width: 100%;
height: 50px;
margin-top: -1px;
padding: 0 3%;
padding: 0 3%;
position: relative;
box-shadow: 0px 0px 0px var(--light-grey);
transition: border-radius 0.3s, transform 0.3s, width 0.3s, margin 0.3s, box-shadow 0.3s;
z-index: 1;
margin: 0 auto 20px;

.shortcuts {
display: flex;
align-items: stretch;
align-items: center;
height: 100%;

button {
background-color: transparent;
background-color: #f3f3f3;
margin-right: 5px;
width: 30px;
height: 30px;
border-radius: 50%;

&.wide-button {
width: auto;
border-radius: 5px;
padding: 0 10px;
}

&.active {
ion-icon, span {
@@ -153,6 +342,14 @@ ion-slides {
}
}

// &.bookmark-button {
// &.active {
// ion-icon, span {
// color: var(--light-grey);
// }
// }
// }

span {
vertical-align: middle;
color: var(--light-grey);
@@ -181,4 +378,113 @@ ion-slides {
}
}

.comments {
background-color: white;
width: 90%;
position: relative;
box-shadow: 0px 0px 0px var(--light-grey);
z-index: 1;
margin: 0px auto 20px;
border-radius: 10px;
box-shadow: 0px 0px 15px rgba(var(--light-grey-rgb), 0.3);
border-radius: 10px;
overflow: hidden;
header {
font-size: 1.1rem;
margin: 0px;
line-height: 1.5;
font-weight: 500;
color: var(--brand-black);
transition: opacity 0.1s;
background-color: white;
padding: 10px 5%;
}

ul {
list-style: none;
padding: 0;
margin: 0;

li {
padding: 5px 5% 10px;
border-top: 1px solid rgba(var(--light-grey-rgb), 0.3);
line-height: 1.5;
}

label {
font-size: 1rem;
font-weight: 500;
letter-spacing: 1px;
color: rgba(var(--brand-red-rgb), 0.8);
}

p {
margin: 3px 0;
font-size: 1rem;
font-weight: 400;
color: var(--light-grey);
}

button {
background-color: #f3f3f3;
margin-right: 5px;
height: 30px;
width: auto;
border-radius: 5px;
padding: 0 5px;

span {
vertical-align: middle;
color: var(--light-grey);
}
ion-icon {
vertical-align: middle;
color: var(--brand-black);
font-size: 1rem;
}

&.active {
ion-icon, span {
color: var(--brand-red);
}
}
}

.input-holder {
display: flex;
width: 100%;
height: 70px;
align-items: center;

input {
width: calc(100% - 80px);
margin: 0 auto;
border: 1px solid rgba(var(--light-grey-rgb), 0.3);
border-radius: 30px;
font-size: 14px;
color: var(--brand-black);
font-weight: 500;
height: 40px;
padding: 0 15px;
}

button {
width: 40px;
height: 40px;
background-color: var(--brand-red);
margin: 0 auto;
border-radius: 50%;

ion-icon {
color: white;
font-size: 16px;
}
}
}
}
}

}

+ 53
- 4
src/app/home/home.page.ts ファイルの表示

@@ -7,6 +7,8 @@ import { Component, OnInit } from '@angular/core';
})
export class HomePage implements OnInit {
selectedTab: string = 'news';
selectedArticle: number = null;
myComment: string = '';

slideOpts = { };

@@ -22,7 +24,8 @@ export class HomePage implements OnInit {
comments: Array<{
user: string,
comment: string,
likes: number
likes: number,
isLiked: boolean
}>,
}> = [];

@@ -34,7 +37,14 @@ export class HomePage implements OnInit {
id: 1,
image: 'https://s3.india.com/wp-content/uploads/2020/10/Mayank-Agarwal-celebrates-Kings-XI-Punjabs-win-over-Mumbai-Indians-in-match-37-of-Dream11-IPL-2020-in-Dubai%C2%A9KXIP-Twitter.jpg',
heading: 'KXIP beat MI by 3 Wickets',
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iste ab qui, incidunt illo dolore laboriosam sapiente deserunt officiis ullam. Explicabo accusantium quia tempore totam repellat amet debitis adipisci deserunt iste.',
description: `Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iste ab qui, incidunt illo dolore laboriosam sapiente deserunt officiis ullam.
Explicabo accusantium quia tempore totam repellat amet debitis adipisci deserunt iste.

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iste ab qui, incidunt illo dolore laboriosam sapiente deserunt officiis u
santium quia tempore totam repellat amet debitis adipisci deserunt iste.
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iste ab qui, incidunt illo dolore laboriosam sapiente deserunt officiis u
santium quia tempore totam repellat amet debitis adipisci deserunt iste.`,
type: 'VIDEO',
likes: 10,
isLiked: false,
@@ -43,16 +53,18 @@ export class HomePage implements OnInit {
user: 'kxipFan',
comment: 'Yay!',
likes: 2,
isLiked: true,
}, {
user: 'SehwagFan',
comment: 'finally!',
likes: 5,
isLiked: false,
}]
}, {
id: 2,
image: 'https://www.ak4tsay1.com/wp-content/uploads/2020/02/Kings-XI-Punjab-KXIP-Strengths-and-Weakness-for-IPL-2020-800x445.jpg',
heading: 'KL Rahul scores fastest 100',
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iste ab qui, incidunt illo dolore laboriosam sapiente deserunt officiis ullam. Explicabo accusantium quia tempore totam repellat amet debitis adipisci deserunt iste.',
description: `Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iste ab qui, incidunt illo dolore laboriosam sapiente deserunt officiis ullam. Explicabo accusantium quia tempore totam repellat amet debitis adipisci deserunt iste.`,
type: 'ARTICLE',
isLiked: false,
isBookmarked: false,
@@ -65,8 +77,45 @@ export class HomePage implements OnInit {
spaceBetween: 30,
slidesOffsetBefore: 30,
slidesOffsetAfter: 30,
}
};

}

expandArticle(index: number) {
this.selectedArticle = index;

this.slideOpts = {
slidesPerView: 1,
spaceBetween: 0,
slidesOffsetBefore: 0,
slidesOffsetAfter: 0,
};
}

closeArticle() {
this.selectedArticle = null;
this.slideOpts = {
slidesPerView: 1.3,
spaceBetween: 30,
slidesOffsetBefore: 30,
slidesOffsetAfter: 30,
};
}

postComment() {
this.newsData[this.selectedArticle].comments.push({
user: 'Test',
comment: this.myComment,
isLiked: false,
likes: 0,
});

this.myComment = '';
}

scrollToAddComment() {
document.querySelector('#comment-input').scrollIntoView({behavior: "smooth", block: "start"});
}

}

+ 17
- 0
src/global.scss ファイルの表示

@@ -32,4 +32,21 @@
outline: none;
text-decoration: none;
letter-spacing: 0.5px;
}

.theme-bg-image {
position: fixed;
opacity: 0.1;
width: 100%;
height: 100%;
display: flex;
align-items: flex-start;
justify-content: center;
left: 0;
top: 0;

img {
display: block;
width: 80%;
}
}

読み込み中…
キャンセル
保存