Use img.decode() (#473)

* remove will-change:transform from container

* WIP: use img.decode()

* more work on img.decode
This commit is contained in:
Nolan Lawson 2018-08-22 21:00:53 -07:00 committed by GitHub
parent d10f924620
commit 8949b36873
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 116 deletions

View file

@ -1,13 +1,15 @@
{#if error} {#if error}
<svg class={computedClass} aria-hidden="true"> <svg class={computedClass} style={svgStyle} aria-hidden="true">
<use xlink:href="#fa-user" /> <use xlink:href="#fa-user" />
</svg> </svg>
{:elseif $autoplayGifs} {:elseif $autoplayGifs}
<img <LazyImage
class={computedClass} className={computedClass}
aria-hidden="true" ariaHidden="true"
alt="" alt=""
src={account.avatar} src={account.avatar}
{width}
{height}
on:imgLoad="set({loaded: true})" on:imgLoad="set({loaded: true})"
on:imgLoadError="set({error: true})" /> on:imgLoadError="set({error: true})" />
{:else} {:else}
@ -17,6 +19,8 @@
alt="" alt=""
src={account.avatar} src={account.avatar}
staticSrc={account.avatar_static} staticSrc={account.avatar_static}
{width}
{height}
{isLink} {isLink}
on:imgLoad="set({loaded: true})" on:imgLoad="set({loaded: true})"
on:imgLoadError="set({error: true})" on:imgLoadError="set({error: true})"
@ -32,48 +36,17 @@
background: none; background: none;
} }
:global(.avatar.size-extra-small) {
width: 24px;
height: 24px;
}
:global(.avatar.size-small) {
width: 48px;
height: 48px;
}
:global(.avatar.size-medium) {
width: 64px;
height: 64px;
}
:global(.avatar.size-big) {
width: 100px;
height: 100px;
}
@media (max-width: 767px) {
:global(.avatar.size-big) {
width: 80px;
height: 80px;
}
}
svg.avatar { svg.avatar {
fill: var(--deemphasized-text-color); fill: var(--deemphasized-text-color);
} }
</style> </style>
<script> <script>
import { imgLoad, imgLoadError } from '../_utils/events'
import { store } from '../_store/store' import { store } from '../_store/store'
import NonAutoplayImg from './NonAutoplayImg.html' import NonAutoplayImg from './NonAutoplayImg.html'
import { classname } from '../_utils/classname' import { classname } from '../_utils/classname'
import LazyImage from './LazyImage.html'
export default { export default {
events: {
imgLoad,
imgLoadError
},
data: () => ({ data: () => ({
className: void 0, className: void 0,
loaded: false, loaded: false,
@ -82,15 +55,30 @@
}), }),
store: () => store, store: () => store,
computed: { computed: {
computedClass: ({ className, loaded, size }) => (classname( computedClass: ({ className, loaded }) => (classname(
'avatar', 'avatar',
className, className,
loaded && 'loaded', loaded && 'loaded'
`size-${size}` )),
)) width: ({ size, $isMobileSize }) => {
switch (size) {
case 'extra-small':
return 24
case 'small':
return 48
case 'big':
return $isMobileSize ? 80 : 100
case 'medium':
default:
return 64
}
},
height: ({ width }) => width,
svgStyle: ({ width, height }) => `width: ${width}px; height: ${height}px;`
}, },
components: { components: {
NonAutoplayImg NonAutoplayImg,
LazyImage
} }
} }
</script> </script>

View file

