Статьи

Как создать плагин для обрезки изображений jQuery с нуля — Часть II

Веб-приложения должны предоставлять простые в использовании решения для загрузки и управления многофункциональным контентом. Этот процесс может создать трудности для некоторых пользователей, которые имеют минимальные навыки редактирования фотографий. Обрезка является одним из наиболее часто используемых методов манипулирования фотографиями, и этот пошаговый учебник охватит весь процесс разработки плагина для обрезки изображений для библиотеки jQuery JavaScript.


В предыдущем уроке мы рассмотрели:

  • как расширить JQuery
  • как сделать плагин более гибким с помощью пользовательских опций
  • как создать базовое приложение для обрезки изображений

Сегодня мы пойдем дальше и завершим наш плагин: мы определим больше пользовательских опций, добавим обратные вызовы, сделаем выбор перетаскиваемым и изменяющим размер, создадим панель предварительного просмотра и подсказку о размере и напишем некоторый код на стороне сервера для обрезать изображение.


Откройте файл jquery.imagecrop.js расположенный по адресу /resources/js/imageCrop/ и добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var defaultOptions = {
    allowMove : true,
    allowResize : true,
    allowSelect : true,
    aspectRatio : 0,
    displayPreview : false,
    displaySizeHint : false,
    minSelect : [0, 0],
    minSize : [0, 0],
    maxSize : [0, 0],
    outlineOpacity : 0.5,
    overlayOpacity : 0.5,
    previewBoundary : 90,
    previewFadeOnBlur : 1,
    previewFadeOnFocus : 0.35,
    selectionPosition : [0, 0],
    selectionWidth : 0,
    selectionHeight : 0,
 
    // Plug-in’s event handlers
    onChange : function() {},
    onSelect : function() {}
};

Мы добавили больше опций и два обратных вызова, onChange и onSelect . Эти два могут быть весьма полезны при получении состояния плагина.

Вот краткое изложение опций, которые мы добавляем:

  • aspectRatio — Определяет соотношение сторон выделения (значение по умолчанию 0 ).
  • displayPreview — Определяет, является ли панель предварительного просмотра видимой или нет (значение по умолчанию — false )
  • displaySizeHint — Определяет, является ли подсказка размера видимой или нет (значение по умолчанию — false )
  • minSize — Определяет минимальный размер выделения (значение по умолчанию [0, 0] )
  • maxSize — определяет максимальный размер выделения (значение по умолчанию [0, 0] )
  • previewBoundary — Определяет размер панели предварительного просмотра (значение по умолчанию — 90 )
  • previewFadeOnBlur — Определяет непрозрачность панели предварительного просмотра для размытия (значение по умолчанию равно 1 )
  • previewFadeOnFocus — указывает непрозрачность области предварительного просмотра в фокусе (значение по умолчанию — 0.35 ).
  • onCahnge — возвращает состояние плагина при изменении выбора
  • onSelect — возвращает состояние плагина, когда выбор сделан

На этом этапе мы собираемся добавить больше слоев. Давайте начнем с подсказки размера.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
 
// Initialize a background layer of size hint and place it above the
// selection layer
var $sizeHintBackground = $(‘<div id=»image-crop-size-hint-background» />’)
        .css({
            opacity : 0.35,
            position : ‘absolute’
        })
        .insertAfter($selection);
 
// Initialize a foreground layer of size hint and place it above the
// background layer
    var $sizeHintForeground = $(‘<span id=»image-crop-size-hint-foreground» />’)
            .css({
                position : ‘absolute’
            })
            .insertAfter($sizeHintBackground);

Мы добавили два отдельных слоя, потому что не хотим, чтобы на передний план влияла непрозрачность фона.

Теперь мы добавим еще девять слоев: обработчики изменения размера.

01
02
03
04
05
06
07
08
09
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
 
// Initialize a north/west resize handler and place it above the
// selection layer
var $nwResizeHandler = $(‘<div class=»image-crop-resize-handler» id=»image-crop-nw-resize-handler» />’)
        .css({
            opacity : 0.5,
            position : ‘absolute’
        })
        .insertAfter($selection);
 
// Initialize a north resize handler and place it above the selection
// layer
var $nResizeHandler = $(‘<div class=»image-crop-resize-handler» id=»image-crop-n-resize-handler» />’)
        .css({
            opacity : 0.5,
            position : ‘absolute’
        })
        .insertAfter($selection);
 
