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}
<svg class={computedClass} aria-hidden="true">
<svg class={computedClass} style={svgStyle} aria-hidden="true">
<use xlink:href="#fa-user" />
</svg>
{:elseif $autoplayGifs}
<img
class={computedClass}
aria-hidden="true"
<LazyImage
className={computedClass}
ariaHidden="true"
alt=""
src={account.avatar}
{width}
{height}
on:imgLoad="set({loaded: true})"
on:imgLoadError="set({error: true})" />
{:else}
@ -17,6 +19,8 @@
alt=""
src={account.avatar}
staticSrc={account.avatar_static}
{width}
{height}
{isLink}
on:imgLoad="set({loaded: true})"
on:imgLoadError="set({error: true})"
@ -32,48 +36,17 @@
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 {
fill: var(--deemphasized-text-color);
}
</style>
<script>
import { imgLoad, imgLoadError } from '../_utils/events'
import { store } from '../_store/store'
import NonAutoplayImg from './NonAutoplayImg.html'
import { classname } from '../_utils/classname'
import LazyImage from './LazyImage.html'
export default {
events: {
imgLoad,
imgLoadError
},
data: () => ({
className: void 0,
loaded: false,
@ -82,15 +55,30 @@
}),
store: () => store,
computed: {
computedClass: ({ className, loaded, size }) => (classname(
computedClass: ({ className, loaded }) => (classname(
'avatar',
className,
loaded && 'loaded',
`size-${size}`
))
loaded && 'loaded'
)),
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: {
NonAutoplayImg
NonAutoplayImg,
LazyImage
}
}
</script>

View file

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

View file

@ -1,22 +1,13 @@
{#if staticSrc === src}
<img class={className || ''}
aria-hidden={ariaHidden}
alt={alt || ''}
title={alt || ''}
{src}
on:imgLoad
on:imgLoadError />
{: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)"
ref:node />
{/if}
<img
class={computedClass}
aria-hidden={ariaHidden}
{alt}
{title}
{width}
{height}
src={displaySrc}
on:mouseover="onMouseOver(event)"
/>
<style>
.non-autoplay-zoom-in {
cursor: zoom-in;
@ -26,18 +17,44 @@
}
</style>
<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 {
async oncreate () {
let { staticSrc } = this.get()
try {
await decodeImage(staticSrc)
this.set({ displaySrc: staticSrc })
this.fire('imgLoad')
} catch (e) {
this.fire('imgLoadError', e)
}
},
methods: {
onMouseOver (mouseOver) {
let { src, staticSrc } = this.get()
this.refs.node.src = mouseOver ? src : staticSrc
let { src, staticSrc, displaySrc } = this.get()
if (displaySrc !== ONE_TRANSPARENT_PIXEL) {
this.set({ displaySrc: mouseOver ? src : staticSrc })
}
}
},
events: {
imgLoad,
imgLoadError,
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>

View file

@ -4,7 +4,9 @@
{#if verifyCredentials}
<h2>Logged in as:</h2>
<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"
href={verifyCredentials.url}>
{'@' + verifyCredentials.acct}

View file

@ -5,6 +5,7 @@ import { onlineObservers } from './onlineObservers'
import { navObservers } from './navObservers'
import { autosuggestObservers } from './autosuggestObservers'
import { pageVisibilityObservers } from './pageVisibilityObservers'
import { resizeObservers } from './resizeObservers'
export function observers (store) {
instanceObservers(store)
@ -14,4 +15,5 @@ export function observers (store) {
navObservers(store)
autosuggestObservers(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) {
function onMouseEnter () {
callback(true) // eslint-disable-line