0%

实现图片裁剪的效果

在生活中我们时常能看到这种图片裁剪的效果, 那么他实际上底层的原理是怎么实现的呢? 让我们深入的解剖一下.

原理分析

首先我们先将视图一分为三, 理解为三个层级叠加在一起的仰视图.

  • 最上面是可拖动的选择窗口
  • 中间待剪辑的可视窗口
  • 底层是一张opacity: .5的背景图片

基础结构

我们这里主要讲JavaScript, HTML与css就简要的过一下.

首先HTML基本结构是两张相同的结构, 两张图片分别是调整过透明度的底图和一张被裁剪过了的中间层.mainBox包裹着选择的小方块square,相对定位于image在最上面一层.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- index.html -->
<body>
<div id="box">
<img src="images/Konachan.com - 239917 sample.jpg" alt="img" id="image1">
<img src="images/Konachan.com - 239917 sample.jpg" alt="img" id="image2">
<div id="mainBox" class="main">
<div class="square left-up"></div>
<div class="square up"></div>
<div class="square right-up"></div>
<div class="square right"></div>
<div class="square right-down"></div>
<div class="square down"></div>
<div class="square left-down"></div>
<div class="square left"></div>
</div>
</div>
<script src="js/main.js"></script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* main.css */
body {background: #333;}
#box {position: absolute;top: 100px; left: 200px; width: 460px; height: 360px;}
#box img {width: 460px;}
#box #image1 {opacity: .5;position: absolute; top: 0;left: 0;}
#box #image2 {position: absolute; top: 0;left: 0; clip: rect(0, 200px, 200px, 0) }
#box .main {position: absolute;border: 1px solid #fff; width: 200px; height: 200px;box-sizing: border-box;}
#box .main .square {position: absolute; width: 8px;height: 8px; background: #fff}
#box .main .left-up{left: -4px;top: -4px;cursor: nw-resize;}
#box .main .up{left: 50%;top: -4px;margin-left: -4px;cursor: n-resize}
#box .main .right-up{right: -4px;top: -4px;cursor: ne-resize}
#box .main .right{right: -4px;top: 50%;margin-top: -4px;cursor: e-resize}
#box .main .right-down{right: -4px;bottom: -4px;cursor: se-resize}
#box .main .down{left: 50%;bottom: -4px;margin-left: -4px;cursor: s-resize}
#box .main .left-down{left: -4px;bottom: -4px;cursor: sw-resize}
#box .main .left{top: 50%;left: -4px;margin-top: -4px;cursor: w-resize}
#box {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

JavaScript实现

ESMAScript并没有提供可拖动的API, 那我们先来思考一下, 该如何实现拖动的功能~ 最上层有9个小方块(Square), 分别代表着不同方向的边界, 拖动这个边界, 无非需要实现这下面的底层步骤.

鼠标落下(MouseDown) => 鼠标拖动 => 松开鼠标(MouseUp).

先创建一个clipImage函数作为入口函数, 主要获取目标元素和生成DOM节点(后面讲), 绑定事件.
先给小方块绑定一个鼠标落下事件(mousedown), 当触发事件(MouseEvent)时, 监听器调用onMousedown函数.
onMousedown函数中, 接受四个参数e事件, box目标元素, ctrl小方块的方向, type属性, 主要是用来记录数据并暴露给全局变量进行通讯.

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
;(function() {
// Util工具函数
var util = {
$: function(dom) {
return document.querySelector(dom);
}
};

// "全局变量", 记录状态
var _MainBox, _MainCtrl, _MainType;
var moving = 0;

clipImage('mainBox');
function clipImage(id) {
var boxMain = document.getElementById(id);
var right = util.$('#box .main .right');

// Add mouse down event
up.addEventListener('mousedown', function(e) {
onMousedown(e, box, up, 'up');
});

function onMousedown(e, box, ctrl, type) {
var e = e || window.event;

// 将接受到的信息暴露出去.
_MainBox = box;
_MainCtrl = ctrl;
_MainType = type;
}
})();

紧接着来计算拖动的距离, 在onMouseDown函数上将moving拖动标记设为1(true也行).

判断拖动的标记是否启动, 创建getPosition函数获取元素相对于页面左/上边的偏移量用于计算拖动的偏移量. 如下图.

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
//初始化
var moving = 0;

function onMousedown(e, box, ctrl, type) {
var e = e || window.event;

_MainBox = box;
_MainCtrl = ctrl;
_MainType = type;

// 移动计算偏移量并设置到box上
moving = 1;
}

// 获取元素相对于文档的距离
function getPosition(node) {
var left = node.offsetLeft;
var top = node.offsetTop;
var parent = node.offsetParent;

while(parent) {
left += parent.offsetLeft;
top += parent.offsetTop;
parent = parent.offsetParent;
}
return { "left": left, "top": top};
}
// 监听鼠标相对于页面的坐标
document.onmousemove = function(e) {
if (moving) {
var e = e || window.event;
// 父容器的宽高
var addWidth, addHeight;
var width = _MainBox.offsetWidth;

// 相对于屏幕左/上的距离
var boxX = getPosition(_MainBox).left;

switch(_MainType) {
case "right":
addWidth = e.clientX - boxX - width;
_MainBox.style.width = width + addWidth + 'px';
break;
}
}
};

紧接着我们会发现虽然实现了拖动的效果, 但是松开鼠标box宽度还是会随着鼠标变化. 这是因为还没有重置标记. 随即监听鼠标松开事件(MouseUp). 单边拖动就完成啦~

1
2
3
4
5
// 鼠标松开
document.onmouseup = function() {
// reset
moving = 0;
};

整理归纳

我们将switch里的代码整理出来. 装进函数里去调用. 相续的将各个方向也加上, 原理也是同理. 值得注意的是将右面和下面要加上box的宽高才能计算出来. 剩下四个边角, 如左上什么的其实就是同时调用正方位的两个函数实现的实现起来. 然后拖动功能就大功告成啦~

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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
;(function() {
var util = {
$: function(dom) {
return document.querySelector(dom);
}
};

var _MainBox, _MainCtrl, _MainType;
var moving = 0;

clipImage('mainBox');
function clipImage(id) {
var boxMain = document.getElementById(id);
var up = util.$('#box .main .up');
var down = util.$('#box .main .down');
var right = util.$('#box .main .right');
var rightUp = util.$('#box .main .right-up');
var rightDown = util.$('#box .main .right-down');
var left = util.$('#box .main .left');
var leftUp = util.$('#box .main .left-up');
var leftDown = util.$('#box .main .left-down');

// Add mouse down event
right.addEventListener('mousedown', function(e) {
onMousedown(e, boxMain, right, 'right');
});

up.addEventListener('mousedown', function(e) {
onMousedown(e, boxMain, up, 'up');
});

down.addEventListener('mousedown', function(e) {
onMousedown(e, boxMain, down, 'down');
});

left.addEventListener('mousedown', function(e) {
onMousedown(e, boxMain, left, 'left');
});

leftUp.addEventListener('mousedown', function(e) {
onMousedown(e, boxMain, leftUp, 'leftUp');
});

leftDown.addEventListener('mousedown', function(e) {
onMousedown(e, boxMain, leftDown, 'leftDown');
});

rightUp.addEventListener('mousedown', function(e) {
onMousedown(e, boxMain, rightUp, 'rightUp');
});

rightDown.addEventListener('mousedown', function(e) {
onMousedown(e, boxMain, rightDown, 'rightDown');
});
}

/**
* [onMousedown description] Mouse down event
* @param {[type]} e [Event]
* @param {[type]} box [target vessel]
* @param {[type]} ctrl [Square DOM node]
* @param {[type]} type [Square direction]
*/
function onMousedown(e, box, ctrl, type) {
_MainBox = box;
_MainCtrl = ctrl;
_MainType = type;

moving = 1;
}

// 获取元素相对于左边的距离
function getPosition(node) {
var left = node.offsetLeft;
var top = node.offsetTop;
var parent = node.offsetParent;

while(parent) {
left += parent.offsetLeft;
top += parent.offsetTop;
parent = parent.offsetParent;
}
return { "left": left, "top": top};
}

// 鼠标移动
document.onmousemove = function(e) {
if (moving) {
var e = e || window.event;
// 父容器的宽高
var height = _MainBox.offsetHeight;

// 相对于屏幕左/上的距离
var boxY = getPosition(_MainBox).top;

switch(_MainType) {
case "right":
right(e);
break;
case "up":
up(e);
break;
case "down":
down(e);
break;
case "left":
left(e);
break;
case "leftUp":
leftUp(e);
break;
case "leftDown":
leftDown(e);
break;
case "rightUp":
rightUp(e);
break;
case "rightDown":
rightDown(e);
break;
}
}
};

// 鼠标松开
document.onmouseup = function() {
moving = 0;
};

function right(e) {
var width = _MainBox.offsetWidth;
var boxX = getPosition(_MainBox).left;
var addWidth = e.clientX - boxX - width;

_MainBox.style.width = width + addWidth + 'px';
}

function up(e) {
var height = _MainBox.offsetHeight;
var boxY = getPosition(_MainBox).top;
var addHeight = boxY - e.clientY;

_MainBox.style.height = height + addHeight + 'px';
_MainBox.style.top = _MainBox.offsetTop - addHeight + "px";
}

function down(e) {
var height = _MainBox.offsetHeight;
var boxY = getPosition(_MainBox).top;
var addHeight = e.clientY - boxY - height;

_MainBox.style.height = height + addHeight + 'px';
}

function left(e) {
var width = _MainBox.offsetWidth;
var boxX = getPosition(_MainBox).left;
var addWidth = boxX - e.clientX;

_MainBox.style.width = width + addWidth + 'px';
_MainBox.style.left = _MainBox.offsetLeft - addWidth + 'px';
}

function leftUp(e) {
var width = _MainBox.offsetWidth;
var height = _MainBox.offsetHeight;
var boxX = getPosition(_MainBox).left;
var boxY = getPosition(_MainBox).top;
var addWidth = boxX - e.clientX;
var addHeight = boxY - e.clientY;

_MainBox.style.width = width + addWidth + 'px';
_MainBox.style.height = height + addHeight + 'px';
_MainBox.style.top = _MainBox.offsetTop - addHeight + "px";
_MainBox.style.left = _MainBox.offsetLeft - addWidth + 'px';
}

function leftDown(e) {
var width = _MainBox.offsetWidth;
var height = _MainBox.offsetHeight;
var boxX = getPosition(_MainBox).left;
var boxY = getPosition(_MainBox).top;
var addWidth = boxX - e.clientX;
var addHeight = e.clientY - boxY - height;

_MainBox.style.height = height + addHeight + 'px';
_MainBox.style.width = width + addWidth + 'px';
_MainBox.style.left = _MainBox.offsetLeft - addWidth + 'px';
}

function rightUp(e) {
var width = _MainBox.offsetWidth;
var height = _MainBox.offsetHeight;
var boxX = getPosition(_MainBox).left;
var boxY = getPosition(_MainBox).top;
var addWidth = e.clientX - boxX - width;
var addHeight = boxY - e.clientY;

_MainBox.style.height = height + addHeight + "px";
_MainBox.style.top = _MainBox.offsetTop - addHeight + "px";
_MainBox.style.width = width + addWidth + "px";
}

function rightDown(e) {
var width = _MainBox.offsetWidth;
var height = _MainBox.offsetHeight;
var boxX = getPosition(_MainBox).left;
var boxY = getPosition(_MainBox).top;
var addWidth = e.clientX - boxX - width;
var addHeight = e.clientY - boxY - height;

_MainBox.style.height = height + addHeight + "px";
_MainBox.style.width = width + addWidth + "px";
}
})();

** <– 努力填坑中~ –> **

「请笔者喝杯奶茶鼓励一下」

欢迎关注我的其它发布渠道