// Initialize a north/east resize handler and place it above the
// selection layer
var $neResizeHandler = $(‘<div class=»image-crop-resize-handler» id=»image-crop-ne-resize-handler» />’)
        .css({
            opacity : 0.5,
            position : ‘absolute’
        })
        .insertAfter($selection);
 
// Initialize an west resize handler and place it above the selection
// layer
var $wResizeHandler = $(‘<div class=»image-crop-resize-handler» id=»image-crop-w-resize-handler» />’)
        .css({
            opacity : 0.5,
            position : ‘absolute’
        })
        .insertAfter($selection);
 
// Initialize an east resize handler and place it above the selection
// layer
var $eResizeHandler = $(‘<div class=»image-crop-resize-handler» id=»image-crop-e-resize-handler» />’)
        .css({
            opacity : 0.5,
            position : ‘absolute’
        })
        .insertAfter($selection);
 
// Initialize a south/west resize handler and place it above the
// selection layer
var $swResizeHandler = $(‘<div class=»image-crop-resize-handler» id=»image-crop-sw-resize-handler» />’)
        .css({
            opacity : 0.5,
            position : ‘absolute’
        })
        .insertAfter($selection);
 
// Initialize a south resize handler and place it above the selection
// layer
var $sResizeHandler = $(‘<div class=»image-crop-resize-handler» id=»image-crop-s-resize-handler» />’)
        .css({
            opacity : 0.5,
            position : ‘absolute’
        })
        .insertAfter($selection);
 
// Initialize a south/east resize handler and place it above the
// selection layer
var $seResizeHandler = $(‘<div class=»image-crop-resize-handler» id=»image-crop-se-resize-handler» />’)
        .css({
            opacity : 0.5,
            position : ‘absolute’
        })
        .insertAfter($selection);

Мы инициализировали обработчик изменения размера для каждого угла и средней стороны.

И, наконец, панель предварительного просмотра.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
 
// Initialize a preview holder and place it after the outline layer
var $previewHolder = $(‘<div id=»image-crop-preview-holder» />’)
        .css({
            opacity : options.previewFadeOnBlur,
            overflow : ‘hidden’,
            position : ‘absolute’
        })
        .insertAfter($outline);
 
// Initialize a preview image and append it to the preview holder
var $preview = $(‘<img alt=»Crop preview» id=»image-crop-preview» />’)
        .css({
            position : ‘absolute’
        })
        .attr(‘src’, $image.attr(‘src’))
        .appendTo($previewHolder);

Мы инициализировали два слоя:

  • держатель, который работает как маска и
  • изображение предварительного просмотра, которое имеет тот же источник, что и исходное изображение.
Дерево каталогов

Мы использовали метод .appendTo() чтобы вставить изображение предварительного просмотра в конец держателя.


Сначала мы добавим две новые глобальные переменные.

1
2
3
4
5
6
7
8
 
// Initialize global variables
var resizeHorizontally = true,
    resizeVertically = true,
    selectionExists,
    selectionOffset = [0, 0],
    selectionOrigin = [0, 0];

Эти переменные понадобятся нам позже, когда мы обновим resizeSelection() .

В первой части мы позаботились только о опции allowSelect . Давайте также обработаем allowMove и allowResize .

1
2
3
4
5
6
7
8
9
 
if (options.allowMove)
    // Bind an event handler to the ‘mousedown’ event of the selection layer
    $selection.mousedown(pickSelection);
 
if (options.allowResize)
    // Bind an event handler to the ‘mousedown’ event of the resize handlers
    $(‘div.image-crop-resize-handler’).mousedown(pickResizeHandler);

Мы прикрепили событие mousedown к выделению и все обработчики изменения размера.

Теперь нам нужно написать немного больше кода, чтобы обновить новые слои, которые мы добавили ранее.

01
02
03
04
05
06
07
08
09
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
 
// Update the size hint
function updateSizeHint(action) {
    switch (action) {
        case ‘fade-out’ :
            // Fade out the size hint
            $sizeHintBackground.fadeOut(‘slow’);
            $sizeHintForeground.fadeOut(‘slow’);
 
            break;
        default :
            var display = (selectionExists && options.displaySize) ?
 
            // Update the foreground layer
            $sizeHintForeground.css({
                    cursor : ‘default’,
                    display : display,
                    left : options.selectionPosition[0] + 4,
                    top : options.selectionPosition[1] + 4
                })
                .html(options.selectionWidth + ‘x’ + options.selectionHeight);
 
            // Update the background layer
            $sizeHintBackground.css({
                    cursor : ‘default’,
                    display : display,
                    left : options.selectionPosition[0] + 1,
                    top : options.selectionPosition[1] + 1
                })
                .width($sizeHintForeground.width() + 6)
                .height($sizeHintForeground.height() + 6);
    }
};

