mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
feat(img): adds lazy load image
This commit is contained in:
48
core/src/components.d.ts
vendored
48
core/src/components.d.ts
vendored
@ -2151,6 +2151,54 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
|
||||||
|
namespace StencilComponents {
|
||||||
|
interface IonImg {
|
||||||
|
/**
|
||||||
|
* This attribute defines the alternative text describing the image. Users will see this text displayed if the image URL is wrong, the image is not in one of the supported formats, or if the image is not yet downloaded.
|
||||||
|
*/
|
||||||
|
'alt': string;
|
||||||
|
/**
|
||||||
|
* The image URL. This attribute is mandatory for the <img> element.
|
||||||
|
*/
|
||||||
|
'src': string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HTMLIonImgElement extends StencilComponents.IonImg, HTMLStencilElement {}
|
||||||
|
|
||||||
|
var HTMLIonImgElement: {
|
||||||
|
prototype: HTMLIonImgElement;
|
||||||
|
new (): HTMLIonImgElement;
|
||||||
|
};
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'ion-img': HTMLIonImgElement;
|
||||||
|
}
|
||||||
|
interface ElementTagNameMap {
|
||||||
|
'ion-img': HTMLIonImgElement;
|
||||||
|
}
|
||||||
|
namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
'ion-img': JSXElements.IonImgAttributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace JSXElements {
|
||||||
|
export interface IonImgAttributes extends HTMLAttributes {
|
||||||
|
/**
|
||||||
|
* This attribute defines the alternative text describing the image. Users will see this text displayed if the image URL is wrong, the image is not in one of the supported formats, or if the image is not yet downloaded.
|
||||||
|
*/
|
||||||
|
'alt'?: string;
|
||||||
|
'onIonImgDidLoad'?: (event: CustomEvent<void>) => void;
|
||||||
|
/**
|
||||||
|
* The image URL. This attribute is mandatory for the <img> element.
|
||||||
|
*/
|
||||||
|
'src'?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
||||||
namespace StencilComponents {
|
namespace StencilComponents {
|
||||||
|
12
core/src/components/img/img.scss
Normal file
12
core/src/components/img/img.scss
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
ion-img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-img > img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
object-fit: inherit;
|
||||||
|
object-position: inherit;
|
||||||
|
}
|
77
core/src/components/img/img.tsx
Normal file
77
core/src/components/img/img.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { Component, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
tag: 'ion-img',
|
||||||
|
styleUrl: 'img.scss'
|
||||||
|
})
|
||||||
|
export class Img {
|
||||||
|
|
||||||
|
private io?: IntersectionObserver;
|
||||||
|
|
||||||
|
@Element() el!: HTMLElement;
|
||||||
|
|
||||||
|
@State() loadSrc?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This attribute defines the alternative text describing the image.
|
||||||
|
* Users will see this text displayed if the image URL is wrong,
|
||||||
|
* the image is not in one of the supported formats, or if the image is not yet downloaded.
|
||||||
|
*/
|
||||||
|
@Prop() alt?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The image URL. This attribute is mandatory for the <img> element.
|
||||||
|
*/
|
||||||
|
@Prop() src?: string;
|
||||||
|
@Watch('src')
|
||||||
|
srcChanged() {
|
||||||
|
this.addIO();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event() ionImgDidLoad!: EventEmitter<void>;
|
||||||
|
|
||||||
|
componentDidLoad() {
|
||||||
|
this.addIO();
|
||||||
|
}
|
||||||
|
|
||||||
|
private addIO() {
|
||||||
|
if (!this.src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
this.removeIO();
|
||||||
|
this.io = new IntersectionObserver((data) => {
|
||||||
|
// because there will only ever be one instance
|
||||||
|
// of the element we are observing
|
||||||
|
// we can just use data[0]
|
||||||
|
if (data[0].isIntersecting) {
|
||||||
|
this.loadSrc = this.src;
|
||||||
|
this.removeIO();
|
||||||
|
this.ionImgDidLoad.emit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.io.observe(this.el);
|
||||||
|
} else {
|
||||||
|
// fall back to setTimeout for Safari and IE
|
||||||
|
setTimeout(() => this.loadSrc = this.src, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeIO() {
|
||||||
|
if (this.io) {
|
||||||
|
this.io.disconnect();
|
||||||
|
this.io = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={this.loadSrc}
|
||||||
|
alt={this.alt}
|
||||||
|
decoding="async"></img>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
145
core/src/components/img/readme.md
Normal file
145
core/src/components/img/readme.md
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# ion-fab
|
||||||
|
|
||||||
|
Fabs are container elements that contain one or more fab buttons. They should be placed in a fixed position that does not scroll with the content. The following attributes can be used to position the fab with respect to the content:
|
||||||
|
|
||||||
|
| Value | Alignment | Details |
|
||||||
|
|--------------|------------|---------------------------------------------------------------------------|
|
||||||
|
| `top` | vertical | Places the container at the top of the content. |
|
||||||
|
| `bottom` | vertical | Places the container at the bottom of the content. |
|
||||||
|
| `middle` | vertical | Places the container in the middle vertically. |
|
||||||
|
| `edge` | vertical | Used to place the container between the content and the header/footer. |
|
||||||
|
| `left` | horizontal | Places the container on the left. |
|
||||||
|
| `right` | horizontal | Places the container on the right. |
|
||||||
|
| `center` | horizontal | Places the container in the center horizontally. |
|
||||||
|
|
||||||
|
The fab should have one main fab button. Fabs can also contain fab lists which contain related buttons that show when the main fab button is clicked. The same fab container can contain several [fab list](../../fab-list/FabList) elements with different side values.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ion-content>
|
||||||
|
<!-- fab placed to the top right -->
|
||||||
|
<ion-fab top right slot="fixed">
|
||||||
|
<ion-fab-button>
|
||||||
|
<ion-icon name="add"></ion-icon>
|
||||||
|
</ion-fab-button>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
|
<!-- fab placed to the bottom right -->
|
||||||
|
<ion-fab bottom right slot="fixed">
|
||||||
|
<ion-fab-button>
|
||||||
|
<ion-icon name="arrow-dropleft"></ion-icon>
|
||||||
|
</ion-fab-button>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
|
<!-- fab placed to the top left -->
|
||||||
|
<ion-fab top left slot="fixed">
|
||||||
|
<ion-fab-button>
|
||||||
|
<ion-icon name="arrow-dropright"></ion-icon>
|
||||||
|
</ion-fab-button>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
|
<!-- fab placed to the bottom left -->
|
||||||
|
<ion-fab bottom left slot="fixed">
|
||||||
|
<ion-fab-button>
|
||||||
|
<ion-icon name="arrow-dropup"></ion-icon>
|
||||||
|
</ion-fab-button>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
|
<!-- fab placed to the left and middle -->
|
||||||
|
<ion-fab left middle slot="fixed">
|
||||||
|
<ion-fab-button>
|
||||||
|
<ion-icon name="share"></ion-icon>
|
||||||
|
</ion-fab-button>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
|
<!-- fab placed to the right and middle -->
|
||||||
|
<ion-fab right middle slot="fixed">
|
||||||
|
<ion-fab-button>
|
||||||
|
<ion-icon name="add"></ion-icon>
|
||||||
|
</ion-fab-button>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
|
<!-- fab placed to the top and right and on the top edge of the content overlapping header -->
|
||||||
|
<ion-fab top right edge slot="fixed">
|
||||||
|
<ion-fab-button>
|
||||||
|
<ion-icon name="person"></ion-icon>
|
||||||
|
</ion-fab-button>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
|
<!-- fab placed to the bottom and left and on the bottom edge of the content overlapping footer with a list to the right -->
|
||||||
|
<ion-fab bottom left edge slot="fixed">
|
||||||
|
<ion-fab-button>
|
||||||
|
<ion-icon name="settings"></ion-icon>
|
||||||
|
</ion-fab-button>
|
||||||
|
<ion-fab-list side="end">
|
||||||
|
<ion-fab-button><ion-icon name="logo-vimeo"></ion-icon></ion-fab-button>
|
||||||
|
</ion-fab-list>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
|
<!-- fab placed in the center of the content with a list on each side -->
|
||||||
|
<ion-fab center middle slot="fixed">
|
||||||
|
<ion-fab-button><ion-icon name="share"></ion-icon></ion-fab-button>
|
||||||
|
<ion-fab-list side="top">
|
||||||
|
<ion-fab-button><ion-icon name="logo-vimeo"></ion-icon></ion-fab-button>
|
||||||
|
</ion-fab-list>
|
||||||
|
<ion-fab-list side="bottom">
|
||||||
|
<ion-fab-button><ion-icon name="logo-facebook"></ion-icon></ion-fab-button>
|
||||||
|
</ion-fab-list>
|
||||||
|
<ion-fab-list side="start">
|
||||||
|
<ion-fab-button><ion-icon name="logo-googleplus"></ion-icon></ion-fab-button>
|
||||||
|
</ion-fab-list>
|
||||||
|
<ion-fab-list side="end">
|
||||||
|
<ion-fab-button><ion-icon name="logo-twitter"></ion-icon></ion-fab-button>
|
||||||
|
</ion-fab-list>
|
||||||
|
</ion-fab>
|
||||||
|
</ion-content>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Auto Generated Below -->
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
#### alt
|
||||||
|
|
||||||
|
string
|
||||||
|
|
||||||
|
This attribute defines the alternative text describing the image.
|
||||||
|
Users will see this text displayed if the image URL is wrong,
|
||||||
|
the image is not in one of the supported formats, or if the image is not yet downloaded.
|
||||||
|
|
||||||
|
|
||||||
|
#### src
|
||||||
|
|
||||||
|
string
|
||||||
|
|
||||||
|
The image URL. This attribute is mandatory for the <img> element.
|
||||||
|
|
||||||
|
|
||||||
|
## Attributes
|
||||||
|
|
||||||
|
#### alt
|
||||||
|
|
||||||
|
string
|
||||||
|
|
||||||
|
This attribute defines the alternative text describing the image.
|
||||||
|
Users will see this text displayed if the image URL is wrong,
|
||||||
|
the image is not in one of the supported formats, or if the image is not yet downloaded.
|
||||||
|
|
||||||
|
|
||||||
|
#### src
|
||||||
|
|
||||||
|
string
|
||||||
|
|
||||||
|
The image URL. This attribute is mandatory for the <img> element.
|
||||||
|
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
#### ionImgDidLoad
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
*Built with [StencilJS](https://stenciljs.com/)*
|
19
core/src/components/img/test/basic/e2e.js
Normal file
19
core/src/components/img/test/basic/e2e.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { By, until } = require('selenium-webdriver');
|
||||||
|
const { register, Page, platforms } = require('../../../../../scripts/e2e');
|
||||||
|
|
||||||
|
class E2ETestPage extends Page {
|
||||||
|
constructor(driver, platform) {
|
||||||
|
super(driver, `http://localhost:3333/src/components/fab/test/basic?ionic:mode=${platform}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
platforms.forEach(platform => {
|
||||||
|
describe('fab/basic', () => {
|
||||||
|
register('should init', driver => {
|
||||||
|
const page = new E2ETestPage(driver, platform);
|
||||||
|
return page.navigate('#content');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
62
core/src/components/img/test/basic/index.html
Normal file
62
core/src/components/img/test/basic/index.html
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Floating Action Button - Basic</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||||
|
<script src="/dist/ionic.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Img - Basic</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content padding>
|
||||||
|
<f></f>
|
||||||
|
<f></f>
|
||||||
|
<ion-img src="https://i.ytimg.com/vi/rq6M3imPgW4/maxresdefault.jpg"></ion-img>
|
||||||
|
<f></f>
|
||||||
|
<f></f>
|
||||||
|
<f></f>
|
||||||
|
<f></f>
|
||||||
|
<f></f>
|
||||||
|
<f></f>
|
||||||
|
<ion-img id="hidden-car" src="https://www.ericpetersautos.com/wp-content/uploads/2017/11/1961-Ferrari-250-GT-SWB-Berlinetta-by-Scaglietti_Erik-Fuller-c-2016-Courtesy-RM-Sothebys-1.jpg"></ion-img>
|
||||||
|
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<ion-footer>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
f {
|
||||||
|
display: block;
|
||||||
|
margin: 15px auto;
|
||||||
|
max-width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
background: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hidden-car {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
f:last-of-type {
|
||||||
|
background: yellow;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</ion-app>
|
||||||
|
<script>
|
||||||
|
document.body.addEventListener('ionImgDidLoad', (event) => {
|
||||||
|
console.log('image did load', event.target);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Reference in New Issue
Block a user