/** * @ignore * ImageZoom. * @author yiminghe@gmail.com, qiaohua@taobao.com */ KISSY.add('imagezoom', function (S, Node, Overlay, Zoomer, undefined) { var $ = Node.all, doc = $(S.Env.host.document), IMAGEZOOM_ICON_TMPL = "<span class='{iconClass}'></span>", undefinedNode = /** @ignore @type Node */undefined, IMAGEZOOM_WRAP_TMPL = "<span class='{wrapClass}'></span>", STANDARD = 'standard', groupEventForInnerAnim = '.ks-imagezoom-img-mouse', INNER = 'inner', ABSOLUTE_STYLE = ' style="position:absolute;top:-9999px;left:-9999px;" ', BIG_IMG_TPL = '<img src=' + '{src} {style} />'; function constrain(v, l, r) { return Math.min(Math.max(v, l), r); } /** * image zoomer for kissy * @class KISSY.ImageZoom * @extends KISSY.Overlay */ var ImageZoom = Overlay.extend({ initializer: function () { var self = this; if (!self.get("bigImageWidth") || !self.get("bigImageHeight")) { S.error("bigImageWidth/bigImageHeight in ImageZoom must be set!"); } imageZoomRenderUI(self); imageZoomBindUI(self); }, renderUI: function () { var self = this, image = self.get('imageNode'), contentEl = self.get("contentEl"); self.bigImage = $(S.substitute(BIG_IMG_TPL, { src: self.get("bigImageSrc"), style: ABSOLUTE_STYLE })).appendTo(contentEl, undefined); self.bigImageCopy = $(S.substitute(BIG_IMG_TPL, { src: image.attr('src'), style: ABSOLUTE_STYLE })).prependTo(contentEl, undefined); if (self.get('type') != INNER) { self.lens = $('<span ' + ABSOLUTE_STYLE + ' class="' + self.get('prefixCls') + 'imagezoom-lens' + '"></span>') .appendTo(self.imageWrap, undefined); } }, bindUI: function () { var self = this; self.on('hide', onZoomerHide, self); }, destructor: function () { var self = this, img = self.get('imageNode'), imageWrap; onZoomerHide.call(self); if (imageWrap = self.imageWrap) { img.insertBefore(imageWrap, undefinedNode); imageWrap.remove(); } img.detach('mouseenter', self.__onImgEnter); }, '_onSetBigImageWidth': function (v) { var self = this; self.bigImage.width(v); self.bigImageCopy.width(v); }, '_onSetBigImageHeight': function (v) { var self = this; self.bigImage.height(v); self.bigImageCopy.height(v); }, '_onSetBigImageSrc': function (v) { this.bigImage.attr('src', v); }, '_onSetCurrentMouse': function (currentMouse) { var self = this, lensLeft, lensTop, pageX = currentMouse.pageX, pageY = currentMouse.pageY, lens = self.lens, bigImageOffset; // inner 动画中 if (self.bigImage.isRunning()) { return; } // 更新 lens 位置 if (lens) { lensLeft = pageX - self.lensWidth / 2; lensTop = pageY - self.lensHeight / 2; lens.offset({ left: self.lensLeft = constrain(lensLeft, self.minLensLeft, self.maxLensLeft), top: self.lensTop = constrain(lensTop, self.minLensTop, self.maxLensTop) }); } // note: 鼠标点对应放大点在中心位置 bigImageOffset = getBigImageOffsetFromMouse(self, currentMouse); self.bigImageCopy.css(bigImageOffset); self.bigImage.css(bigImageOffset); } }, { ATTRS: { /** * existing image node needed to be zoomed. * @cfg {HTMLElement|String} imageNode */ /** * @ignore */ imageNode: { setter: function (el) { return Node.one(el); } }, /** * existing image node's src. * @type {String} * @property imageSrc */ /** * @ignore */ imageSrc: { valueFn: function () { return this.get('imageNode').attr('src'); } }, /** * zoomed overlay width * Defaults to imageNode's width. * @cfg {Number} width */ /** * @ignore */ width: { valueFn: function () { return this.get("imageNode").width(); } }, /** * zoomed overlay height * Defaults to imageNode's height. * @cfg {Number} height */ /** * @ignore */ height: { valueFn: function () { return this.get("imageNode").height(); } }, /** * whether to allow imageNode zoom * @cfg {Boolean} hasZoom */ /** * whether to allow imageNode zoom * @type {Boolean} * @property hasZoom */ /** * @ignore */ hasZoom: { value: true, setter: function (v) { return !!v; } }, /** * type of zooming effect * @cfg {KISSY.ImageZoom.ZoomType} type */ /** * @ignore */ type: { value: STANDARD // STANDARD or INNER }, /** * big image src. * Default to: value of imageNode's **data-ks-imagezoom** attribute value * @cfg {string} bigImageSrc */ /** * big image src. * @type {string} * @property bigImageSrc */ /** * @ignore */ bigImageSrc: { valueFn: function () { return this.get('imageNode').attr('data-ks-imagezoom'); } }, /** * width of big image * @cfg {Number} bigImageWidth */ /** * width of big image * @type {Number} * @property bigImageWidth */ /** * @ignore */ bigImageWidth: {}, /** * height of big image * @cfg {Number} bigImageHeight */ /** * height of big image * @type {Number} * @property bigImageHeight */ /** * @ignore */ bigImageHeight: {}, /** * current mouse position * @private * @property currentMouse */ /** * @ignore */ currentMouse: {} } }, { xclass: 'imagezoom-viewer' }); // # -------------------------- private start function setZoomerPreShowSession(self) { var img = self.get('imageNode'), imageOffset = img.offset(), imageLeft, imageWidth, imageHeight, zoomMultipleH, zoomMultipleW, lensWidth, lensHeight, bigImageWidth = self.get('bigImageWidth'), bigImageHeight = self.get('bigImageHeight'), width = self.get('width'), height = self.get('height'), align, originNode, imageTop; imageLeft = self.imageLeft = imageOffset.left; imageTop = self.imageTop = imageOffset.top; imageWidth = self.imageWidth = img.width(); imageHeight = self.imageHeight = img.height(); zoomMultipleH = self.zoomMultipleH = bigImageHeight / imageHeight; zoomMultipleW = self.zoomMultipleW = bigImageWidth / imageWidth; // 考虑放大可视区域,大图,与实际小图 // 镜片大小和小图的关系相当于放大可视区域与大图的关系 // 计算镜片宽高, vH / bigImageH = lensH / imageH lensWidth = self.lensWidth = width / zoomMultipleW; lensHeight = self.lensHeight = height / zoomMultipleH; self.minLensLeft = imageLeft; self.minLensTop = imageTop; self.maxLensTop = imageTop + imageHeight - lensHeight; self.maxLensLeft = imageLeft + imageWidth - lensWidth; self.maxBigImageLeft = 0; self.maxBigImageTop = 0; self.minBigImageLeft = -(bigImageWidth - width); self.minBigImageTop = -(bigImageHeight - height); if (self.get('type') === INNER) { // inner 位置强制修改 self.set('align', { node: img, points: ['cc', 'cc'] }); } else { align = self.get("align") || {}; originNode = align.node; delete align.node; align = S.clone(align); align.node = originNode || img; self.set("align", align); } self.icon.hide(); doc.on('mousemove mouseleave', onMouseMove, self); } function onZoomerHide() { var self = this, lens = self.lens; doc.detach('mousemove mouseleave', onMouseMove, self); self.icon.show(); if (lens) { lens.hide(); } } function imageZoomRenderUI(self) { var imageWrap, icon, image = self.get('imageNode'); imageWrap = self.imageWrap = $(S.substitute(IMAGEZOOM_WRAP_TMPL, { wrapClass: self.get('prefixCls') + 'imagezoom-wrap' })).insertBefore(image, undefinedNode); imageWrap.prepend(image); icon = self.icon = $(S.substitute(IMAGEZOOM_ICON_TMPL, { iconClass: self.get('prefixCls') + 'imagezoom-icon' })); imageWrap.append(icon); } function imageZoomBindUI(self) { var img = self.get('imageNode'), currentMouse, type = self.get('type'), commonFn = (function () { var buffer; function t() { if (buffer) { return; } buffer = S.later(function () { buffer = 0; detachImg(img); setZoomerPreShowSession(self); self.show(); // after create lens self.lens.show() .css({ width: self.lensWidth, height: self.lensHeight }); self.set('currentMouse', currentMouse); }, 50); } t.stop = function () { buffer.cancel(); buffer = 0; }; return t; })(), // prevent flash of content for inner anim innerFn = S.buffer(function () { detachImg(img); setZoomerPreShowSession(self); self.show(); animForInner(self, 0.4, currentMouse); }, 50), fn = type == 'inner' ? innerFn : commonFn; img.on('mouseenter', self.__onImgEnter = function (ev) { if (self.get('hasZoom')) { currentMouse = ev; img.on('mousemove' + groupEventForInnerAnim,function (ev) { currentMouse = ev; fn(); }).on('mouseleave' + groupEventForInnerAnim, function () { fn.stop(); detachImg(img); }); fn(); } }); self.on('afterImageSrcChange', onImageZoomSetImageSrc, self); self.on('afterHasZoomChange', onImageZoomSetHasZoom, self); onImageZoomSetHasZoom.call(self, {newVal: self.get('hasZoom')}); } function detachImg(img) { img.detach('mouseleave' + groupEventForInnerAnim); img.detach('mousemove' + groupEventForInnerAnim); } function onMouseMove(ev) { var self = this, rl = self.imageLeft, rt = self.imageTop, rw = self.imageWidth, pageX = ev.pageX, pageY = ev.pageY, rh = self.imageHeight; if (String(ev.type) == 'mouseleave') { self.hide(); return; } if (pageX > rl && pageX < rl + rw && pageY > rt && pageY < rt + rh) { self.set('currentMouse', { pageX: pageX, pageY: pageY }); } else { self.hide(); } } // Inner 效果中的放大动画 function animForInner(self, seconds, currentMouse) { var bigImages = self.bigImage; bigImages.add(self.bigImageCopy); bigImages.stop(); // set min width and height bigImages.css({ width: self.imageWidth, height: self.imageHeight, left: 0, top: 0 }); bigImages.animate(S.mix({ width: self.get('bigImageWidth'), height: self.get('bigImageHeight') }, getBigImageOffsetFromMouse(self, currentMouse)), seconds); } function onImageZoomSetHasZoom(e) { this.icon[e.newVal ? 'show' : 'hide'](); } function onImageZoomSetImageSrc(e) { var src = e.newVal, self = this, bigImageCopy; self.get('imageNode').attr('src', src); if (bigImageCopy = self.bigImageCopy) { bigImageCopy.attr('src', src); } } function getBigImageOffsetFromMouse(self, currentMouse) { var width = self.get('width'), height = self.get('height'); return { left: constrain(-(currentMouse.pageX - self.imageLeft) * self.zoomMultipleW + width / 2, self.minBigImageLeft, self.maxBigImageLeft), top: constrain(-(currentMouse.pageY - self.imageTop) * self.zoomMultipleH + height / 2, self.minBigImageTop, self.maxBigImageTop) }; } // # -------------------------- private end /** * zoom mode for imagezoom * @enum {String} KISSY.ImageZoom.ZoomType */ ImageZoom.ZoomType = { /** * zoom overlay is beside imageNode */ STANDARD: 'standard', /** * zoom overlay covers imageNode */ INNER: 'inner' }; return ImageZoom; }, { requires: ['node', 'overlay'] }); /** * @ignore * NOTES: * 2012-12-17 yiminghe@gmail.com * - refactor and document * - TODO extend overlay ?? confused * * 20120504 by yiminghe@gmail.com * - refactor * - fix bug: show 前 hasZoom 设了无效 * * 201101 by qiaohua@taobao.com * - 重构代码, 基于 UIBase */