Функция updateSizeHint() обрабатывает два случая в зависимости от указанного параметра.

  • Если ничего не указано, поведение по умолчанию — отображать и обновлять подсказку о размере (если выбор существует).
  • Второе поведение — исчезнуть намек. Это будет использоваться, когда пользователь выполнит изменение размера выделения.

На предыдущем шаге мы только инициализировали обработчики изменения размера. Теперь мы разместим их в правильном положении.

01
02
03
04
05
06
07
08
09
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
 
// Update the resize handlers
function updateResizeHandlers(action) {
    switch (action) {
        case ‘hide-all’ :
            $(‘.image-crop-resize-handler’).each(function() {
                $(this).css({
                        display : ‘none’
                    });
            });
 
            break;
        default :
            var display = (selectionExists && options.allowResize) ?
 
            $nwResizeHandler.css({
                    cursor : ‘nw-resize’,
                    display : display,
                    left : options.selectionPosition[0] — Math.round($nwResizeHandler.width() / 2),
                    top : options.selectionPosition[1] — Math.round($nwResizeHandler.height() / 2)
                });
 
            $nResizeHandler.css({
                    cursor : ‘n-resize’,
                    display : display,
                    left : options.selectionPosition[0] + Math.round(options.selectionWidth / 2 — $neResizeHandler.width() / 2) — 1,
                    top : options.selectionPosition[1] — Math.round($neResizeHandler.height() / 2)
                });
 
            $neResizeHandler.css({
                    cursor : ‘ne-resize’,
                    display : display,
                    left : options.selectionPosition[0] + options.selectionWidth — Math.round($neResizeHandler.width() / 2) — 1,
                    top : options.selectionPosition[1] — Math.round($neResizeHandler.height() / 2)
                });
 
            $wResizeHandler.css({
                    cursor : ‘w-resize’,
                    display : display,
                    left : options.selectionPosition[0] — Math.round($neResizeHandler.width() / 2),
                    top : options.selectionPosition[1] + Math.round(options.selectionHeight / 2 — $neResizeHandler.height() / 2) — 1
                });
 
            $eResizeHandler.css({
                    cursor : ‘e-resize’,
                    display : display,
                    left : options.selectionPosition[0] + options.selectionWidth — Math.round($neResizeHandler.width() / 2) — 1,
                    top : options.selectionPosition[1] + Math.round(options.selectionHeight / 2 — $neResizeHandler.height() / 2) — 1
                });
 
            $swResizeHandler.css({
                    cursor : ‘sw-resize’,
                    display : display,
                    left : options.selectionPosition[0] — Math.round($swResizeHandler.width() / 2),
                    top : options.selectionPosition[1] + options.selectionHeight — Math.round($swResizeHandler.height() / 2) — 1
                });
 
            $sResizeHandler.css({
                    cursor : ‘s-resize’,
                    display : display,
                    left : options.selectionPosition[0] + Math.round(options.selectionWidth / 2 — $seResizeHandler.width() / 2) — 1,
                    top : options.selectionPosition[1] + options.selectionHeight — Math.round($seResizeHandler.height() / 2) — 1
                });
 
            $seResizeHandler.css({
                    cursor : ‘se-resize’,
                    display : display,
                    left : options.selectionPosition[0] + options.selectionWidth — Math.round($seResizeHandler.width() / 2) — 1,
                    top : options.selectionPosition[1] + options.selectionHeight — Math.round($seResizeHandler.height() / 2) — 1
                });
    }
};

Подобно последней функции, updateResizeHandlers() тестирует два случая: hide-all и по default . В первом случае мы вызываем метод .each() для итерации по соответствующим элементам.

Давайте создадим updatePreview() .

01
02
03
04
05
06
07
08
09
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
 