@ -1,14 +1,13 @@
<div class="lazy-image" <div class="lazy-image" style={computedStyle} >
style="width: {width}px; height: {height}px; background: {background};">
{#if displaySrc} {#if displaySrc}
<img <img
class="{hidden ? 'hidden' : ''} {className || ''}" class={className}
aria-hidden={ariaHidden || ''} aria-hidden={ariaHidden}
alt={alt || ''} {alt}
title={alt || ''} {title}
src={displaySrc}
{width} {width}
{height} {height}
src={displaySrc}
/> />
{/if} {/if}
</div> </div>
@ -16,40 +15,45 @@
.lazy-image { .lazy-image {
overflow: hidden; overflow: hidden;
} }
.lazy-image img {
transition: opacity 0.2s linear;
}
</style> </style>
<script> <script>
import { mark, stop } from '../_utils/marks' import { mark, stop } from '../_utils/marks'
import { decodeImage } from '../_utils/decodeImage'
export default { export default {
oncreate () { async oncreate () {
mark('LazyImage oncreate()') mark('LazyImage oncreate()')
let img = new Image() let { src, fallback } = this.get()
let { src } = this.get() try {
let { fallback } = this.get() await decodeImage(src)
img.onload = () => { this.set({ displaySrc: src })
requestAnimationFrame(() => { } catch (e) {
this.set({ if (fallback) {
displaySrc: src,
hidden: true
})
requestAnimationFrame(() => {
this.set({hidden: false})
})
})
}
img.onerror = () => {
this.set({displaySrc: fallback}) this.set({displaySrc: fallback})
} }
img.src = src }
stop('LazyImage oncreate()') stop('LazyImage oncreate()')
}, },
data: () => ({ data: () => ({
displaySrc: void 0, displaySrc: void 0,
hidden: false, hidden: false,
ariaHidden: false fallback: void 0,
}) background: '',
width: void 0,
height: void 0,
className: '',
ariaHidden: '',
alt: '',
title: ''
}),
computed: {
computedStyle: ({width, height, background}) => {
return [
width && `width: ${width}px;`,
height && `height: ${height}px;`,
background && `background: ${background};`
].filter(Boolean).join('')
}
}
} }
</script> </script>

View file

@ -1,22 +1,13 @@
{#if staticSrc === src} <img
<img class={className || ''} class={computedClass}
aria-hidden={ariaHidden} aria-hidden={ariaHidden}
alt={alt || ''} {alt}
title={alt || ''} {title}
{src} {width}
on:imgLoad {height}
on:imgLoadError /> src={displaySrc}
{:else}
<img class="{className || ''} non-autoplay-zoom-in {isLink ? 'is-link' : ''}"
aria-hidden={ariaHidden}
alt={alt || ''}
title={alt || ''}
src={staticSrc}
on:imgLoad
on:imgLoadError
on:mouseover="onMouseOver(event)" on:mouseover="onMouseOver(event)"
ref:node /> />
{/if}
<style> <style>
.non-autoplay-zoom-in { .non-autoplay-zoom-in {
cursor: zoom-in; cursor: zoom-in;
@ -26,18 +17,44 @@
} }
</style> </style>
<script> <script>
import { imgLoad, imgLoadError, mouseover } from '../_utils/events' import { mouseover } from '../_utils/events'
import { decodeImage } from '../_utils/decodeImage'
import { ONE_TRANSPARENT_PIXEL } from '../_static/media'
import { classname } from '../_utils/classname'
export default { export default {
async oncreate () {
let { staticSrc } = this.get()
try {
await decodeImage(staticSrc)
this.set({ displaySrc: staticSrc })
this.fire('imgLoad')
} catch (e) {
this.fire('imgLoadError', e)
}
},
methods: { methods: {
onMouseOver (mouseOver) { onMouseOver (mouseOver) {
let { src, staticSrc } = this.get() let { src, staticSrc, displaySrc } = this.get()
this.refs.node.src = mouseOver ? src : staticSrc if (displaySrc !== ONE_TRANSPARENT_PIXEL) {
this.set({ displaySrc: mouseOver ? src : staticSrc })
}
} }
}, },
events: { events: {
imgLoad,
imgLoadError,
mouseover mouseover
},
data: () => ({
displaySrc: ONE_TRANSPARENT_PIXEL,
alt: '',
title: ''
}),
computed: {
computedClass: ({className, src, staticSrc, isLink}) => (classname(
className,
src !== staticSrc && 'non-autoplay-zoom-in',
isLink && 'is-link'
))
} }
} }
</script> </script>

View file

@ -4,7 +4,9 @@
{#if verifyCredentials} {#if verifyCredentials}
<h2>Logged in as:</h2> <h2>Logged in as:</h2>
<div class="acct-current-user"> <div class="acct-current-user">
<Avatar account={verifyCredentials} className="acct-avatar" size="big"/> <div class="acct-avatar">
<Avatar account={verifyCredentials} size="big"/>
</div>
<ExternalLink className="acct-handle" <ExternalLink className="acct-handle"
href={verifyCredentials.url}> href={verifyCredentials.url}>
{'@' + verifyCredentials.acct} {'@' + verifyCredentials.acct}

View file

@ -5,6 +5,7 @@ import { onlineObservers } from './onlineObservers'
import { navObservers } from './navObservers' import { navObservers } from './navObservers'
import { autosuggestObservers } from './autosuggestObservers' import { autosuggestObservers } from './autosuggestObservers'
import { pageVisibilityObservers } from './pageVisibilityObservers' import { pageVisibilityObservers } from './pageVisibilityObservers'
import { resizeObservers } from './resizeObservers'
export function observers (store) { export function observers (store) {
instanceObservers(store) instanceObservers(store)
@ -14,4 +15,5 @@ export function observers (store) {
navObservers(store) navObservers(store)
autosuggestObservers(store) autosuggestObservers(store)
pageVisibilityObservers(store) pageVisibilityObservers(store)
resizeObservers(store)
} }

View file

@ -0,0 +1,14 @@
import { registerResizeListener } from '../../_utils/resize'
export function resizeObservers (store) {
if (!process.browser) {
return
}
const recalculateIsMobileSize = () => {
store.set({isMobileSize: window.matchMedia('(max-width: 767px)').matches})
}
registerResizeListener(recalculateIsMobileSize)
recalculateIsMobileSize()
}

View file

@ -0,0 +1,14 @@
export function decodeImage (src) {
if (typeof Image.prototype.decode === 'function') {
let img = new Image()
img.src = src
return img.decode()
}
return new Promise((resolve, reject) => {
let img = new Image()
img.src = src
img.onload = resolve
img.onerror = reject
})
}

View file

@ -1,23 +1,3 @@
export function imgLoadError (node, callback) {
node.addEventListener('error', callback)
return {
destroy () {
node.removeEventListener('error', callback)
}
}
}
export function imgLoad (node, callback) {
node.addEventListener('load', callback)
return {
destroy () {
node.removeEventListener('load', callback)
}
}
}
export function mouseover (node, callback) { export function mouseover (node, callback) {
function onMouseEnter () { function onMouseEnter () {
callback(true) // eslint-disable-line callback(true) // eslint-disable-line