在项目中可能有这种需求,我们需要对一组图片使用swiper组件,让它成为幻灯片可以滑动,但是需要先将图片下载之后才init swiper。所以如果网络卡的话,会出现容器塌陷,当图片载入成功时,然后容器又从塌陷变成有高度状态,用户体验很不好。下面使用react组件方式来解决这个问题。
流程:
graph LR ajax请求获取图片数据-->占位图片--> 加载图片时显示占位 --> 加载完成后-->替换占位符并初始化swiper插件
考虑到有多张图片,什么时候图片完全加载成功呢?可以使用 Promise.all
来解决多个图片的加载成功的监听。 先写加载图片的loadImgs.js
function load(url){
return new Promise((resolve,reject)=>{
let img =new Image()
img.src=url;
img.onload=()=>{
resolve()
}
img.onerror=()=>{
reject();
}
})
}
function loadImgs(imgs){
if(imgs.length==0){
return Promise.reject();
}
let promisesImg =imgs.map(item=>(
load(item)
) );
return Promise.all(promisesImg)
}
export default loadImgs
复制代码
然后处理占位图片的问题,这里可以使用代理模式。控制对真实图片的展示和隐藏
为了复用占位组件,这里使用高阶组件。将真实图片展示组件作为它的子元素
/**
* 做一层代理 proxy
* @param {*} BasesComponent 基组件展示图片的或者包含图片的块
* @param {*} imageKey 图片字段
*/
import loadImgs from './loadImgs'
export let EnhanceBSwiper=function(BasesComponent,imageKey=images_keys){
const wrappedDisplayName = BasesComponent.name;
return class extends Component {
//便于调试
static displayName = `Enhance(${wrappedDisplayName})`;
constructor(props){
super(props)
this.state={
init: false
}
if(props.images.length>0){
//ajax api 请求已经完成
this.loadImg(props.images);
}
}
loadImg(images){
loadImgs(images).then(()=>{
this.setState({
init:true
})
console.log('ok')
},()=>{
//图片载入失败重试
// location.reload();
alert('imgs load error')
})
}
componentWillReceiveProps(nextProps) {
if(nextProps[imageKey].length!=this.props[imageKey].length){
this.loadImg(nextProps[imageKey])
}
}
render(){
let {init}=this.state;
if(!init){
return <div className="placeholder">
</div>
}
return <BasesComponent {...this.props} images={this.props[imageKey]}/>
}
}
}
复制代码
使用,现在有一个BSwiper组件。
export class BSwiper extends Component {
componentDidMount() {
setTimeout(this.newSwiper,200)
}
newSwiper(){
let swiper = new Swiper('.swiper-container', {
pagination: '.pagination',
loop:true,
paginationClickable: true,
paginationBulletRender: function (swiper, index, className) {
return '<span class="' + className ||"swiper-pagination-bullet" + '">' + (index + 1) + '</span>';
}
});
}
render() {
let { images} =this.props;
return (
<div className="swiper-container">
<div className="swiper-wrapper js_banner_list">
{ this.props.images.map( (item,index)=>(
<div className="swiper-slide" key={index}>
<img src={item} alt="" />
</div>
))}
</div>
<div className="pagination"></div>
</div>
);
}
}
复制代码
使用时
先
let BEnhanceBSwiper = EnhanceBSwiper( BSwiper ,'images');
<BEnhanceBSwiper images={images} />
复制代码
这样就拆分了功能,BSwiper组件只关心图片的初始化swiper,由上层组件来决定它什么时候初始化渲染。 上面那个高阶组件职责,加载所有图片前,初始化一个占位符,等加载完成后,再render真实的业务展示组件。至于展示什么业务组件,则由BaseComponent来决定。它可能是一个包含图片的列表,或者一个swiper。分离了职责。提高了维护性。 缺点:对某些图片没有情求成功做处理