// Update the preview
function updatePreview(action) {
    switch (action) {
        case ‘focus’ :
            // Fade in the preview holder layer
            $previewHolder.stop()
                .animate({
                    opacity : options.previewFadeOnFocus
                });
 
            break;
        case ‘blur’ :
            // Fade out the preview holder layer
            $previewHolder.stop()
                .animate({
                    opacity : options.previewFadeOnBlur
                });
 
            break;
        case ‘hide’ :
            // Hide the preview holder layer
            $previewHolder.css({
                display : ‘none’
            });
 
            break;
        default :
            var display = (selectionExists && options.displayPreview) ?
 
            // Update the preview holder layer
            $previewHolder.css({
                    display : display,
                    left : options.selectionPosition[0],
                    top : options.selectionPosition[1] + options.selectionHeight + 10
                });
 
            // Update the preview size
            if (options.selectionWidth > options.selectionHeight) {
                if (options.selectionWidth && options.selectionHeight) {
                    // Update the preview image size
                    $preview.width(Math.round($image.width() * options.previewBoundary / options.selectionWidth));
                    $preview.height(Math.round($image.height() * $preview.width() / $image.width()));
 
                    // Update the preview holder layer size
                    $previewHolder.width(options.previewBoundary)
                    .height(Math.round(options.selectionHeight * $preview.height() / $image.height()));
                }
            } else {
                if (options.selectionWidth && options.selectionHeight) {
                    // Update the preview image size
                    $preview.height(Math.round($image.height() * options.previewBoundary / options.selectionHeight));
                    $preview.width(Math.round($image.width() * $preview.height() / $image.height()));
 
                    // Update the preview holder layer size
                    $previewHolder.width(Math.round(options.selectionWidth * $preview.width() / $image.width()))
                        .height(options.previewBoundary);
                }
            }
 
            // Update the preview image position
            $preview.css({
                left : — Math.round(options.selectionPosition[0] * $preview.width() / $image.width()),
                top : — Math.round(options.selectionPosition[1] * $preview.height() / $image.height())
            });
    }
};

Код для первых трех случаев должен быть понятен. Мы вызываем метод .animate() для выполнения пользовательской анимации набора свойств CSS. Далее мы определяем значение display и устанавливаем положение предварительного просмотра. Затем мы масштабируем изображение предварительного просмотра в соответствии с параметром previewBoundary и вычисляем его новую позицию.

Нам также необходимо обновить updateCursor() .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
// Update the cursor type
function updateCursor(cursorType) {
    $trigger.css({
            cursor : cursorType
        });
 
    $outline.css({
            cursor : cursorType
        });
 
    $selection.css({
            cursor : cursorType
        });
 
    $sizeHintBackground.css({
            cursor : cursorType
        });
 
    $sizeHintForeground.css({
            cursor : cursorType
        });
};

А теперь последняя функция этого шага.

01
02
03
04
05
06
07
08
09
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
 
// Update the plug-in interface
function updateInterface(sender) {
    switch (sender) {
        case ‘setSelection’ :
            updateOverlayLayer();
            updateSelection();
            updateResizeHandlers(‘hide-all’);
            updatePreview(‘hide’);
 
            break;
        case ‘pickSelection’ :
            updateResizeHandlers(‘hide-all’);
 
            break;
        case ‘pickResizeHandler’ :
            updateSizeHint();
            updateResizeHandlers(‘hide-all’);
 
            break;
        case ‘resizeSelection’ :
            updateSelection();
            updateSizeHint();
            updateResizeHandlers(‘hide-all’);
            updatePreview();
            updateCursor(‘crosshair’);
 
            break;
        case ‘moveSelection’ :
            updateSelection();
            updateResizeHandlers(‘hide-all’);
            updatePreview();
            updateCursor(‘move’);
 
            break;
        case ‘releaseSelection’ :
            updateTriggerLayer();
            updateOverlayLayer();
            updateSelection();
            updateSizeHint(‘fade-out’);
            updateResizeHandlers();
            updatePreview();
 
            break;
        default :
            updateTriggerLayer();
            updateOverlayLayer();
            updateSelection();
            updateResizeHandlers();
            updatePreview();
    }
};

Здесь мы добавим только одну вещь: поддержку панели предварительного просмотра.

01
02
03
04
05
06
07
08
09
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
 
// Set a new selection
function setSelection(event) {
    // Prevent the default action of the event
    event.preventDefault();
 
    // Prevent the event from being notified
    event.stopPropagation();
 
    // Bind an event handler to the ‘mousemove’ event
    $(document).mousemove(resizeSelection);
 
    // Bind an event handler to the ‘mouseup’ event
    $(document).mouseup(releaseSelection);
 
    // If display preview option is enabled
    if (options.displayPreview) {
        // Bind an event handler to the ‘mouseenter’ event of the preview
        // holder
        $previewHolder.mouseenter(function() {
            updatePreview(‘focus’);
         });
 
         // Bind an event handler to the ‘mouseleave’ event of the preview
         // holder
         $previewHolder.mouseleave(function() {
             updatePreview(‘blur’);
         });
    }
 
    // Notify that a selection exists
    selectionExists = true;
 
    // Reset the selection size
    options.selectionWidth = 0;
    options.selectionHeight = 0;
 
    // Get the selection origin
    selectionOrigin = getMousePosition(event);
 
    // And set its position
    options.selectionPosition[0] = selectionOrigin[0];
    options.selectionPosition[1] = selectionOrigin[1];
 
    // Update only the needed elements of the plug-in interface
    // by specifying the sender of the current call
    updateInterface(‘setSelection’);
};

