一套完整的生成缩略图及点击缩略图显示原图的解决方案

实例

https://kangyonggan.com/album

使用缩略图的优点

可以减轻服务器压力,给用户更好的体验。一个界面可能有很多图片,但是用户关心的图片可能只有那么一两个,如果我们加载全部原图,并且原图很大的时候,会浪费很多服务器资源,并且用户会看见大量图片处于加载中(如下图)。
如果使用缩略图,就可以解决这个问题。

如何生成缩略图

如果只是简单的生成缩略图,方法有很多,但是如何生成一个”好看”的缩略图呢?先捋一下思路,如下:

仅压缩

如果强行把一个长图压缩成宽图,就可能导致图片中的人变矮变胖。

仅剪裁

如果把一个大图,比如:800x2000的图,剪裁成400x300的图,可能图片中的主要内容根本都显示出来。

剪裁中心区域

一般来说,图片中心是主要内容,我们剪裁中心区域400x300的图。

等比例压缩后再剪裁中心区域

虽然好了很多,但是还有一个明显的问题,那就是图片包含的内容太少了,因此我先把原图等比例压缩成400x1000的图片,在剪裁中心区域的400x300。

注意:如果是宽图,先把高度压成300,再把宽度等比例压缩,最后剪裁中心区域。

java代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.kangyonggan.app.util;

import javax.imageio.ImageIO;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
* @author kangyonggan
* @date 6/23/17
*/
public final class Images {

private Images() {
}

/**
* 生成缩略图。
*
* @param source 原图(大)
* @param desc 目标图片
* @param width 目标宽
* @param height 目标高
* @throws Exception
*/
public static void thumb(String source, String desc, int width, int height) throws Exception {
BufferedImage image = ImageIO.read(new FileInputStream(source));

// 判断原图的长宽比是不是比目标图的长宽比大,用于计算压缩后的宽和高
int pressWidth;
int pressHeight;
if (image.getHeight() * 1.0 / image.getWidth() > height * 1.0 / width) {
// 原图是高图
// 让压缩后的宽度等于目标宽度
pressWidth = width;
// 等比例计算高度
pressHeight = (int) (1.0 * image.getHeight() * width / image.getWidth());
} else {
// 原图是宽图
// 让压缩后的高度等于目标高度
pressHeight = height;
// 等比例计算高度
pressWidth = (int) (1.0 * image.getWidth() * height / image.getHeight());
}

// 先压缩
BufferedImage temp = zoomImage(image, pressWidth, pressHeight);

// 再截取
int x = (temp.getWidth() - width) / 2;
int y = (temp.getHeight() - height) / 2;
temp = temp.getSubimage(x, y, width, height);

// 最后写文件
ImageIO.write(temp, "png", new FileOutputStream(desc));
}

/**
* 缩放图片
*
* @param image
* @param w
* @param h
* @throws Exception
*/
private static BufferedImage zoomImage(BufferedImage image, int w, int h) throws Exception {
double wr = w * 1.0 / image.getWidth();
double hr = h * 1.0 / image.getHeight();

AffineTransformOp ato = new AffineTransformOp(AffineTransform.getScaleInstance(wr, hr), null);
return ato.filter(image, null);
}

public static void main(String[] args) throws Exception {
thumb("/Users/kyg/Desktop/origin.jpeg", "/Users/kyg/Desktop/demo.png", 400, 300);
}

}

上面过程仅是生成缩略图,当生成缩略图后,我们要在网站上展示缩略图,点击放大可查看原图。为此,我写了一个jQuery插件,如下。

zoomer的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<ul class="list">
<li>
<img src="thubm_1.png" data-origin="1.png"/>
</li>
<li>
<img src="thubm_2.png"/>
</li>
<li>
<img src="thubm_3.png" data-origin="3.png"/>
</li>
</ul>

<script src="jquery/jquery-1.8.3.min.js"></script>
<script src="zoomer/zoomer.min.js"></script>
<script>
$(function () {
$(".list").zoomer();
})
</script>

