notes icon indicating copy to clipboard operation
notes copied to clipboard

如何美化图片缺失的默认样式

Open XboxYan opened this issue 4 years ago • 0 comments

在谷歌浏览器中,当图片加载失败时呈现如下

<img class="img" src="head.jpg" alt="head">

image

很难看不是吗?

现有的解决方法

网上这类问题的解决方式大多都是直接监听onerror方法

<img class="img" src="head.jpg" alt="head" onerror="this.src='xx.jpg'">

这种写法最为简单,但是风险也高,如果赋值的src也失败的话,那就无限循环了

image

当然,可以设定this.onerror=null只执行一次

<img class="img" src="head.jpg" alt="head" onerror="this.src='xx.jpg';this.onerror=null;">

虽然这样可以阻止无限循环,但是如果第二次加载失败,就回到了最初状态,还是很难看

稍微好一点的方法

在上面的基础上,如何更好的优化呢?首先我们必须保证onerror里面的图片一定要加载成功。如果保证成功呢?线上的肯定不行,可以设置一张base64的图片,这样就能保证一定加载成功了

<img class="img" src="head.jpg" alt="head" onerror="this.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj4gPHBhdGggZD0iTTQ2NCA2NEg0OEMyMS40OSA2NCAwIDg1LjQ5IDAgMTEydjI4OGMwIDI2LjUxIDIxLjQ5IDQ4IDQ4IDQ4aDQxNmMyNi41MSAwIDQ4LTIxLjQ5IDQ4LTQ4VjExMmMwLTI2LjUxLTIxLjQ5LTQ4LTQ4LTQ4em0tNiAzMzZINTRhNiA2IDAgMCAxLTYtNlYxMThhNiA2IDAgMCAxIDYtNmg0MDRhNiA2IDAgMCAxIDYgNnYyNzZhNiA2IDAgMCAxLTYgNnpNMTI4IDE1MmMtMjIuMDkxIDAtNDAgMTcuOTA5LTQwIDQwczE3LjkwOSA0MCA0MCA0MCA0MC0xNy45MDkgNDAtNDAtMTcuOTA5LTQwLTQwLTQwek05NiAzNTJoMzIwdi04MGwtODcuNTE1LTg3LjUxNWMtNC42ODYtNC42ODYtMTIuMjg0LTQuNjg2LTE2Ljk3MSAwTDE5MiAzMDRsLTM5LjUxNS0zOS41MTVjLTQuNjg2LTQuNjg2LTEyLjI4NC00LjY4Ni0xNi45NzEgMEw5NiAzMDR2NDh6Ij48L3BhdGg+IDwvc3ZnPg=='">

尽管能保证加载成功,但是这html结构也太难看了吧,洋洋洒洒的一大片。

进一步,我们可以设置一张很小的图片,比如1像素的gif。其实只要图片存在,就不会出现文章头部的那张加载失败的图片。然后,再设置背景图片,就可以达到默认图的效果了

<img class="img" src="head.jpg" alt="head" onerror="this.src='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';">
<!--是不是短了很多呢-->
img{
	background:url('head.jpg') #eee
}

即使背景图加载失败,也不会留下那张丑陋的默认图,是不是好了很多呢?

如果你有代码洁癖,肯定忍受不了在html添加内联脚本,那么,可以将脚本提取出来

<img class="img" src="head.jpg" alt="head" onerror="error">
function error(){
	this.src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
}

你看能觉得onerror写在html上还是有点不雅,而且对这类全局函数的使用也不够放心,可以试试自定义标签

class XImg extends HTMLImageElement {
	constructor() {
		super();
	}
	connectedCallback() {
		this.addEventListener('error',()=>{
			this.src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
		})
	}
}

customElements.define('x-img', XImg, { extends: 'img' })

然后只需在html添加一个is属性就可以了,无论是页面上存在的元素,还是后来新添加的元素都起作用。

<img class="img" src="head.jpg" alt="head" is="x-img">

是不是有点悄无声息的感觉?

CSS解决方式

上面都是js的相关解决方式,可以说没有js解决不了的,只是看实现优不优雅。

对于img标签来说,正常情况选是不能包含伪元素(::before::after)的,但是,当图片加载失败的时候,又可以包含了。

img::after{
	content:'-error'
}

image

有了伪元素,就可以搞很多事情了。

这里有以下思路:

  1. 把默认图覆盖
  2. 把默认图挤走
  3. ??

先看第一个,img设置相对定位,伪元素设置绝对定位就可以了

img{
	position:relative;
}
img::after{
	content:attr(alt);/*可以添加一些提示*/
	position:absolute;
	left:0;
	top:0;
	width:100%;
	height:100%;
	background:#eee;/*用一个实色背景覆盖*/
	/*其他美化样式*/
}

第二种思路,可以用::beforeimg内部的shadow-dom挤下去(默认图就在shadow-dom里面)

image

然后设置img超出隐藏即可

img{
	overflow:hidden;/*超出隐藏*/
}
img::before{
	content:attr(alt);
	height:100%;/*设置高度100%即可*/
	/*其他美化样式*/
}

可以看出第二种方式无需背景色覆盖,代码量也更少,推荐。

其他解决方式

如果对语义没有严格要求的话,可用背景图片来代替

<div class="img"></div>
.img{
	background:url(1.jpg) url(默认图.jpg)
}

多背景会叠加,可以把默认图放在最后,这样在页面上显示为最底层,而且就算加载失败,背景图也没有默认图标。

还有一种html解决方式。object标签也可以设置图片资源,而且,当资源加载失败时才会显示子节点内容,这点和img有点类似。

<object class="img" data="pic.jpg" type="image/jpg">
	<!--加载失败时才会显示这里的内容-->
</object>

XboxYan avatar May 09 '20 02:05 XboxYan