Мы протестировали опцию displayPreview и использовали функции .mouseenter() и .mouseleave() для присоединения обработчиков событий к держателю предварительного просмотра.


Чтобы сделать выбор перетаскиваемым, нам нужно сделать вывод, когда пользователь перемещает и отпускает кнопку мыши.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
// Pick the current selection
function pickSelection(event) {
    // Prevent the default action of the event
    event.preventDefault();
 
    // Prevent the event from being notified
    event.stopPropagation();
 
    // Bind an event handler to the ‘mousemove’ event
    $(document).mousemove(moveSelection);
 
    // Bind an event handler to the ‘mouseup’ event
    $(document).mouseup(releaseSelection);
 
    var mousePosition = getMousePosition(event);
 
    // Get the selection offset relative to the mouse position
    selectionOffset[0] = mousePosition[0] — options.selectionPosition[0];
    selectionOffset[1] = mousePosition[1] — options.selectionPosition[1];
 
    // Update only the needed elements of the plug-in interface
    // by specifying the sender of the current call
    updateInterface(‘pickSelection’);
};

Также у нас есть смещение выделения относительно позиции мыши. Это понадобится нам позже, в функции moveSelection() .


Пользователь сможет изменить размер выделения, выбрав и перетащив один из обработчиков изменения размера. И это может быть сделано двумя способами: по обеим осям — если пользователь выбирает перетаскивание обработчика из угла — или по одной оси — если пользователь выбирает перетаскивание обработчика из середины стороны.

01
02
03
04
05
06
07
08
09
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
 
// Pick one of the resize handlers
function pickResizeHandler(event) {
// Prevent the default action of the event
    event.preventDefault();
 
    // Prevent the event from being notified
    event.stopPropagation();
 
    switch (event.target.id) {
        case ‘image-crop-nw-resize-handler’ :
            selectionOrigin[0] += options.selectionWidth;
            selectionOrigin[1] += options.selectionHeight;
            options.selectionPosition[0] = selectionOrigin[0] — options.selectionWidth;
            options.selectionPosition[1] = selectionOrigin[1] — options.selectionHeight;
 
            break;
        case ‘image-crop-n-resize-handler’ :
            selectionOrigin[1] += options.selectionHeight;
            options.selectionPosition[1] = selectionOrigin[1] — options.selectionHeight;
 
            resizeHorizontally = false;
 
            break;
        case ‘image-crop-ne-resize-handler’ :
            selectionOrigin[1] += options.selectionHeight;
            options.selectionPosition[1] = selectionOrigin[1] — options.selectionHeight;
 
            break;
        case ‘image-crop-w-resize-handler’ :
            selectionOrigin[0] += options.selectionWidth;
            options.selectionPosition[0] = selectionOrigin[0] — options.selectionWidth;
 
            resizeVertically = false;
 
            break;
        case ‘image-crop-e-resize-handler’ :
            resizeVertically = false;
 
            break;
        case ‘image-crop-sw-resize-handler’ :
            selectionOrigin[0] += options.selectionWidth;
            options.selectionPosition[0] = selectionOrigin[0] — options.selectionWidth;
 
            break;
        case ‘image-crop-s-resize-handler’ :
            resizeHorizontally = false;
 
            break;
    }
 
    // Bind an event handler to the ‘mousemove’ event
    $(document).mousemove(resizeSelection);
 
    // Bind an event handler to the ‘mouseup’ event
    $(document).mouseup(releaseSelection);
 
    // Update only the needed elements of the plug-in interface
    // by specifying the sender of the current call
    updateInterface(‘pickResizeHandler’);
};

Мы написали кейс для каждого обработчика изменения размера, потому что каждый нуждается в определенных настройках.


В отличие от первой версии, resizeSelection() сможет проверять минимальный / максимальный размер и блокировать пропорции выбора.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
 
