У меня есть элементы, которые динамически добавляются, где каждый элемент использует CSS Grid Layout. Вот как выглядит моя сетка
Как видно, первый столбец в первой сетке не совпадает с шириной первого столбца ввторая сетка. Как мне сделать их одинаковыми?
Код на Codepen
HTML
<div id="app">
<div class="list_item" v-for="item in sortedItems" :key="item.feedItemId">
<div class="news_time">{{getFormattedDate(item.pubdate)}}</div>
<div class="news_title">{{item.title}}</div>
<div class="news_link"><span class="news_link_text">{{getHostFromUrl(item.link)}}</span></div>
<div class="news_stats" :class="isStatShown(item) ? '' : 'hide'">
<div class="likes"><i class="fa fa-thumbs-up"></i><span class="stats_text">{{item.likes || 0}}</span></div>
<div class="dislikes"><i class="fa fa-thumbs-down"></i><span class="stats_text">{{item.dislikes || 0}}</span></div>
<div class="bullish"><i class="fa fa-arrow-up"></i><span class="stats_text">{{item.bullish || 0}}</span></div>
<div class="bearish"><i class="fa fa-arrow-down"></i><span class="stats_text">{{item.bearish || 0}}</span></div>
<div class="comments"><i class="fa fa-comment-alt"></i><span class="stats_text">{{item.comments || 0}}</span></div>
</div>
<div class="news_tags">
<div class="news_tag" v-for="tag in item.tags" :key="item.tag">{{tag}}</div>
</div>
</div>
</div>
CSS
@import url('https://fonts.googleapis.com/css?family=Noto+Sans&display=swap');
/**
There are 2 CSS Grids to acheive this layout?
It can be achieved in 1 layout so why 2?
Because stats are shown conditionally.
Meaning if all are 0 we dont show the whole row!
The outer CSS Grid has 3 rows and 4 columns
Row 1
time title title and tag
Row 2
time link link and tag
Row 3
time stats <empty-cell> and tag
The row containing stats is set to auto as it collapses when all stats are 0
*/
* {
padding: 0;
margin: 0;
box-sizing: border-box;
line-height: 1.6;
font-family: 'Noto Sans', sans-serif;
}
.news_time {
grid-area: time;
}
.news_title {
grid-area: title;
}
.news_link {
grid-area: link;
}
.news_stats {
grid-area: stats;
}
.news_tags {
grid-area: tags;
}
.list_item {
display: grid;
grid-template-areas:
"time title title tags"
"time link link tags"
"time stats . tags";
grid-template-rows: 1fr auto auto;
grid-template-columns: auto auto 1fr auto;
align-items: center;
}
.likes {
grid-area: likes;
}
.dislikes {
grid-area: dislikes;
}
.bullish {
grid-area: bullish;
}
.bearish {
grid-area: bearish;
}
.comments {
grid-area: comments;
}
/**
This is our second CSS Grid
If stats are not available, this whole grid will be hidden.
It has only 1 row and 5 columns
Row 1
Likes Dislikes Bullish Bearish and Comments
It adds a gap to separate the adjacent cells
*/
.news_stats {
display: grid;
grid-template-areas:
"likes dislikes bullish bearish comments";
grid-rows: 1fr;
grid-columns: 1fr 1fr 1fr 1fr 1fr;
grid-gap: 0.75rem;
gap: 0.75rem;
}
/**
Spacing
*/
.list_item {
border: 1px solid #eef;
}
.news_stats {
font-size: 0.75rem;
opacity: 0.4;
margin: 0.3rem 0;
}
.news_link {
font-size: 0.85rem;
opacity: 0.6;
}
.stats_text {
display: inline-block;
margin-left: 0.3rem;
}
.news_time {
font-size: 0.75rem;
margin: 1rem;
opacity: 0.75;
}
.news_tags {
margin: 1rem;
font-size: 0.75rem;
opacity: 0.75;
}
/**
Use this class to hide anything that must not be shown
*/
.hide {
display: none;
}
JS
const mapState = Vuex.mapState;
const mapMutations = Vuex.mapMutations;
const mapGetters = Vuex.mapGetters;
const storeObject = {
state() {
return {
items: {
"54069cf1-7a6a-d356-2a7a-3a4d5cf82191": {
feedItemId: "54069cf1-7a6a-d356-2a7a-3a4d5cf82191",
pubdate: new Date("2019-10-1 16:06:40+05:30"),
link:
"https://www.cryptovibes.com/blog/2019/09/30/cardano-technology-will-be-used-by-new-balance-to-combat-counterfeits/",
title:
"Cardano Technology will be Used by New Balance to Combat Counterfeits",
author: "Ali Raza",
feed_id: 98,
likes: 3,
dislikes: 1,
bullish: 6,
bearish: 3,
comments: 5,
tags: ["ADA"]
},
"c68ef754-da41-b4fd-eb3a-150735fc0535": {
feedItemId: "c68ef754-da41-b4fd-eb3a-150735fc0535",
pubdate: new Date("2019-09-30 02:50:43+05:30"),
link:
"https://ethereumworldnews.com/jp-morgan-bitcoin-price-crash-8000/",
title: "Top Airdrops You Should Look Out for in October 2019",
author: "Ogwu Osaemezu Emmanuel",
feed_id: 98,
tags: ["BTC", "ETH"]
},
"a68ef754-da41-b4fd-eb3a-150735fc0535": {
feedItemId: "a68ef754-da41-b4fd-eb3a-150735fc0535",
pubdate: new Date("2019-09-30 02:50:43+05:30"),
link:
"https://ethereumworldnews.com/jp-morgan-bitcoin-price-crash-8000/",
title: "JP Morgan on What Caused Bitcoin Price Crash to $8,000",
author: "Nick Chong",
feed_id: 98,
likes: 0,
dislikes: 8,
bullish: 0,
bearish: 3,
comments: 8,
tags: ["BTC", "ETH", "EOS"]
}
}
};
},
getters: {
sortedItems(state) {
return Object.values(state.items).sort((a, b) => b.pubdate - a.pubdate);
}
}
};
const store = new Vuex.Store(storeObject);
const timeSince = Vue.mixin({
methods: {
timeSince(date) {
var seconds = Math.floor((new Date() - date) / 1000);
var interval = Math.floor(seconds / 31536000);
if (interval > 1) {
return interval + " y";
}
interval = Math.floor(seconds / 2592000);
if (interval > 1) {
return interval + " M";
}
interval = Math.floor(seconds / 86400);
if (interval > 1) {
return interval + " d";
}
interval = Math.floor(seconds / 3600);
if (interval > 1) {
return interval + " h";
}
interval = Math.floor(seconds / 60);
if (interval > 1) {
return interval + " m";
}
return Math.floor(seconds) + " s";
}
}
});
const viewModel = new Vue({
el: "#app",
mixins: [timeSince],
computed: {
...mapState(["items"]),
...mapGetters(["sortedItems"])
},
methods: {
getHostFromUrl(url) {
return new URL(url).host.replace(/^www\./, "");
},
getFormattedDate(date) {
return this.timeSince(date);
},
isStatShown(item) {
return item.likes || item.dislikes || item.bullish || item.bearish || item.comments;
}
},
store
});