zoomer.js源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/**
* 图片缩放
*
* @author kangyonggan
* @since 2017/3/21
*/
(function ($) {
/**
* 总入口
*
* @param $target
* @param settings
*/
function Zoomer($target, settings) {
// 把settings的属性合并到defaults,并且不改变defaults
settings = $.extend(true, $.fn.zoomer.defaults, settings);

// 模态框图层
var $overlay = $();
// 全部图片
var $allImgs = $target.find("img");

// 修改光标
$allImgs.css("cursor", "zoom-in");

// 给每个img一个下标
for (var i = 0; i < $allImgs.length; i++) {
$($allImgs[i]).data("index", i);
}

// 绑定点击事件
$allImgs.click(function () {
showModal($(this));
});

/**
* 显示模态框
*
* @param $img
*/
function showModal($img) {
$overlay.remove();
var src = $img.data('origin');
if (!src) {
src = $img.attr('src');
}
var index = $img.data("index");

$overlay = $('<div>' +
'<a href="javascript:" class="zoomer-prev-img" data-index="' + index + '" style="display: none; position: absolute;z-index: 1;"></a>' +
'<a href="javascript:" class="zoomer-next-img" data-index="' + index + '" style="display: none; position: absolute;z-index: 1;"></a>' +
'<div style="text-align: center;color: #ddd;position: absolute;left: 0;right: 0;top: 40%;">加载中...</div>' +
'<img src="' + src + '" style="max-width: 100%;max-height: 100%;display: none"/></div>').css({
position: "fixed",
left: 0,
top: 0,
bottom: 0,
right: 0,
color: '#fff',
zIndex: 9999,
cursor: 'zoom-out',
background: 'rgba(0, 0, 0, 0.4)',
textAlign: "center"
});
$("body").append($overlay);

// 图片加载完成事件
$overlay.find("img").load(function () {
// 图片距离顶部的距离
var top = ($(window).height() - $(this).height()) / 2;
// 图片距离左边的距离
var left = ($(window).width() - $(this).width()) / 2;

// 上一张
$(this).parents("div").find(".zoomer-prev-img").css({
"display": "inline-block",
"top": top + "px",
"left": left + "px",
"bottom": top + "px",
"width": $(this).width() / 3 + "px"
}).hover(function () {
$(this).css({
background: "-webkit-linear-gradient(left, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0))",
background: "-o-linear-gradient(right, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0))",
background: "-moz-linear-gradient(right, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0))",
background: "linear-gradient(to right, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0))"
})
}, function () {
$(this).css({
"background": "rgba(0, 0, 0, 0.0)"
})
});

// 下一张
$(this).parents("div").find(".zoomer-next-img").css({
"display": "inline-block",
"top": top + "px",
"right": left + "px",
"bottom": top + "px",
"width": $(this).width() / 3 + "px"
}).hover(function () {
$(this).css({
background: "-webkit-linear-gradient(left, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3))",
background: "-o-linear-gradient(right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3))",
background: "-moz-linear-gradient(right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3))",
background: "linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3))"
})
}, function () {
$(this).css({
"background": "rgba(0, 0, 0, 0.0)"
})
});

$(this).parents("div").find("div").css({"display": "none"});
$(this).css({"display": "inline-block", "marginTop": top + "px"});
});

// 图片加载失败事件
$overlay.find("img").error(function () {
if (settings.failure && typeof settings.failure === 'function') {
settings.failure($(this), $allImgs, $target, $overlay);
}
$(this).parents("div").find("div").html("加载失败!");
});

// 给模态框绑定点击事件
$overlay.click(function () {
$overlay.remove();
});

// 上一张事件
$overlay.find(".zoomer-prev-img").click(function () {
var index = $(this).data("index");
if (index === 0) {
$overlay.remove();
return;
}
showModal($($allImgs[index - 1]));
return false;
});

// 下一张事件
$overlay.find(".zoomer-next-img").click(function () {
var index = $(this).data("index");
if (index === $allImgs.length - 1) {
$overlay.remove();
return;
}
showModal($($allImgs[index + 1]));
return false;
});
}
}

/**
* 图片缩放
*
* @param option
* @returns {*}
*/
$.fn.zoomer = function (option) {
return this.each(function () {
$(this).data('zoomer', new Zoomer($(this), option));
});
};

/**
* 默认配置
*
* @type {{failure: null}}
*/
$.fn.zoomer.defaults = {
// 图片加载失败回调
failure: null
}
})(window.jQuery);

zoomer下载

zoomer.js
zoomer.min.js