// Resize the current selection
function resizeSelection(event) {
    // Prevent the default action of the event
    event.preventDefault();
 
    // Prevent the event from being notified
    event.stopPropagation();
 
    var mousePosition = getMousePosition(event);
 
    // Get the selection size
    var height = mousePosition[1] — selectionOrigin[1],
        width = mousePosition[0] — selectionOrigin[0];
 
    // If the selection size is smaller than the minimum size set it
    // accordingly
    if (Math.abs(width) < options.minSize[0])
        width = (width >= 0) ?
 
    if (Math.abs(height) < options.minSize[1])
        height = (height >= 0) ?
 
    // Test if the selection size exceeds the image bounds
    if (selectionOrigin[0] + width < 0 || selectionOrigin[0] + width > $image.width())
        width = — width;
 
    if (selectionOrigin[1] + height < 0 || selectionOrigin[1] + height > $image.height())
        height = — height;
 
    if (options.maxSize[0] > options.minSize[0] &&
        options.maxSize[1] > options.minSize[1]) {
        // Test if the selection size is bigger than the maximum size
        if (Math.abs(width) > options.maxSize[0])
            width = (width >= 0) ?
 
        if (Math.abs(height) > options.maxSize[1])
            height = (height >= 0) ?
    }
 
    // Set the selection size
    if (resizeHorizontally)
        options.selectionWidth = width;
 
    if (resizeVertically)
        options.selectionHeight = height;
 
    // If any aspect ratio is specified
    if (options.aspectRatio) {
        // Calculate the new width and height
        if ((width > 0 && height > 0) || (width < 0 && height < 0))
            if (resizeHorizontally)
                height = Math.round(width / options.aspectRatio);
            else
                width = Math.round(height * options.aspectRatio);
        else
            if (resizeHorizontally)
                height = — Math.round(width / options.aspectRatio);
            else
                width = — Math.round(height * options.aspectRatio);
 
        // Test if the new size exceeds the image bounds
        if (selectionOrigin[0] + width > $image.width()) {
            width = $image.width() — selectionOrigin[0];
            height = (height > 0) ?
        }
 
        if (selectionOrigin[1] + height < 0) {
            height = — selectionOrigin[1];
            width = (width > 0) ?
        }
 
        if (selectionOrigin[1] + height > $image.height()) {
            height = $image.height() — selectionOrigin[1];
            width = (width > 0) ?
        }
 
        // Set the selection size
        options.selectionWidth = width;
        options.selectionHeight = height;
    }
 
    if (options.selectionWidth < 0) {
        options.selectionWidth = Math.abs(options.selectionWidth);
        options.selectionPosition[0] = selectionOrigin[0] — options.selectionWidth;
    } else
        options.selectionPosition[0] = selectionOrigin[0];
 
    if (options.selectionHeight < 0) {
        options.selectionHeight = Math.abs(options.selectionHeight);
        options.selectionPosition[1] = selectionOrigin[1] — options.selectionHeight;
    } else
        options.selectionPosition[1] = selectionOrigin[1];
 
    // Trigger the ‘onChange’ event when the selection is changed
    options.onChange(getCropData());
 
    // Update only the needed elements of the plug-in interface
    // by specifying the sender of the current call
    updateInterface(‘resizeSelection’);
};

Кроме того, мы onChange() обратный вызов onChange() в конце функции. Функция getCropData() возвращает текущее состояние плагина. Мы напишем его тело несколькими шагами позже.


Теперь мы напишем moveSelection() .

01
02
03
04
05
06
07
08
09
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
 
// Move the current selection
function moveSelection(event) {
    // Prevent the default action of the event
    event.preventDefault();
 
    // Prevent the event from being notified
    event.stopPropagation();
 
    var mousePosition = getMousePosition(event);
 
    // Set the selection position on the x-axis relative to the bounds
    // of the image
    if (mousePosition[0] — selectionOffset[0] > 0)
        if (mousePosition[0] — selectionOffset[0] + options.selectionWidth < $image.width())
            options.selectionPosition[0] = mousePosition[0] — selectionOffset[0];
        else
            options.selectionPosition[0] = $image.width() — options.selectionWidth;
    else
        options.selectionPosition[0] = 0;
 
    // Set the selection position on the y-axis relative to the bounds
    // of the image
    if (mousePosition[1] — selectionOffset[1] > 0)
        if (mousePosition[1] — selectionOffset[1] + options.selectionHeight < $image.height())
            options.selectionPosition[1] = mousePosition[1] — selectionOffset[1];
        else
            options.selectionPosition[1] = $image.height() — options.selectionHeight;
        else
            options.selectionPosition[1] = 0;
 
    // Trigger the ‘onChange’ event when the selection is changed
    options.onChange(getCropData());
 
    // Update only the needed elements of the plug-in interface
    // by specifying the sender of the current call
    updateInterface(‘moveSelection’);
};

