Use img.decode() (#473)
* remove will-change:transform from container * WIP: use img.decode() * more work on img.decode
This commit is contained in:
parent
d10f924620
commit
8949b36873
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
14
routes/_store/observers/resizeObservers.js
Normal file
14
routes/_store/observers/resizeObservers.js
Normal 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()
|
||||||
|
}
|
14
routes/_utils/decodeImage.js
Normal file
14
routes/_utils/decodeImage.js
Normal 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
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue