From 55238d120c5b8da4fe67011e9d690c1629e34a0d Mon Sep 17 00:00:00 2001 From: Craig Michael Thompson Date: Wed, 3 Apr 2013 12:35:12 +0100 Subject: [PATCH] SVG plugin now supports complex shapes, just like Imagemap plugin, via use of generalized polys plugin used by both. --- Gruntfile.js | 6 +- src/imagemap/imagemap.js | 172 ------------------------- src/position/imagemap.js | 43 +++++++ src/position/polys.js | 115 +++++++++++++++++ src/position/svg.js | 82 ++++++++++++ src/{viewport => position}/viewport.js | 0 src/svg/svg.js | 47 ------- 7 files changed, 243 insertions(+), 222 deletions(-) delete mode 100644 src/imagemap/imagemap.js create mode 100644 src/position/imagemap.js create mode 100644 src/position/polys.js create mode 100644 src/position/svg.js rename src/{viewport => position}/viewport.js (100%) delete mode 100644 src/svg/svg.js diff --git a/Gruntfile.js b/Gruntfile.js index 88fe0a07..6593b92d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -44,12 +44,12 @@ module.exports = function(grunt) { css3: '<%=dirs.src%>/css3.css' }, plugins: { - svg: { js: '<%=dirs.src%>/svg/svg.js' }, ajax: { js: '<%=dirs.src%>/ajax/ajax.js' }, tips: { js: '<%=dirs.src%>/tips/tips.js', css: '<%=dirs.src%>/tips/tips.css' }, modal: { js: '<%=dirs.src%>/modal/modal.js', css: '<%=dirs.src%>/modal/modal.css' }, - viewport: { js: '<%=dirs.src%>/viewport/viewport.js' }, - imagemap: { js: '<%=dirs.src%>/imagemap/imagemap.js' }, + viewport: { js: '<%=dirs.src%>/position/viewport.js' }, + svg: { js: [ '<%=dirs.src%>/position/polys.js', '<%=dirs.src%>/position/svg.js' ] }, + imagemap: { js: [ '<%=dirs.src%>/position/polys.js', '<%=dirs.src%>/position/imagemap.js' ] }, ie6: { js: '<%=dirs.src%>/ie6/ie6.js', css: '<%=dirs.src%>/ie6/ie6.css' } }, diff --git a/src/imagemap/imagemap.js b/src/imagemap/imagemap.js deleted file mode 100644 index 4c7e4223..00000000 --- a/src/imagemap/imagemap.js +++ /dev/null @@ -1,172 +0,0 @@ -PLUGINS.imagemap = function(api, area, corner, adjustMethod) -{ - if(!area.jquery) { area = $(area); } - - var cache = (api.cache.areas = {}), - shape = (area[0].shape || area.attr('shape')).toLowerCase(), - coordsString = area[0].coords || area.attr('coords'), - baseCoords = coordsString.split(','), - coords = [], - image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'), - imageOffset = image.offset(), - result = { - width: 0, height: 0, - position: { - top: 1e10, right: 0, - bottom: 0, left: 1e10 - } - }, - circleMap = { - tc: 3 / 2, tr: 7 / 4, tl: 5 / 4, - bc: 1 / 2, br: 1 / 4, bl: 3 / 4, - rc: 2, lc: 1, c: 0 - }, - i = 0, next = 0, - dimensions, c, cx, cy; - - // POLY area coordinate calculator - // Special thanks to Ed Cradock for helping out with this. - // Uses a binary search algorithm to find suitable coordinates. - function polyCoordinates(result, coords, corner) - { - var i = 0, - compareX = 1, compareY = 1, - realX = 0, realY = 0, - newWidth = result.width, - newHeight = result.height; - - // Use a binary search algorithm to locate most suitable coordinate (hopefully) - while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0) - { - newWidth = Math.floor(newWidth / 2); - newHeight = Math.floor(newHeight / 2); - - if(corner.x === LEFT){ compareX = newWidth; } - else if(corner.x === RIGHT){ compareX = result.width - newWidth; } - else{ compareX += Math.floor(newWidth / 2); } - - if(corner.y === TOP){ compareY = newHeight; } - else if(corner.y === BOTTOM){ compareY = result.height - newHeight; } - else{ compareY += Math.floor(newHeight / 2); } - - i = coords.length; while(i--) - { - if(coords.length < 2){ break; } - - realX = coords[i][0] - result.position.left; - realY = coords[i][1] - result.position.top; - - if((corner.x === LEFT && realX >= compareX) || - (corner.x === RIGHT && realX <= compareX) || - (corner.x === CENTER && (realX < compareX || realX > (result.width - compareX))) || - (corner.y === TOP && realY >= compareY) || - (corner.y === BOTTOM && realY <= compareY) || - (corner.y === CENTER && (realY < compareY || realY > (result.height - compareY)))) { - coords.splice(i, 1); - } - } - } - - return { left: coords[0][0], top: coords[0][1] }; - } - - // Make sure we account for padding and borders on the image - imageOffset.left += Math.ceil((image.outerWidth() - image.width()) / 2); - imageOffset.top += Math.ceil((image.outerHeight() - image.height()) / 2); - - // Parse coordinates into proper array - if(shape === 'poly') { - i = baseCoords.length; while(i--) - { - next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ]; - - if(next[0] > result.position.right){ result.position.right = next[0]; } - if(next[0] < result.position.left){ result.position.left = next[0]; } - if(next[1] > result.position.bottom){ result.position.bottom = next[1]; } - if(next[1] < result.position.top){ result.position.top = next[1]; } - - coords.push(next); - } - } - else { - i = -1; while(i++ < baseCoords.length) { - (next = parseInt(baseCoords[i], 10)) && coords.push(next); - } - } - - // Calculate details - switch(shape) - { - case 'rect': - result = { - width: Math.abs(coords[2] - coords[0]), - height: Math.abs(coords[3] - coords[1]), - position: { - left: Math.min(coords[0], coords[2]), - top: Math.min(coords[1], coords[3]) - } - }; - break; - - case 'circle': - c = circleMap[ corner.abbrev() ]; - cx = coords[2] * Math.cos( c * Math.PI ); - cy = coords[2] * Math.sin( c * Math.PI ); - - result = { - width: (coords[2] * 2) - Math.abs(cx), - height: (coords[2] * 2) - Math.abs(cy), - position: { - left: coords[0] + cx, - top: coords[1] + cy - }, - adjustable: FALSE - }; - - break; - - case 'poly': - result.width = Math.abs(result.position.right - result.position.left); - result.height = Math.abs(result.position.bottom - result.position.top); - - if(corner.abbrev() === 'c') { - result.position = { - left: result.position.left + (result.width / 2), - top: result.position.top + (result.height / 2) - }; - } - else { - // Calculate if we can't find a cached value - if(!cache[corner+coordsString]) { - result.position = polyCoordinates(result, coords.slice(), corner); - - // If flip adjustment is enabled, also calculate the closest opposite point - if(adjustMethod && (adjustMethod[0] === 'flip' || adjustMethod[1] === 'flip')) { - result.offset = polyCoordinates(result, coords.slice(), { - x: corner.x === LEFT ? RIGHT : corner.x === RIGHT ? LEFT : CENTER, - y: corner.y === TOP ? BOTTOM : corner.y === BOTTOM ? TOP : CENTER - }); - - result.offset.left -= result.position.left; - result.offset.top -= result.position.top; - } - - // Store the result - cache[corner+coordsString] = result; - } - - // Grab the cached result - result = cache[corner+coordsString]; - } - - result.adjustable = FALSE; - break; - } - - // Add image position to offset coordinates - result.position.left += imageOffset.left; - result.position.top += imageOffset.top; - - return result; -}; - diff --git a/src/position/imagemap.js b/src/position/imagemap.js new file mode 100644 index 00000000..a90caea4 --- /dev/null +++ b/src/position/imagemap.js @@ -0,0 +1,43 @@ +PLUGINS.imagemap = function(api, area, corner, adjustMethod) +{ + if(!area.jquery) { area = $(area); } + + var shape = area.attr('shape').toLowerCase().replace('poly', 'polygon'), + image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'), + coordsString = area.attr('coords'), + coordsArray = coordsString.split(','), + imageOffset, coords, i, next; + + // If we can't find the image using the map... + if(!image.length) { return FALSE; } + + // Pass coordinates string if polygon + if(shape === 'polygon') { + result = PLUGINS.polys.polygon(coordsArray, corner); + } + + // Otherwise parse the coordinates and pass them as arguments + else if(PLUGINS.polys[shape]) { + for(i = -1, len = coordsArray.length, coords = []; ++i < len;) { + coords.push( parseInt(coordsArray[i], 10) ); + } + + result = PLUGINS.polys[shape].apply( + this, coords.concat(corner) + ); + } + + // If no shapre calculation method was found, return false + else { return FALSE; } + + // Make sure we account for padding and borders on the image + imageOffset = image.offset(); + imageOffset.left += Math.ceil((image.outerWidth() - image.width()) / 2); + imageOffset.top += Math.ceil((image.outerHeight() - image.height()) / 2); + + // Add image position to offset coordinates + result.position.left += imageOffset.left; + result.position.top += imageOffset.top; + + return result; +}; \ No newline at end of file diff --git a/src/position/polys.js b/src/position/polys.js new file mode 100644 index 00000000..d2132b96 --- /dev/null +++ b/src/position/polys.js @@ -0,0 +1,115 @@ +PLUGINS.polys = { + // POLY area coordinate calculator + // Special thanks to Ed Cradock for helping out with this. + // Uses a binary search algorithm to find suitable coordinates. + polygon: function(baseCoords, corner) { + var result = { + width: 0, height: 0, + position: { + top: 1e10, right: 0, + bottom: 0, left: 1e10 + }, + adjustable: FALSE + }, + i = 0, next, + coords = [], + compareX = 1, compareY = 1, + realX = 0, realY = 0, + newWidth, newHeight; + + // First pass, sanitize coords and determine outer edges + i = baseCoords.length; while(i--) { + next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ]; + + if(next[0] > result.position.right){ result.position.right = next[0]; } + if(next[0] < result.position.left){ result.position.left = next[0]; } + if(next[1] > result.position.bottom){ result.position.bottom = next[1]; } + if(next[1] < result.position.top){ result.position.top = next[1]; } + + coords.push(next); + } + + // Calculate height and width from outer edges + newWidth = result.width = Math.abs(result.position.right - result.position.left); + newHeight = result.height = Math.abs(result.position.bottom - result.position.top); + + // If it's the center corner... + if(corner.abbrev() === 'c') { + result.position = { + left: result.position.left + (result.width / 2), + top: result.position.top + (result.height / 2) + }; + } + else { + // Second pass, use a binary search algorithm to locate most suitable coordinate + while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0) + { + newWidth = Math.floor(newWidth / 2); + newHeight = Math.floor(newHeight / 2); + + if(corner.x === LEFT){ compareX = newWidth; } + else if(corner.x === RIGHT){ compareX = result.width - newWidth; } + else{ compareX += Math.floor(newWidth / 2); } + + if(corner.y === TOP){ compareY = newHeight; } + else if(corner.y === BOTTOM){ compareY = result.height - newHeight; } + else{ compareY += Math.floor(newHeight / 2); } + + i = coords.length; while(i--) + { + if(coords.length < 2){ break; } + + realX = coords[i][0] - result.position.left; + realY = coords[i][1] - result.position.top; + + if((corner.x === LEFT && realX >= compareX) || + (corner.x === RIGHT && realX <= compareX) || + (corner.x === CENTER && (realX < compareX || realX > (result.width - compareX))) || + (corner.y === TOP && realY >= compareY) || + (corner.y === BOTTOM && realY <= compareY) || + (corner.y === CENTER && (realY < compareY || realY > (result.height - compareY)))) { + coords.splice(i, 1); + } + } + } + result.position = { left: coords[0][0], top: coords[0][1] }; + } + + return result; + }, + + rect: function(ax, ay, bx, by, corner) { + return { + width: Math.abs(bx - ax), + height: Math.abs(by - ay), + position: { + left: Math.min(ax, bx), + top: Math.min(ay, by) + } + }; + }, + + _angles: { + tc: 3 / 2, tr: 7 / 4, tl: 5 / 4, + bc: 1 / 2, br: 1 / 4, bl: 3 / 4, + rc: 2, lc: 1, c: 0 + }, + ellipse: function(cx, cy, rx, ry, corner) { + var c = PLUGINS.polys._angles[ corner.abbrev() ], + rxc = rx * Math.cos( c * Math.PI ), + rys = ry * Math.sin( c * Math.PI ); + + return { + width: (rx * 2) - Math.abs(rxc), + height: (ry * 2) - Math.abs(rys), + position: { + left: cx + rxc, + top: cy + rys + }, + adjustable: FALSE + }; + }, + circle: function(cx, cy, r, corner) { + return PLUGINS.polys.ellipse(cx, cy, r, r, corner); + } +}; \ No newline at end of file diff --git a/src/position/svg.js b/src/position/svg.js new file mode 100644 index 00000000..b7f9a785 --- /dev/null +++ b/src/position/svg.js @@ -0,0 +1,82 @@ +PLUGINS.svg = function(api, svg, corner, adjustMethod) +{ + var doc = $(document), + elem = svg[0], + result = FALSE, + name, box, position, dimensions; + + // Ascend the parentNode chain until we find an element with getBBox() + while(!elem.getBBox) { elem = elem.parentNode; } + if(!elem.getBBox || !elem.parentNode) { return FALSE; } + + // Determine which shape calculation to use + switch(elem.nodeName) { + case 'rect': + position = PLUGINS.svg.toPixel(elem, elem.x.baseVal.value, elem.y.baseVal.value); + dimensions = PLUGINS.svg.toPixel(elem, + elem.x.baseVal.value + elem.width.baseVal.value, + elem.y.baseVal.value + elem.height.baseVal.value + ); + + result = PLUGINS.polys.rect( + position[0], position[1], + dimensions[0], dimensions[1], + corner + ); + break; + + case 'ellipse': + case 'circle': + position = PLUGINS.svg.toPixel(elem, + elem.cx.baseVal.value, + elem.cy.baseVal.value + ); + + result = PLUGINS.polys.ellipse( + position[0], position[1], + (elem.rx || elem.r).baseVal.value, + (elem.ry || elem.r).baseVal.value, + corner + ); + break; + + case 'line': + case 'polygon': + case 'polyline': + points = elem.points || [ + { x: elem.x1.baseVal.value, y: elem.y1.baseVal.value }, + { x: elem.x2.baseVal.value, y: elem.y2.baseVal.value } + ]; + + for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) { + next = points.getItem ? points.getItem(i) : points[i]; + result.push.apply(result, PLUGINS.svg.toPixel(elem, next.x, next.y)); + } + + result = PLUGINS.polys.polygon(result, corner); + break; + + // Invalid shape + default: return FALSE; + } + + // Adjust by scroll offset + result.position.left += doc.scrollLeft(); + result.position.top += doc.scrollTop(); + + return result; +}; + +PLUGINS.svg.toPixel = function(elem, x, y) { + var mtx = elem.getScreenCTM(), + root = elem.farthestViewportElement || elem, + result, point; + + // Create SVG point + if(!root.createSVGPoint) { return FALSE; } + point = root.createSVGPoint(); + + point.x = x; point.y = y; + result = point.matrixTransform(mtx); + return [ result.x, result.y ]; +}; \ No newline at end of file diff --git a/src/viewport/viewport.js b/src/position/viewport.js similarity index 100% rename from src/viewport/viewport.js rename to src/position/viewport.js diff --git a/src/svg/svg.js b/src/svg/svg.js deleted file mode 100644 index 652ebe83..00000000 --- a/src/svg/svg.js +++ /dev/null @@ -1,47 +0,0 @@ -PLUGINS.svg = function(api, svg, corner, adjustMethod) -{ - var doc = $(document), - elem = svg[0], - result = { - width: 0, height: 0, - position: { top: 1e10, left: 1e10 } - }, - box, mtx, root, point, tPoint; - - // Ascend the parentNode chain until we find an element with getBBox() - while(!elem.getBBox) { elem = elem.parentNode; } - - // Check for a valid bounding box method - if (elem.getBBox && elem.parentNode) { - box = elem.getBBox(); - mtx = elem.getScreenCTM(); - root = elem.farthestViewportElement || elem; - - // Return if no method is found - if(!root.createSVGPoint) { return result; } - - // Create our point var - point = root.createSVGPoint(); - - // Adjust top and left - point.x = box.x; - point.y = box.y; - tPoint = point.matrixTransform(mtx); - result.position.left = tPoint.x; - result.position.top = tPoint.y; - - // Adjust width and height - point.x += box.width; - point.y += box.height; - tPoint = point.matrixTransform(mtx); - result.width = tPoint.x - result.position.left; - result.height = tPoint.y - result.position.top; - - // Adjust by scroll offset - result.position.left += doc.scrollLeft(); - result.position.top += doc.scrollTop(); - } - - return result; -}; -