Как и раньше, мы onChange() обратный вызов onChange() в конце функции.


Нам также нужно отредактировать releaseSelection() .

01
02
03
04
05
06
07
08
09
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
 
// Release the current selection
function releaseSelection(event) {
    // Prevent the default action of the event
    event.preventDefault();
 
    // Prevent the event from being notified
    event.stopPropagation();
 
    // Unbind the event handler to the ‘mousemove’ event
    $(document).unbind(‘mousemove’);
 
    // Unbind the event handler to the ‘mouseup’ event
    $(document).unbind(‘mouseup’);
 
    // Update the selection origin
    selectionOrigin[0] = options.selectionPosition[0];
    selectionOrigin[1] = options.selectionPosition[1];
 
    // Reset the resize constraints
    resizeHorizontally = true;
    resizeVertically = true;
 
    // Verify if the selection size is bigger than the minimum accepted
    // and set the selection existence accordingly
    if (options.selectionWidth > options.minSelect[0] &&
        options.selectionHeight > options.minSelect[1])
        selectionExists = true;
    else
        selectionExists = false;
 
    // Trigger the ‘onSelect’ event when the selection is made
    options.onSelect(getCropData());
 
    // If the selection doesn’t exist
    if (!selectionExists) {
        // Unbind the event handler to the ‘mouseenter’ event of the
        // preview
        $previewHolder.unbind(‘mouseenter’);
 
        // Unbind the event handler to the ‘mouseleave’ event of the
        // preview
        $previewHolder.unbind(‘mouseleave’);
    }
 
    // Update only the needed elements of the plug-in interface
    // by specifying the sender of the current call
    updateInterface(‘releaseSelection’);
};

Мы сбросили ограничения на изменение размера и добавили поддержку панели предварительного просмотра. Кроме того, мы onSelect() обратный вызов onSelect() таким же образом, как мы делали это раньше с onChange() .


Теперь мы почти готовы. Давайте напишем getCropData() .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
 
// Return an object containing information about the plug-in state
function getCropData() {
    return {
        selectionX : options.selectionPosition[0],
        selectionY : options.selectionPosition[1],
        selectionWidth : options.selectionWidth,
        selectionHeight : options.selectionHeight,
 
        selectionExists : function() {
            return selectionExists;
        }
    };
};

Мы только что написали последнюю функцию этого файла. Сохраните его и подготовьтесь к следующему шагу.


«Сокращение кода уменьшает его размер и сокращает время загрузки».

На этом этапе мы минимизируем код нашего плагина, чтобы уменьшить его размер и сократить время загрузки. Эта практика заключается в удалении ненужных символов, таких как комментарии, пробелы, новые строки и вкладки. Два популярных инструмента для минимизации кода JavaScript — это YUI Compressor (который также может минимизировать CSS) и JSMin . Мы будем использовать первый. Кроме того, он с открытым исходным кодом, так что вы можете взглянуть на код, чтобы понять, как именно он работает.

YUI Compressor написан на Java, поэтому не имеет значения, какую операционную систему вы используете. Единственное требование — Java > = 1.4. Загрузите YUI Compressor и распакуйте его в папку /resources/js/imageCrop/ . Откройте командную строку и измените текущий рабочий каталог на тот же путь.

Если вы используете его впервые, вам следует начать с выполнения следующей строки в командной строке и прочитать инструкции по использованию.

1
$ java -jar yuicompressor-xyzjar

Теперь давайте минимизируем наш код.

1
$ java -jar yuicompressor-xyzjar jquery.imagecrop.js -o jquery.imagecrop.js —preserve-semi

Не забудьте заменить xyz версией YUI Compressor, которую вы используете. Вот и все; дождитесь его завершения и закройте окно командной строки.


Откройте /resources/js/imageCrop/jquery.imagecrop.css и добавьте в него следующие строки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 
div#image-crop-size-hint-background {
    background-color : #000000;
}
 
span#image-crop-size-hint-foreground {
    color : #ffffff;
    font-family : ‘Verdana’, ‘Geneva’, sans-serif;
    font-size : 12px;
    text-shadow : 0 -1px 0 #000000;
}
 
