diff --git a/examples/Nyancat.png b/examples/Nyancat.png new file mode 100644 index 0000000..9c071f1 Binary files /dev/null and b/examples/Nyancat.png differ diff --git a/examples/basicShapeCirclePath.html b/examples/basicShapeCirclePath.html new file mode 100644 index 0000000..c26e31d --- /dev/null +++ b/examples/basicShapeCirclePath.html @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/src/offsetPath.js b/src/offsetPath.js index c6a1a12..49d8a50 100644 --- a/src/offsetPath.js +++ b/src/offsetPath.js @@ -2,6 +2,12 @@ 'use strict'; (function () { + var isNumeric = internalScope.isNumeric; + + function roundToHundredth (number) { + return Math.round(number * 100) / 100; + } + function basicShapePolygonParse (input, element) { // TODO: Support the fill-rule option and % var argumentList = input.split(','); @@ -50,37 +56,127 @@ return {type: 'path', path: path}; } - function basicShapeCircleParse (input, element) { - // TODO: Need element as an argument to this function - var radius; - var position = /at (.*?)$/.exec(input); + function getCirclePathPosition (parentProperties, position) { + var analysedPosition = []; + + if (position !== null) { + var positionList = position[1].split(/\s+/); + + // If only one value is specified, the second value is assumed to be 'center' + // https://drafts.csswg.org/css-backgrounds-3/#position + for (var index in positionList) { + var aPosition = positionList[index]; + + var aPositionUnit = /(%|px)$/.exec(aPosition)[1]; + if (aPositionUnit === null) { + return null; + } + + var aPositionValueString = aPosition.substring(0, aPosition.length - aPositionUnit.length); + if (!isNumeric(aPositionValueString) || aPositionValueString === '') { + return null; + } + + var aPositionValue = Number(aPositionValueString); + if (aPositionUnit === '%') { + if (Number(index) === 0) { + if (!parentProperties || !parentProperties.width) { + return null; + } + aPositionValue *= (parentProperties.width / 100); + } + if (Number(index) === 1) { + if (!parentProperties || !parentProperties.height) { + return null; + } + aPositionValue *= (parentProperties.height / 100); + } + } + analysedPosition[index] = aPositionValue; + } + } - // TODO: Need to support other positions as currently this only supports positions in which both x and y are specified and are in px + if (analysedPosition.length < 2) { + for (var i = analysedPosition.length; i < 2; i++) { + if (Number(i) === 0) { + if (!parentProperties || !parentProperties.width) { + return null; + } + analysedPosition[i] = parentProperties.width / 2; + } else if (Number(i) === 1) { + if (!parentProperties || !parentProperties.height) { + return null; + } + analysedPosition[i] = parentProperties.height / 2; + } + } + } + + return analysedPosition; + } + + function getCirclePathRadius (parentProperties, position, input) { + var radiusString; if (position === null) { - // TODO: Set default position to the center of the reference box - position = '0px 0px'; if (input !== '') { - radius = input; + radiusString = input; } } else { - position = position[1].split(/\s+/); - radius = (/^(.*?) at/.exec(input)); - if (radius === null) { - radius = 'closest-side'; + radiusString = (/^(.*?) at/.exec(input)); + // TODO: Add support for when a radius had not been specified + if (radiusString === null) { + radiusString = 'closest-side'; } else { - radius = radius[1]; + radiusString = radiusString[1]; } } - radius = Number(radius.substring(0, radius.length - 2)); + var radiusUnit = /(%|px)$/.exec(radiusString); + if (radiusUnit === null) { + return null; + } + + var radiusValueString = radiusString.substring(0, radiusString.length - radiusUnit[1].length); + if (!isNumeric(radiusValueString)) { + return null; + } + var radiusValue = Number(radiusValueString); + + if (radiusUnit[1] === '%') { + var height = parentProperties.height; + var width = parentProperties.width; + if (!height || !width) { + return null; + } - var positionX = Number(position[0].substring(0, position[0].length - 2)); - var positionY = Number(position[1].substring(0, position[1].length - 2)); + return roundToHundredth((Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) / Math.sqrt(2)) * radiusValue / 100); + } + + return Number(radiusValueString); + } + + function basicShapeCircleParse (input, element) { + var parentProperties = null; + if (element) { + parentProperties = element.offsetParent ? element.offsetParent.getBoundingClientRect() : null; + } + + var position = /at (.*?)$/.exec(input); + + var radiusValue = getCirclePathRadius(parentProperties, position, input); + if (!radiusValue) { + return undefined; + } + + var analysedPosition = getCirclePathPosition(parentProperties, position); + if (!analysedPosition) { + return undefined; + } - var pathString = 'M ' + positionX + ' ' + positionY + - ' m 0,' + (-radius) + - ' a ' + radius + ',' + radius + ' 0 0,1 ' + radius + ',' + radius + - ' a ' + radius + ',' + radius + ' 0 1,1 ' + (-radius) + ',' + (-radius) + ' z'; + var pathString = 'M ' + analysedPosition[0] + ' ' + analysedPosition[1] + + ' m 0,' + (-radiusValue) + + ' a ' + radiusValue + ',' + radiusValue + ' 0 0,1 ' + radiusValue + ',' + radiusValue + + ' a ' + radiusValue + ',' + radiusValue + ' 0 1,1 ' + (-radiusValue) + ',' + (-radiusValue) + ' z'; return {type: 'path', path: pathString}; } @@ -166,6 +262,7 @@ return undefined; } var toParse = [basicShapePolygonParse, basicShapeCircleParse, basicShapeInsetParse, basicShapeEllipseParse]; + // var toParse = [basicShapeCircleParse]; for (var i = 0; i < toParse.length; i++) { var result = toParse[i](shapeArguments[1], element); if (result) { @@ -228,4 +325,5 @@ internalScope.offsetPathParse = offsetPathParse; internalScope.offsetPathMerge = offsetPathMerge; + internalScope.roundToHundredth = roundToHundredth; })(); diff --git a/src/toTransform.js b/src/toTransform.js index fe4ec3e..5f3a76c 100644 --- a/src/toTransform.js +++ b/src/toTransform.js @@ -2,6 +2,8 @@ 'use strict'; (function () { + var roundToHundredth = internalScope.roundToHundredth; + function convertTranslate (input) { var valuesArray = internalScope.translateParse(input); @@ -88,10 +90,6 @@ return offsetDistanceLength; } - function roundToHundredth (number) { - return Math.round(number * 100) / 100; - } - function convertPathString (properties) { var offsetPath = internalScope.offsetPathParse(properties['offsetPath']); var closedLoop = isClosedLoop(offsetPath); diff --git a/test/pathBasicShapeCircleTest.js b/test/pathBasicShapeCircleTest.js index 6d50fae..57b2dd0 100644 --- a/test/pathBasicShapeCircleTest.js +++ b/test/pathBasicShapeCircleTest.js @@ -1,65 +1,97 @@ -/* global suite test internalScope */ +/* global assert suite test internalScope */ - (function () { - suite('offsetPath', function () { - test('basicShapeCircle', function () { - var assertTransformInterpolation = internalScope.assertTransformInterpolation; +(function () { + suite('offsetPath', function () { + test('basicShapeCircle', function () { + var assertTransformInterpolation = internalScope.assertTransformInterpolation; + var offsetPathParse = internalScope.offsetPathParse; - assertTransformInterpolation([ + var containerStyle = { + position: 'absolute', + height: '100px', + width: '200px' + }; + + var container = document.createElement('div'); + + for (var property in containerStyle) { + container.style[property] = containerStyle[property]; + } + + var target = document.createElement('div'); + container.appendChild(target); + document.body.appendChild(container); + + var circlePathString = offsetPathParse('circle(50%)', target).path; + assert.equal(circlePathString, 'M 100 50 m 0,-79.06 a 79.06,79.06 0 0,1 79.06,79.06 a 79.06,79.06 0 1,1 -79.06,-79.06 z'); + + circlePathString = offsetPathParse('circle(10px)', target).path; + assert.equal(circlePathString, 'M 100 50 m 0,-10 a 10,10 0 0,1 10,10 a 10,10 0 1,1 -10,-10 z'); + + circlePathString = offsetPathParse('circle(10px at 50%)', target).path; + assert.equal(circlePathString, 'M 100 50 m 0,-10 a 10,10 0 0,1 10,10 a 10,10 0 1,1 -10,-10 z'); + + circlePathString = offsetPathParse('circle(50% at 100px 200px)', target).path; + assert.equal(circlePathString, 'M 100 200 m 0,-79.06 a 79.06,79.06 0 0,1 79.06,79.06 a 79.06,79.06 0 1,1 -79.06,-79.06 z'); + + circlePathString = offsetPathParse('circle(10px at 50% 50%)', target).path; + assert.equal(circlePathString, 'M 100 50 m 0,-10 a 10,10 0 0,1 10,10 a 10,10 0 1,1 -10,-10 z'); + + assertTransformInterpolation([ {'offsetPath': 'circle(10px at 0px 0px)', 'offsetDistance': '0%'}, {'offsetPath': 'circle(10px at 0px 0px)', 'offsetDistance': '100%'}], - [ + [ {at: 0, is: 'translate3d(0px, -10px, 0px)'}, {at: 0.25, is: 'translate3d(10px, 0px, 0px) rotate(90deg)'}, {at: 0.5, is: 'translate3d(0px, 10px, 0px) rotate(180deg)'}, {at: 0.75, is: 'translate3d(-10px, 0px, 0px) rotate(-90deg)'}, {at: 1, is: 'translate3d(0px, -10px, 0px)'} - ] + ] ); - assertTransformInterpolation([ + assertTransformInterpolation([ {'offsetPath': 'circle(10px at 0px 0px)', 'offsetDistance': '0px'}, {'offsetPath': 'circle(10px at 0px 0px)', 'offsetDistance': '62.83px'}], - [ + [ {at: 0, is: 'translate3d(0px, -10px, 0px)'}, {at: 0.25, is: 'translate3d(10px, 0px, 0px) rotate(89.45deg)'}, {at: 0.5, is: 'translate3d(0.01px, 10px, 0px) rotate(180deg)'}, {at: 0.75, is: 'translate3d(-10px, 0.01px, 0px) rotate(-90deg)'}, {at: 1, is: 'translate3d(-0.01px, -10px, 0px) rotate(-0.55deg)'} - ] + ] ); - assertTransformInterpolation([ + assertTransformInterpolation([ {'offsetPath': 'circle(10px at 0px 0px)', 'offsetDistance': '0%'}, {'offsetPath': 'circle(10px at 0px 0px)', 'offsetDistance': '50%'}], - [ + [ {at: 0, is: 'translate3d(0px, -10px, 0px)'}, {at: 0.5, is: 'translate3d(10px, 0px, 0px) rotate(90deg)'}, {at: 1, is: 'translate3d(0px, 10px, 0px) rotate(180deg)'} - ] + ] ); - assertTransformInterpolation([ + assertTransformInterpolation([ {'offsetPath': 'circle(10px at 0px 0px)', 'offsetDistance': '0px'}, {'offsetPath': 'circle(10px at 0px 0px)', 'offsetDistance': '31.42px'}], - [ + [ {at: 0, is: 'translate3d(0px, -10px, 0px)'}, {at: 0.5, is: 'translate3d(10px, 0px, 0px) rotate(90deg)'}, {at: 1, is: 'translate3d(0px, 10px, 0px) rotate(179.45deg)'} - ] + ] ); - assertTransformInterpolation([ + assertTransformInterpolation([ {'offsetPath': 'circle(10px at 100px 100px)', 'offsetDistance': '0%'}, {'offsetPath': 'circle(10px at 100px 100px)', 'offsetDistance': '100%'}], - [ + [ {at: 0, is: 'translate3d(100px, 90px, 0px)'}, {at: 0.25, is: 'translate3d(110px, 100px, 0px) rotate(90deg)'}, {at: 0.5, is: 'translate3d(100px, 110px, 0px) rotate(180deg)'}, {at: 0.75, is: 'translate3d(90px, 100px, 0px) rotate(-90deg)'}, {at: 1, is: 'translate3d(100px, 90px, 0px)'} - ] + ] ); - }); - }); - })(); + }); + }); +})();