Lazy load images

This commit is contained in:
Eduard Urbach 2017-06-24 16:17:38 +02:00
parent 79ba7ecf3b
commit b6c4968c6c
6 changed files with 65 additions and 23 deletions

View File

@ -1,14 +1,14 @@
component InputText(id string, value string, label string, placeholder string) component InputText(id string, value string, label string, placeholder string)
.widget-input .widget-input
label(for=id)= label + ":" label(for=id)= label + ":"
input.widget-element.action(id=id, type="text", value=value, placeholder=placeholder, data-action="save", data-trigger="change") input.widget-element.action(id=id, type="text", value=value, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")
component InputTextArea(id string, value string, label string, placeholder string) component InputTextArea(id string, value string, label string, placeholder string)
.widget-input .widget-input
label(for=id)= label + ":" label(for=id)= label + ":"
textarea.widget-element.action(id=id, placeholder=placeholder, data-action="save", data-trigger="change")= value textarea.widget-element.action(id=id, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")= value
component InputNumber(id string, value int, label string, placeholder string, min string, max string) component InputNumber(id string, value int, label string, placeholder string, min string, max string)
.widget-input .widget-input
label(for=id)= label + ":" label(for=id)= label + ":"
input.widget-element.action(id=id, type="number", value=value, min=min, max=max, placeholder=placeholder, data-action="save", data-trigger="change") input.widget-element.action(id=id, type="number", value=value, min=min, max=max, placeholder=placeholder, title=placeholder, data-action="save", data-trigger="change")

View File

@ -159,8 +159,10 @@ component Anime(anime *arn.Anime, user *arn.User)
//- if providers.AnimePlanet //- if providers.AnimePlanet
//- a.light-button(href="http://www.anime-planet.com/anime/" + providers.AnimePlanet.providerId, target="_blank") AnimePlanet //- a.light-button(href="http://www.anime-planet.com/anime/" + providers.AnimePlanet.providerId, target="_blank") AnimePlanet
.sources .footer
p Powered by Kitsu. a(href="/api/anime/" + anime.ID) Anime API
span |
span Powered by Kitsu.
//- if descriptionSource //- if descriptionSource
//- span= " Summary by " + summarySource + "." //- span= " Summary by " + summarySource + "."
//- //- //- //-

View File

@ -87,10 +87,11 @@
.anime-rating-categories .anime-rating-categories
vertical vertical
.sources .footer
font-size 0.8rem font-size 0.8rem
opacity 0.5 opacity 0.7
margin-top 0.5rem margin-top 0.5rem
text-align center
.relations .relations
horizontal-wrap horizontal-wrap

View File

@ -84,6 +84,6 @@ component Profile(viewUser *arn.User, user *arn.User, animeList *arn.AnimeList,
.spacer .spacer
.post-likes= len(post.Likes) .post-likes= len(post.Likes)
if user != nil && user.Role == "admin"
.side-note .footer
a(href="/api/user/" + viewUser.ID)= "API: ", viewUser.Nick a(href="/api/user/" + viewUser.ID) User API

View File

@ -1,7 +1,7 @@
component Settings(user *arn.User) component Settings(user *arn.User)
h2.page-title Settings h2.page-title Settings
.widgets(data-api="/api/user/" + user.ID) .widgets
.widget.mountable .widget.mountable(data-api="/api/user/" + user.ID)
h3.widget-title h3.widget-title
Icon("user") Icon("user")
span Personal span Personal
@ -10,11 +10,19 @@ component Settings(user *arn.User)
InputText("Tagline", user.Tagline, "Tagline", "Text that appears below your username") InputText("Tagline", user.Tagline, "Tagline", "Text that appears below your username")
InputText("Website", user.Website, "Website", "Your homepage") InputText("Website", user.Website, "Website", "Your homepage")
.widget.mountable .widget.mountable(data-api="/api/user/" + user.ID)
h3.widget-title h3.widget-title
Icon("cubes") Icon("cubes")
span Accounts span Accounts
InputText("Accounts.AniList.Nick", user.Accounts.AniList.Nick, "AniList", "Your username on anilist.co") InputText("Accounts.AniList.Nick", user.Accounts.AniList.Nick, "AniList", "Your username on anilist.co")
InputText("Accounts.MyAnimeList.Nick", user.Accounts.MyAnimeList.Nick, "MyAnimeList", "Your username on myanimelist.net") InputText("Accounts.MyAnimeList.Nick", user.Accounts.MyAnimeList.Nick, "MyAnimeList", "Your username on myanimelist.net")
InputText("Accounts.Kitsu.Nick", user.Accounts.Kitsu.Nick, "Kitsu", "Your username on kitsu.io") InputText("Accounts.AnimePlanet.Nick", user.Accounts.Kitsu.Nick, "Kitsu", "Your username on kitsu.io")
InputText("Accounts.AnimePlanet.Nick", user.Accounts.AnimePlanet.Nick, "AnimePlanet", "Your username on anime-planet.com")
.widget.mountable(data-api="/api/settings/" + user.ID)
h3.widget-title
Icon("cogs")
span Settings
InputText("TitleLanguage", user.Settings().TitleLanguage, "Title language", "Language of anime titles")

View File

@ -5,9 +5,21 @@ import * as actions from "./actions"
export class AnimeNotifier { export class AnimeNotifier {
app: Application app: Application
visibilityObserver: IntersectionObserver
constructor(app: Application) { constructor(app: Application) {
this.app = app this.app = app
this.visibilityObserver = new IntersectionObserver(
entries => {
for(let entry of entries) {
if(entry.intersectionRatio > 0) {
entry.target["became visible"]()
this.visibilityObserver.unobserve(entry.target)
}
}
},
{}
)
} }
onReadyStateChange() { onReadyStateChange() {
@ -24,6 +36,15 @@ export class AnimeNotifier {
this.app.run() this.app.run()
} }
onContentLoaded() {
this.visibilityObserver.disconnect()
// Update each of these asynchronously
Promise.resolve().then(() => this.updateMountables())
Promise.resolve().then(() => this.updateActions())
Promise.resolve().then(() => this.lazyLoadImages())
}
reloadContent() { reloadContent() {
return fetch("/_" + this.app.currentPath, { return fetch("/_" + this.app.currentPath, {
credentials: "same-origin" credentials: "same-origin"
@ -57,9 +78,24 @@ export class AnimeNotifier {
} }
} }
updateAvatars() { lazyLoadImages() {
for(let element of findAll("user-image")) { for(let element of findAll("user-image")) {
let img = element as HTMLImageElement this.lazyLoadImage(element as HTMLImageElement)
}
for(let element of findAll("anime-cover-image")) {
this.lazyLoadImage(element as HTMLImageElement)
}
}
lazyLoadImage(img: HTMLImageElement) {
// Prevent browser from loading it instantly
img["original source"] = img.src
img.src = ""
// Once the image becomes visible, load it
img["became visible"] = () => {
img.src = img["original source"]
if(img.naturalWidth === 0) { if(img.naturalWidth === 0) {
img.onload = function() { img.onload = function() {
@ -73,6 +109,8 @@ export class AnimeNotifier {
img.classList.add("user-image-found") img.classList.add("user-image-found")
} }
} }
this.visibilityObserver.observe(img)
} }
updateMountables() { updateMountables() {
@ -94,13 +132,6 @@ export class AnimeNotifier {
} }
} }
onContentLoaded() {
// Update each of these asynchronously
Promise.resolve().then(() => this.updateMountables())
Promise.resolve().then(() => this.updateAvatars())
Promise.resolve().then(() => this.updateActions())
}
onPopState(e: PopStateEvent) { onPopState(e: PopStateEvent) {
if(e.state) { if(e.state) {
this.app.load(e.state, { this.app.load(e.state, {