div#image-crop-preview-holder {
    -moz-box-shadow : 0 0 5px #000000;
    -webkit-box-shadow : 0 0 5px #000000;
    border : 3px #ef2929 solid;
    box-shadow : 0 0 5px #000000;
}
 
img#image-crop-preview {
    border : none;
}
 
div.image-crop-resize-handler {
    background-color : #000000;
    border : 1px #ffffff solid;
    height : 7px;
    overflow : hidden;
    width : 7px;
}

Мы добавили некоторые стили для подсказки по размеру, панели предварительного просмотра и обработчиков изменения размера.


Сначала давайте загрузим минимизированный плагин.

Чтобы протестировать плагин, нам нужно каким-то образом получить размер и положение выделения. Вот почему мы будем использовать onSelect вызов onSelect ; возвращает объект с текущим состоянием плагина.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$(document).ready(function() {
    $(‘img#example’).imageCrop({
        displayPreview : true,
        displaySize : true,
        overlayOpacity : 0.25,
 
        onSelect : updateForm
    });
});
 
var selectionExists;
 
// Update form inputs
function updateForm(crop) {
    $(‘input#x’).val(crop.selectionX);
    $(‘input#y’).val(crop.selectionY);
    $(‘input#width’).val(crop.selectionWidth);
    $(‘input#height’).val(crop.selectionHeight);
 
    selectionExists = crop.selectionExists();
};
 
// Validate form data
function validateForm() {
    if (selectionExists)
        return true;
 
    alert(‘Please make a selection first!’);
 
    return false;
};

Функция updateForm() устанавливает входные значения и сохраняет их, если выбор существует. Затем функция validateForm() проверяет, существует ли выбор, и отображает всплывающее окно с предупреждением, если это необходимо.

Давайте добавим форму.

Мы добавили несколько скрытых входов и кнопку отправки.

В этом примере мы будем использовать PHP с библиотекой gd, но вы можете использовать любой другой серверный язык сценариев, который поддерживает графическую библиотеку.

Создайте пустой файл, назовите его crop.php и запустите ваш редактор.

01
02
03
04
05
06
07
08
09
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
<?php
    if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’)
    {
        // Initialize the size of the output image
        $boundary = 150;
        $dst_w = $_POST[‘width’];
        $dst_h = $_POST[‘height’];
 
        if ($dst_w > $dst_h)
        {
            $dst_h = $dst_h * $boundary / $dst_w;
            $dst_w = $boundary;
        }
        else
        {
            $dst_w = $dst_w * $boundary / $dst_h;
            $dst_h = $boundary;
        }
 
        // Initialize the quality of the output image
        $quality = 80;
 
        // Set the source image path
        $src_path = ‘resources/images/example.jpg’;
 
        // Create a new image from the source image path
        $src_image = imagecreatefromjpeg($src_path);
 
        // Create the output image as a true color image at the specified size
        $dst_image = imagecreatetruecolor($dst_w, $dst_h);
 
        // Copy and resize part of the source image with resampling to the
        // output image
        imagecopyresampled($dst_image, $src_image, 0, 0, $_POST[‘x’],
                           $_POST[‘y’], $dst_w, $dst_h, $_POST[‘width’],
                           $_POST[‘height’]);
 
        // Destroy the source image
        imagedestroy($src_image);
 
        // Send a raw HTTP header
        header(‘Content-type: image/jpeg’);
 
        // Output the image to browser
        imagejpeg($dst_image, null, $quality);
 
        // Destroy the output image
        imagedestroy($dst_image);
 
        // Terminate the current script
        exit();
    }
?>

Мы использовали метод imagecreatefromjpeg() для создания нового изображения из исходного пути и метод imagecreatetruecolor() для создания выходного изображения в виде изображения с истинным цветом. Затем мы вызвали imagecopyresampled() чтобы скопировать и изменить размер части изображения с повторной выборкой. Текущий тип документа не тот, который нам нужен, поэтому мы вызываем функцию header() чтобы изменить его на image/jpeg . Изображения, которые больше не нужны, уничтожаются с помощью функции imagedestroy() . С помощью exit() мы прекращаем выполнение текущего скрипта.


Теперь у нас есть полностью настраиваемый плагин для обрезки изображений jQuery, который позволяет пользователю создавать, перетаскивать и изменять размер выделения, отображает подсказку о размере и панель предварительного просмотра. И да, это выглядит одинаково даже в Internet Explorer 6! Таким образом, это учебник из двух частей! Спасибо за прочтение!