diff --git a/LICENSE b/LICENSE index 0767b2c..8d5a9fa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Douglas McKechie +Copyright (c) 2012-2019 Douglas McKechie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c1b839e..75aa121 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,17 @@ Wheels can be animated using GreenSock's Animation Platform (TweenMax.js) which Winwheel.js Features Include: * Easy to use, highly configurable JavaScript classes. * Draw wheels using code generated segments or graphically rich images. +* Responsive features so wheels display correctly on different sized devices. * Numerous text orientation, direction, size and colour options. * Random or Pre-calculated prize stopping location. * Play sounds while the wheel is spinning including a "tick" sound. * Ability to get the segment the user clicked upon. * Fully commented source code. Plenty of tutorials and other documentation. -* Winwheel.js is free to use with an open source licence. +* Winwheel.js is free to use with an open source license. ## Example ```javascript -var myWheel = new Winwheel({ +let theWheel = new Winwheel({ 'numSegments' : 4, 'segments' : [ @@ -45,4 +46,11 @@ Please visit http://dougtesting.net/winwheel/docs to see a complete set of tutor ## Maintainer Douglas McKechie https://github.com/zarocknz -Keep informed about Winwheel.js by following https://twitter.com/dougtesting +## Please note +I am not planning to do any further work on this library as my day job keeps me very busy and after 7 years of Winwheel I would rather spend +any spare time I do have for personal coding on other projects. + +So this means if you would like a version of Winwheel.js for your current JavaScript framework of choice its up to you or others in the community +to create it. If you create one perhaps open an Issue with the details so others can find and use it. Thanks. + +You are welcome to ask questions using the Issues feature of Github, but please don't be offended if I take quite a long time to respond to them. To be honest its probably quicker to ask the Stackoverflow community for help https://stackoverflow.com/search?tab=newest&q=Winwheel diff --git a/Winwheel.js b/Winwheel.js index 7e1af2a..520c07d 100644 --- a/Winwheel.js +++ b/Winwheel.js @@ -4,7 +4,7 @@ The MIT License (MIT) - Copyright (c) 2018 Douglas McKechie + Copyright (c) 2012-2019 Douglas McKechie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -26,7 +26,7 @@ */ // ==================================================================================================================== -// The constructor for the WinWheel object, a JOSN-like array of options can be passed in. +// The constructor for the WinWheel object, a JSON-like array of options can be passed in. // By default the wheel is drawn if canvas object exists on the page, but can pass false as second parameter if don't want this to happen. // ==================================================================================================================== function Winwheel(options, drawWheel) @@ -58,31 +58,26 @@ function Winwheel(options, drawWheel) 'drawText' : true, // By default the text of the segments is rendered in code drawMode and not in image drawMode. 'pointerAngle' : 0, // Location of the pointer that indicates the prize when wheel has stopped. Default is 0 so the (corrected) 12 o'clock position. 'wheelImage' : null, // Must be set to image data in order to use image to draw the wheel - drawMode must also be 'image'. - 'imageDirection' : 'N' // Used when drawMode is segmentImage. Default is north, can also be (E)ast, (S)outh, (W)est. + 'imageDirection' : 'N', // Used when drawMode is segmentImage. Default is north, can also be (E)ast, (S)outh, (W)est. + 'responsive' : false, // If set to true the wheel will resize when the window first loads and also onResize. + 'scaleFactor' : 1, // Set by the responsive function. Used in many calculations to scale the wheel. }; // ----------------------------------------- // Loop through the default options and create properties of this class set to the value for the option passed in // or if not value for the option was passed in then to the default. - for (var key in defaultOptions) - { - if ((options != null) && (typeof(options[key]) !== 'undefined')) - { + for (let key in defaultOptions) { + if ((options != null) && (typeof(options[key]) !== 'undefined')) { this[key] = options[key]; - } - else - { + } else { this[key] = defaultOptions[key]; } } // Also loop though the passed in options and add anything specified not part of the class in to it as a property. - if (options != null) - { - for (var key in options) - { - if (typeof(this[key]) === 'undefined') - { + if (options != null) { + for (let key in options) { + if (typeof(this[key]) === 'undefined') { this[key] = options[key]; } } @@ -91,69 +86,52 @@ function Winwheel(options, drawWheel) // ------------------------------------------ // If the id of the canvas is set, try to get the canvas as we need it for drawing. - if (this.canvasId) - { + if (this.canvasId) { this.canvas = document.getElementById(this.canvasId); - if (this.canvas) - { + if (this.canvas) { // If the centerX and centerY have not been specified in the options then default to center of the canvas // and make the outerRadius half of the canvas width - this means the wheel will fill the canvas. - if (this.centerX == null) - { + if (this.centerX == null) { this.centerX = this.canvas.width / 2; } - if (this.centerY == null) - { + if (this.centerY == null) { this.centerY = this.canvas.height / 2; } - if (this.outerRadius == null) - { + if (this.outerRadius == null) { // Need to set to half the width of the shortest dimension of the canvas as the canvas may not be square. // Minus the line segment line width otherwise the lines around the segments on the top,left,bottom,right // side are chopped by the edge of the canvas. - if (this.canvas.width < this.canvas.height) - { + if (this.canvas.width < this.canvas.height) { this.outerRadius = (this.canvas.width / 2) - this.lineWidth; - } - else - { + } else { this.outerRadius = (this.canvas.height / 2) - this.lineWidth; } } // Also get a 2D context to the canvas as we need this to draw with. this.ctx = this.canvas.getContext('2d'); - } - else - { + } else { this.canvas = null; this.ctx = null; } - } - else - { - this.cavnas = null; + } else { + this.canvas = null; this.ctx = null; } - // ------------------------------------------ // Add array of segments to the wheel, then populate with segments if number of segments is specified for this object. this.segments = new Array(null); - for (x = 1; x <= this.numSegments; x++) - { + for (let x = 1; x <= this.numSegments; x++) { // If options for the segments have been specified then create a segment sending these options so // the specified values are used instead of the defaults. - if ((options != null) && (options['segments']) && (typeof(options['segments'][x-1]) !== 'undefined')) - { + if ((options != null) && (options['segments']) && (typeof(options['segments'][x-1]) !== 'undefined')) { this.segments[x] = new Segment(options['segments'][x-1]); - } - else - { + } else { this.segments[x] = new Segment(); } } @@ -162,101 +140,99 @@ function Winwheel(options, drawWheel) // Call function to update the segment sizes setting the starting and ending angles. this.updateSegmentSizes(); - // If the text margin is null then set to same as font size as we want some by default. - if (this.textMargin === null) - { + if (this.textMargin === null) { this.textMargin = (this.textFontSize / 1.7); } // ------------------------------------------ // If the animation options have been passed in then create animation object as a property of this class // and pass the options to it so the animation is set. Otherwise create default animation object. - if ((options != null) && (options['animation']) && (typeof(options['animation']) !== 'undefined')) - { + if ((options != null) && (options['animation']) && (typeof(options['animation']) !== 'undefined')) { this.animation = new Animation(options['animation']); - } - else - { + } else { this.animation = new Animation(); } // ------------------------------------------ // If some pin options then create create a pin object and then pass them in. - if ((options != null) && (options['pins']) && (typeof(options['pins']) !== 'undefined')) - { + if ((options != null) && (options['pins']) && (typeof(options['pins']) !== 'undefined')) { this.pins = new Pin(options['pins']); } // ------------------------------------------ - // On that note, if the drawMode is image change some defaults provided a value has not been specified. - if ((this.drawMode == 'image') || (this.drawMode == 'segmentImage')) - { + // If the drawMode is image change some defaults provided a value has not been specified. + if ((this.drawMode == 'image') || (this.drawMode == 'segmentImage')) { // Remove grey fillStyle. - if (typeof(options['fillStyle']) === 'undefined') - { + if (typeof(options['fillStyle']) === 'undefined') { this.fillStyle = null; } // Set strokeStyle to red. - if (typeof(options['strokeStyle']) === 'undefined') - { + if (typeof(options['strokeStyle']) === 'undefined') { this.strokeStyle = 'red'; } // Set drawText to false as we will assume any text is part of the image. - if (typeof(options['drawText']) === 'undefined') - { + if (typeof(options['drawText']) === 'undefined') { this.drawText = false; } // Also set the lineWidth to 1 so that segment overlay will look correct. - if (typeof(options['lineWidth']) === 'undefined') - { + if (typeof(options['lineWidth']) === 'undefined') { this.lineWidth = 1; } // Set drawWheel to false as normally the image needs to be loaded first. - if (typeof(drawWheel) === 'undefined') - { + if (typeof(drawWheel) === 'undefined') { drawWheel = false; } - } - else - { + } else { // When in code drawMode the default is the wheel will draw. - if (typeof(drawWheel) === 'undefined') - { + if (typeof(drawWheel) === 'undefined') { drawWheel = true; } } // Create pointer guide. - if ((options != null) && (options['pointerGuide']) && (typeof(options['pointerGuide']) !== 'undefined')) - { + if ((options != null) && (options['pointerGuide']) && (typeof(options['pointerGuide']) !== 'undefined')) { this.pointerGuide = new PointerGuide(options['pointerGuide']); - } - else - { + } else { this.pointerGuide = new PointerGuide(); } + // Check if the wheel is to be responsive, if so then need to save the original size of the canvas + // and also check for data- attributes on the canvas which help control the scaling. + if (this.responsive) { + winwheelToDrawDuringAnimation = this; + + // Save the original defined width and height of the canvas, this is needed later to work out the scaling. + this._originalCanvasWidth = this.canvas.width; + this._originalCanvasHeight = this.canvas.height; + + // Get data-attributes on the canvas. + this._responsiveScaleHeight = this.canvas.dataset.responsivescaleheight; + this._responsiveMinWidth = this.canvas.dataset.responsiveminwidth; + this._responsiveMinHeight = this.canvas.dataset.responsiveminheight; + this._responsiveMargin = this.canvas.dataset.responsivemargin; + + // Add event listeners for onload and onresize and call a function defined at the bottom + // of this script which will handle that and work out the scale factor. + window.addEventListener("load", winwheelResize); + window.addEventListener("resize", winwheelResize); + } + // Finally if drawWheel is true then call function to render the wheel, segment text, overlay etc. - if (drawWheel == true) - { + if (drawWheel == true) { this.draw(this.clearTheCanvas); - } - else if (this.drawMode == 'segmentImage') - { + } else if (this.drawMode == 'segmentImage') { // If segment image then loop though all the segments and load the images for them setting a callback // which will call the draw function of the wheel once all the images have been loaded. winwheelToDrawDuringAnimation = this; winhweelAlreadyDrawn = false; - for (y = 1; y <= this.numSegments; y ++) - { - if (this.segments[y].image !== null) - { + for (let y = 1; y <= this.numSegments; y ++) { + if (this.segments[y].image !== null) { this.segments[y].imgData = new Image(); this.segments[y].imgData.onload = winwheelLoadedImage; this.segments[y].imgData.src = this.segments[y].image; @@ -272,48 +248,40 @@ function Winwheel(options, drawWheel) Winwheel.prototype.updateSegmentSizes = function() { // If this object actually contains some segments - if (this.segments) - { + if (this.segments) { // First add up the arc used for the segments where the size has been set. - var arcUsed = 0; - var numSet = 0; + let arcUsed = 0; + let numSet = 0; // Remember, to make it easy to access segments, the position of the segments in the array starts from 1 (not 0). - for (x = 1; x <= this.numSegments; x ++) - { - if (this.segments[x].size !== null) - { + for (let x = 1; x <= this.numSegments; x ++) { + if (this.segments[x].size !== null) { arcUsed += this.segments[x].size; numSet ++; } } - var arcLeft = (360 - arcUsed); + let arcLeft = (360 - arcUsed); // Create variable to hold how much each segment with non-set size will get in terms of degrees. - var degreesEach = 0; + let degreesEach = 0; - if (arcLeft > 0) - { + if (arcLeft > 0) { degreesEach = (arcLeft / (this.numSegments - numSet)); } // ------------------------------------------ // Now loop though and set the start and end angle of each segment. - var currentDegree = 0; + let currentDegree = 0; - for (x = 1; x <= this.numSegments; x ++) - { + for (let x = 1; x <= this.numSegments; x ++) { // Set start angle. this.segments[x].startAngle = currentDegree; // If the size is set then add this to the current degree to get the end, else add the degreesEach to it. - if (this.segments[x].size) - { + if (this.segments[x].size) { currentDegree += this.segments[x].size; - } - else - { + } else { currentDegree += degreesEach; } @@ -328,8 +296,7 @@ Winwheel.prototype.updateSegmentSizes = function() // ==================================================================================================================== Winwheel.prototype.clearCanvas = function() { - if (this.ctx) - { + if (this.ctx) { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } } @@ -340,83 +307,67 @@ Winwheel.prototype.clearCanvas = function() Winwheel.prototype.draw = function(clearTheCanvas) { // If have the canvas context. - if (this.ctx) - { + if (this.ctx) { // Clear the canvas, unless told not to. - if (typeof(clearTheCanvas) !== 'undefined') - { - if (clearTheCanvas == true) - { + if (typeof(clearTheCanvas) !== 'undefined') { + if (clearTheCanvas == true) { this.clearCanvas(); } - } - else - { + } else { this.clearCanvas(); } // Call functions to draw the segments and then segment text. - if (this.drawMode == 'image') - { + if (this.drawMode == 'image') { // Draw the wheel by loading and drawing an image such as a png on the canvas. this.drawWheelImage(); // If we are to draw the text, do so before the overlay is drawn // as this allows the overlay to be used to create some interesting effects. - if (this.drawText == true) - { + if (this.drawText == true) { this.drawSegmentText(); } // If image overlay is true then call function to draw the segments over the top of the image. // This is useful during development to check alignment between where the code thinks the segments are and where they appear on the image. - if (this.imageOverlay == true) - { + if (this.imageOverlay == true) { this.drawSegments(); } - } - else if (this.drawMode == 'segmentImage') - { + } else if (this.drawMode == 'segmentImage') { // Draw the wheel by rendering the image for each segment. this.drawSegmentImages(); // If we are to draw the text, do so before the overlay is drawn // as this allows the overlay to be used to create some interesting effects. - if (this.drawText == true) - { + if (this.drawText == true) { this.drawSegmentText(); } // If image overlay is true then call function to draw the segments over the top of the image. // This is useful during development to check alignment between where the code thinks the segments are and where they appear on the image. - if (this.imageOverlay == true) - { + if (this.imageOverlay == true) { this.drawSegments(); } - } - else - { + } else { // The default operation is to draw the segments using code via the canvas arc() method. this.drawSegments(); // The text is drawn on top. - if (this.drawText == true) - { + if (this.drawText == true) { this.drawSegmentText(); } } // If this class has pins. - if (typeof this.pins !== 'undefined') - { + if (typeof this.pins !== 'undefined') { // If they are to be visible then draw them. - if (this.pins.visible == true) + if (this.pins.visible == true) { this.drawPins(); + } } // If pointer guide is display property is set to true then call function to draw the pointer guide. - if (this.pointerGuide.display == true) - { + if (this.pointerGuide.display == true) { this.drawPointerGuide(); } } @@ -427,16 +378,28 @@ Winwheel.prototype.draw = function(clearTheCanvas) // ==================================================================================================================== Winwheel.prototype.drawPins = function() { - if ((this.pins) && (this.pins.number)) - { + if ((this.pins) && (this.pins.number)) { + // Get scaled centerX and centerY to use in the code below so pins will draw responsively too. + let centerX = (this.centerX * this.scaleFactor); + let centerY = (this.centerY * this.scaleFactor); + let outerRadius = (this.outerRadius * this.scaleFactor); + + // Check if the pin's size is to be responsive too, if so set the pinOuterRadius to a scaled version number. + let pinOuterRadius = this.pins.outerRadius; + let pinMargin = this.pins.margin; + + if (this.pins.responsive) { + pinOuterRadius = (this.pins.outerRadius * this.scaleFactor); + pinMargin = (this.pins.margin * this.scaleFactor); + } + // Work out the angle to draw each pin a which is simply 360 / the number of pins as they space evenly around. //++ There is a slight oddity with the pins in that there is a pin at 0 and also one at 360 and these will be drawn //++ directly over the top of each other. Also pins are 0 indexed which could possibly cause some confusion //++ with the getCurrentPin function - for now this is just used for audio so probably not a problem. - var pinSpacing = (360 / this.pins.number); + let pinSpacing = (360 / this.pins.number); - for(i=1; i<=this.pins.number; i ++) - { + for(let i=1; i<=this.pins.number; i ++) { this.ctx.save(); // Set the stroke style and line width. @@ -445,24 +408,26 @@ Winwheel.prototype.drawPins = function() this.ctx.fillStyle = this.pins.fillStyle; // Move to the center. - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); // Rotate to to the pin location which is i * the pinSpacing. this.ctx.rotate(this.degToRad(i * pinSpacing + this.rotationAngle)); // Move back out. - this.ctx.translate(-this.centerX, -this.centerY); + this.ctx.translate(-centerX, -centerY); // Create a path for the pin circle. this.ctx.beginPath(); // x, y, radius, startAngle, endAngle. - this.ctx.arc(this.centerX,(this.centerY - this.outerRadius) + this.pins.outerRadius + this.pins.margin,this.pins.outerRadius,0,2*Math.PI); + this.ctx.arc(centerX,(centerY - outerRadius) + pinOuterRadius + pinMargin, pinOuterRadius, 0, 2*Math.PI); - if (this.pins.fillStyle) + if (this.pins.fillStyle) { this.ctx.fill(); + } - if (this.pins.strokeStyle) + if (this.pins.strokeStyle) { this.ctx.stroke(); + } this.ctx.restore(); } @@ -475,14 +440,18 @@ Winwheel.prototype.drawPins = function() Winwheel.prototype.drawPointerGuide = function() { // If have canvas context. - if (this.ctx) - { + if (this.ctx) { + // Get scaled center x an y and also the outer radius. + let centerX = (this.centerX * this.scaleFactor); + let centerY = (this.centerY * this.scaleFactor); + let outerRadius = (this.outerRadius * this.scaleFactor); + this.ctx.save(); // Rotate the canvas to the line goes towards the location of the pointer. - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); this.ctx.rotate(this.degToRad(this.pointerAngle)); - this.ctx.translate(-this.centerX, -this.centerY); + this.ctx.translate(-centerX, -centerY); // Set line colour and width. this.ctx.strokeStyle = this.pointerGuide.strokeStyle; @@ -490,8 +459,8 @@ Winwheel.prototype.drawPointerGuide = function() // Draw from the center of the wheel outwards past the wheel outer radius. this.ctx.beginPath(); - this.ctx.moveTo(this.centerX, this.centerY); - this.ctx.lineTo(this.centerX, -(this.outerRadius / 4)); + this.ctx.moveTo(centerX, centerY); + this.ctx.lineTo(centerX, -(outerRadius / 4)); this.ctx.stroke(); this.ctx.restore(); @@ -506,21 +475,29 @@ Winwheel.prototype.drawWheelImage = function() // Double check the wheelImage property of this class is not null. This does not actually detect that an image // source was set and actually loaded so might get error if this is not the case. This is why the initial call // to draw() should be done from a wheelImage.onload callback as detailed in example documentation. - if (this.wheelImage != null) - { + if (this.wheelImage != null) { + // Get the centerX and centerY in to variables, adjust by the scaleFactor. + let centerX = (this.centerX * this.scaleFactor); + let centerY = (this.centerY * this.scaleFactor); + + // Get the scaled width and height of the image. + let scaledWidth = (this.wheelImage.width * this.scaleFactor); + let scaledHeight = (this.wheelImage.height * this.scaleFactor); + // Work out the correct X and Y to draw the image at. We need to get the center point of the image // aligned over the center point of the wheel, we can't just place it at 0, 0. - var imageLeft = (this.centerX - (this.wheelImage.height / 2)); - var imageTop = (this.centerY - (this.wheelImage.width / 2)); + let imageLeft = (centerX - (scaledWidth / 2)); + let imageTop = (centerY - (scaledHeight / 2)); // Rotate and then draw the wheel. // We must rotate by the rotationAngle before drawing to ensure that image wheels will spin. this.ctx.save(); - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); this.ctx.rotate(this.degToRad(this.rotationAngle)); - this.ctx.translate(-this.centerX, -this.centerY); + this.ctx.translate(-centerX, -centerY); - this.ctx.drawImage(this.wheelImage, imageLeft, imageTop); + // Draw the image passing the scaled width and height which will ensure the image will be responsive. + this.ctx.drawImage(this.wheelImage, imageLeft, imageTop, scaledWidth, scaledHeight); this.ctx.restore(); } @@ -532,80 +509,79 @@ Winwheel.prototype.drawWheelImage = function() Winwheel.prototype.drawSegmentImages = function() { // Again check have context in case this function was called directly and not via draw function. - if (this.ctx) - { + if (this.ctx) { + // Get the centerX and centerY of the wheel adjusted with the scale factor. + let centerX = (this.centerX * this.scaleFactor); + let centerY = (this.centerY * this.scaleFactor); + // Draw the segments if there is at least one in the segments array. - if (this.segments) - { + if (this.segments) { // Loop though and output all segments - position 0 of the array is not used, so start loop from index 1 // this is to avoid confusion when talking about the first segment. - for (x = 1; x <= this.numSegments; x ++) - { + for (let x = 1; x <= this.numSegments; x ++) { // Get the segment object as we need it to read options from. - seg = this.segments[x]; + let seg = this.segments[x]; // Check image has loaded so a property such as height has a value. - if (seg.imgData.height) - { + if (seg.imgData.height) { // Work out the correct X and Y to draw the image at which depends on the direction of the image. // Images can be created in 4 directions. North, South, East, West. // North: Outside at top, inside at bottom. Sits evenly over the 0 degrees angle. // South: Outside at bottom, inside at top. Sits evenly over the 180 degrees angle. // East: Outside at right, inside at left. Sits evenly over the 90 degrees angle. // West: Outside at left, inside at right. Sits evenly over the 270 degrees angle. - var imageLeft = 0; - var imageTop = 0; - var imageAngle = 0; - var imageDirection = ''; + let imageLeft = 0; + let imageTop = 0; + let imageAngle = 0; + let imageDirection = ''; + + // Get scaled width and height of the segment image. + let scaledWidth = (seg.imgData.width * this.scaleFactor); + let scaledHeight = (seg.imgData.height * this.scaleFactor); - if (seg.imageDirection !== null) + if (seg.imageDirection !== null) { imageDirection = seg.imageDirection; - else + } else { imageDirection = this.imageDirection; + } - if (imageDirection == 'S') - { + if (imageDirection == 'S') { // Left set so image sits half/half over the 180 degrees point. - imageLeft = (this.centerX - (seg.imgData.width / 2)); + imageLeft = (centerX - (scaledWidth / 2)); // Top so image starts at the centerY. - imageTop = this.centerY; + imageTop = centerY; // Angle to draw the image is its starting angle + half its size. // Here we add 180 to the angle to the segment is poistioned correctly. imageAngle = (seg.startAngle + 180 + ((seg.endAngle - seg.startAngle) / 2)); - } - else if (imageDirection == 'E') - { + } else if (imageDirection == 'E') { // Left set so image starts and the center point. - imageLeft = this.centerX; + imageLeft = centerX; // Top is so that it sits half/half over the 90 degree point. - imageTop = (this.centerY - (seg.imgData.height / 2)); + imageTop = (centerY - (scaledHeight / 2)); // Again get the angle in the center of the segment and add it to the rotation angle. // this time we need to add 270 to that to the segment is rendered the correct place. imageAngle = (seg.startAngle + 270 + ((seg.endAngle - seg.startAngle) / 2)); - } - else if (imageDirection == 'W') - { + } else if (imageDirection == 'W') { // Left is the centerX minus the width of the image. - imageLeft = (this.centerX - seg.imgData.width); + imageLeft = (centerX - scaledWidth); // Top is so that it sits half/half over the 270 degree point. - imageTop = (this.centerY - (seg.imgData.height / 2)); + imageTop = (centerY - (scaledHeight / 2)); // Again get the angle in the center of the segment and add it to the rotation angle. // this time we need to add 90 to that to the segment is rendered the correct place. imageAngle = (seg.startAngle + 90 + ((seg.endAngle - seg.startAngle) / 2)); - } - else // North is the default. - { + } else { + // North is the default. // Left set so image sits half/half over the 0 degrees point. - imageLeft = (this.centerX - (seg.imgData.width / 2)); + imageLeft = (centerX - (scaledWidth / 2)); // Top so image is its height out (above) the center point. - imageTop = (this.centerY - seg.imgData.height); + imageTop = (centerY - scaledHeight); // Angle to draw the image is its starting angle + half its size. // this sits it half/half over the center angle of the segment. @@ -615,19 +591,17 @@ Winwheel.prototype.drawSegmentImages = function() // -------------------------------------------------- // Rotate to the position of the segment and then draw the image. this.ctx.save(); - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); // So math here is the rotation angle of the wheel plus half way between the start and end angle of the segment. this.ctx.rotate(this.degToRad(this.rotationAngle + imageAngle)); - this.ctx.translate(-this.centerX, -this.centerY); + this.ctx.translate(-centerX, -centerY); - // Draw the image. - this.ctx.drawImage(seg.imgData, imageLeft, imageTop); + // Draw the image passing the scaled width and height so that it can be responsive. + this.ctx.drawImage(seg.imgData, imageLeft, imageTop, scaledWidth, scaledHeight); this.ctx.restore(); - } - else - { + } else { console.log('Segment ' + x + ' imgData is not loaded'); } } @@ -641,92 +615,92 @@ Winwheel.prototype.drawSegmentImages = function() Winwheel.prototype.drawSegments = function() { // Again check have context in case this function was called directly and not via draw function. - if (this.ctx) - { + if (this.ctx) { // Draw the segments if there is at least one in the segments array. - if (this.segments) - { + if (this.segments) { + // Get scaled centerX and centerY and also scaled inner and outer radius. + let centerX = (this.centerX * this.scaleFactor); + let centerY = (this.centerY * this.scaleFactor); + let innerRadius = (this.innerRadius * this.scaleFactor); + let outerRadius = (this.outerRadius * this.scaleFactor); + // Loop though and output all segments - position 0 of the array is not used, so start loop from index 1 // this is to avoid confusion when talking about the first segment. - for (x = 1; x <= this.numSegments; x ++) - { + for (let x = 1; x <= this.numSegments; x ++) { // Get the segment object as we need it to read options from. - seg = this.segments[x]; + let seg = this.segments[x]; - var fillStyle; - var lineWidth; - var strokeStyle; + let fillStyle; + let lineWidth; + let strokeStyle; // Set the variables that defined in the segment, or use the default options. - if (seg.fillStyle !== null) + if (seg.fillStyle !== null) { fillStyle = seg.fillStyle; - else + } else { fillStyle = this.fillStyle; + } this.ctx.fillStyle = fillStyle; - if (seg.lineWidth !== null) + if (seg.lineWidth !== null) { lineWidth = seg.lineWidth; - else + } else { lineWidth = this.lineWidth; + } this.ctx.lineWidth = lineWidth; - if (seg.strokeStyle !== null) + if (seg.strokeStyle !== null) { strokeStyle = seg.strokeStyle; - else + } else { strokeStyle = this.strokeStyle; + } this.ctx.strokeStyle = strokeStyle; - // Check there is a strokeStyle or fillStyle, if either the the segment is invisible so should not - // try to draw it otherwise a path is began but not ended. - if ((strokeStyle) || (fillStyle)) - { - // ---------------------------------- + // Check there is a strokeStyle or fillStyle, if not the segment is invisible so should not try to draw it otherwise a path is began but not ended. + if ((strokeStyle) || (fillStyle)) { // Begin a path as the segment consists of an arc and 2 lines. this.ctx.beginPath(); // If don't have an inner radius then move to the center of the wheel as we want a line out from the center // to the start of the arc for the outside of the wheel when we arc. Canvas will draw the connecting line for us. - if (!this.innerRadius) - { - this.ctx.moveTo(this.centerX, this.centerY); - } - else - { - //++ do need to draw the starting line in the correct x + y based on the start angle - //++ otherwise as seen when the wheel does not use up 360 the starting segment is missing the stroked side, + if (!this.innerRadius) { + this.ctx.moveTo(centerX, centerY); + } else { + // Work out the x and y values for the starting point of the segment which is at its starting angle + // but out from the center point of the wheel by the value of the innerRadius. Some correction for line width is needed. + let iX = Math.cos(this.degToRad(seg.startAngle + this.rotationAngle - 90)) * (innerRadius - lineWidth / 2); + let iY = Math.sin(this.degToRad(seg.startAngle + this.rotationAngle - 90)) * (innerRadius - lineWidth / 2); + + // Now move here relative to the center point of the wheel. + this.ctx.moveTo(centerX + iX, centerY + iY); } // Draw the outer arc of the segment clockwise in direction --> - this.ctx.arc(this.centerX, this.centerY, this.outerRadius, this.degToRad(seg.startAngle + this.rotationAngle - 90), this.degToRad(seg.endAngle + this.rotationAngle - 90), false); + this.ctx.arc(centerX, centerY, outerRadius, this.degToRad(seg.startAngle + this.rotationAngle - 90), this.degToRad(seg.endAngle + this.rotationAngle - 90), false); - if (this.innerRadius) - { + if (this.innerRadius) { // Draw another arc, this time anticlockwise <-- at the innerRadius between the end angle and the start angle. // Canvas will draw a connecting line from the end of the outer arc to the beginning of the inner arc completing the shape. - - //++ Think the reason the lines are thinner for 2 of the segments is because the thing auto chops part of it - //++ when doing the next one. Again think that actually drawing the lines will help. - - this.ctx.arc(this.centerX, this.centerY, this.innerRadius, this.degToRad(seg.endAngle + this.rotationAngle - 90), this.degToRad(seg.startAngle + this.rotationAngle - 90), true); - } - else - { + this.ctx.arc(centerX, centerY, innerRadius, this.degToRad(seg.endAngle + this.rotationAngle - 90), this.degToRad(seg.startAngle + this.rotationAngle - 90), true); + } else { // If no inner radius then we draw a line back to the center of the wheel. - this.ctx.lineTo(this.centerX, this.centerY); + this.ctx.lineTo(centerX, centerY); } // Fill and stroke the segment. Only do either if a style was specified, if the style is null then // we assume the developer did not want that particular thing. // For example no stroke style so no lines to be drawn. - if (fillStyle) + if (fillStyle) { this.ctx.fill(); + } - if (strokeStyle) + if (strokeStyle) { this.ctx.stroke(); + } } } } @@ -739,34 +713,37 @@ Winwheel.prototype.drawSegments = function() Winwheel.prototype.drawSegmentText = function() { // Again only draw the text if have a canvas context. - if (this.ctx) - { + if (this.ctx) { // Declare variables to hold the values. These are populated either with the value for the specific segment, // or if not specified then the global default value. - var fontFamily; - var fontSize; - var fontWeight; - var orientation; - var alignment; - var direction; - var margin; - var fillStyle; - var strokeStyle; - var lineWidth; - var fontSetting; + let fontFamily; + let fontSize; + let fontWeight; + let orientation; + let alignment; + let direction; + let margin; + let fillStyle; + let strokeStyle; + let lineWidth; + let fontSetting; + + // Get the centerX and centerY scaled with the scale factor, also the same for outer and inner radius. + let centerX = (this.centerX * this.scaleFactor); + let centerY = (this.centerY * this.scaleFactor); + let outerRadius = (this.outerRadius * this.scaleFactor); + let innerRadius = (this.innerRadius * this.scaleFactor); // Loop though all the segments. - for (x = 1; x <= this.numSegments; x ++) - { + for (let x = 1; x <= this.numSegments; x ++) { // Save the context so it is certain that each segment text option will not affect the other. this.ctx.save(); // Get the segment object as we need it to read options from. - seg = this.segments[x]; + let seg = this.segments[x]; // Check is text as no point trying to draw if there is no text to render. - if (seg.text) - { + if (seg.text) { // Set values to those for the specific segment or use global default if null. if (seg.textFontFamily !== null) fontFamily = seg.textFontFamily; else fontFamily = this.textFontFamily; if (seg.textFontSize !== null) fontSize = seg.textFontSize; else fontSize = this.textFontSize; @@ -779,18 +756,25 @@ Winwheel.prototype.drawSegmentText = function() if (seg.textStrokeStyle !== null) strokeStyle = seg.textStrokeStyle; else strokeStyle = this.textStrokeStyle; if (seg.textLineWidth !== null) lineWidth = seg.textLineWidth; else lineWidth = this.textLineWidth; + // Scale the font size and the margin by the scale factor so the text can be responsive. + fontSize = (fontSize * this.scaleFactor); + margin = (margin * this.scaleFactor); + // ------------------------------ // We need to put the font bits together in to one string. - fontSetting = ''; + let fontSetting = ''; - if (fontWeight != null) + if (fontWeight != null) { fontSetting += fontWeight + ' '; + } - if (fontSize != null) + if (fontSize != null) { fontSetting += fontSize + 'px '; // Fonts on canvas are always a px value. + } - if (fontFamily != null) + if (fontFamily != null) { fontSetting += fontFamily; + } // Now set the canvas context to the decided values. this.ctx.font = fontSetting; @@ -799,210 +783,200 @@ Winwheel.prototype.drawSegmentText = function() this.ctx.lineWidth = lineWidth; // Split the text in to multiple lines on the \n character. - var lines = seg.text.split('\n'); + let lines = seg.text.split('\n'); // Figure out the starting offset for the lines as when there are multiple lines need to center the text // vertically in the segment (when thinking of normal horozontal text). - var lineOffset = 0 - (fontSize * (lines.length / 2)) + (fontSize / 2); + let lineOffset = 0 - (fontSize * (lines.length / 2)) + (fontSize / 2); // The offset works great for horozontal and vertial text, also centered curved. But when the text is curved // and the alignment is outer then the multiline text should not have some text outside the wheel. Same if inner curved. - if ((orientation == 'curved') && ((alignment == 'inner') || (alignment == 'outer'))) - { + if ((orientation == 'curved') && ((alignment == 'inner') || (alignment == 'outer'))) { lineOffset = 0; } - for(i = 0; i < lines.length; i ++) - { - // --------------------------------- + for (let i = 0; i < lines.length; i ++) { // If direction is reversed then do things differently than if normal (which is the default - see further down) - if (direction == 'reversed') - { + if (direction == 'reversed') { // When drawing reversed or 'upside down' we need to do some trickery on our part. // The canvas text rendering function still draws the text left to right and the correct way up, // so we need to overcome this with rotating the opposite side of the wheel the correct way up then pulling the text // through the center point to the correct segment it is supposed to be on. - if (orientation == 'horizontal') - { - if (alignment == 'inner') + if (orientation == 'horizontal') { + if (alignment == 'inner') { this.ctx.textAlign = 'right'; - else if (alignment == 'outer') + } else if (alignment == 'outer') { this.ctx.textAlign = 'left'; - else + } else { this.ctx.textAlign = 'center'; + } this.ctx.textBaseline = 'middle'; // Work out the angle to rotate the wheel, this is in the center of the segment but on the opposite side of the wheel which is why do -180. - var textAngle = this.degToRad((seg.endAngle - ((seg.endAngle - seg.startAngle) / 2) + this.rotationAngle - 90) - 180); + let textAngle = this.degToRad((seg.endAngle - ((seg.endAngle - seg.startAngle) / 2) + this.rotationAngle - 90) - 180); this.ctx.save(); - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); this.ctx.rotate(textAngle); - this.ctx.translate(-this.centerX, -this.centerY); + this.ctx.translate(-centerX, -centerY); - if (alignment == 'inner') - { + if (alignment == 'inner') { // In reversed state the margin is subtracted from the innerX. // When inner the inner radius also comes in to play. - if (fillStyle) - this.ctx.fillText(lines[i], this.centerX - this.innerRadius - margin, this.centerY + lineOffset); + if (fillStyle) { + this.ctx.fillText(lines[i], centerX - innerRadius - margin, centerY + lineOffset); + } - if (strokeStyle) - this.ctx.strokeText(lines[i], this.centerX - this.innerRadius - margin, this.centerY + lineOffset); - } - else if (alignment == 'outer') - { + if (strokeStyle) { + this.ctx.strokeText(lines[i], centerX - innerRadius - margin, centerY + lineOffset); + } + } else if (alignment == 'outer') { // In reversed state the position is the center minus the radius + the margin for outer aligned text. - if (fillStyle) - this.ctx.fillText(lines[i], this.centerX - this.outerRadius + margin, this.centerY + lineOffset); + if (fillStyle) { + this.ctx.fillText(lines[i], centerX - outerRadius + margin, centerY + lineOffset); + } - if (strokeStyle) - this.ctx.strokeText(lines[i], this.centerX - this.outerRadius + margin, this.centerY + lineOffset); - } - else - { + if (strokeStyle) { + this.ctx.strokeText(lines[i], centerX - outerRadius + margin, centerY + lineOffset); + } + } else { // In reversed state the everything in minused. - if (fillStyle) - this.ctx.fillText(lines[i], this.centerX - this.innerRadius - ((this.outerRadius - this.innerRadius) / 2) - margin, this.centerY + lineOffset); + if (fillStyle) { + this.ctx.fillText(lines[i], centerX - innerRadius - ((outerRadius - innerRadius) / 2) - margin, centerY + lineOffset); + } - if (strokeStyle) - this.ctx.strokeText(lines[i], this.centerX - this.innerRadius - ((this.outerRadius - this.innerRadius) / 2) - margin, this.centerY + lineOffset); + if (strokeStyle) { + this.ctx.strokeText(lines[i], centerX - innerRadius - ((outerRadius - innerRadius) / 2) - margin, centerY + lineOffset); + } } this.ctx.restore(); - } - else if (orientation == 'vertical') - { + + } else if (orientation == 'vertical') { // See normal code further down for comments on how it works, this is similar by plus/minus is reversed. this.ctx.textAlign = 'center'; // In reversed mode this are reversed. - if (alignment == 'inner') + if (alignment == 'inner') { this.ctx.textBaseline = 'top'; - else if (alignment == 'outer') + } else if (alignment == 'outer') { this.ctx.textBaseline = 'bottom'; - else + } else { this.ctx.textBaseline = 'middle'; + } - var textAngle = (seg.endAngle - ((seg.endAngle - seg.startAngle) / 2) - 180); + let textAngle = (seg.endAngle - ((seg.endAngle - seg.startAngle) / 2) - 180); textAngle += this.rotationAngle; this.ctx.save(); - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); this.ctx.rotate(this.degToRad(textAngle)); - this.ctx.translate(-this.centerX, -this.centerY); - - if (alignment == 'outer') - var yPos = (this.centerY + this.outerRadius - margin); - else if (alignment == 'inner') - var yPos = (this.centerY + this.innerRadius + margin); + this.ctx.translate(-centerX, -centerY); + + //++ @TODO double-check the default of 0 is correct. + let yPos = 0; + if (alignment == 'outer') { + yPos = (centerY + outerRadius - margin); + } else if (alignment == 'inner') { + yPos = (centerY + innerRadius + margin); + } // I have found that the text looks best when a fraction of the font size is shaved off. - var yInc = (fontSize - (fontSize / 9)); + let yInc = (fontSize - (fontSize / 9)); // Loop though and output the characters. - if (alignment == 'outer') - { + if (alignment == 'outer') { // In reversed mode outer means text in 6 o'clock segment sits at bottom of the wheel and we draw up. - for (var c = (lines[i].length -1); c >= 0; c--) - { - character = lines[i].charAt(c); + for (let c = (lines[i].length -1); c >= 0; c--) { + let character = lines[i].charAt(c); - if (fillStyle) - this.ctx.fillText(character, this.centerX + lineOffset, yPos); + if (fillStyle) { + this.ctx.fillText(character, centerX + lineOffset, yPos); + } - if (strokeStyle) - this.ctx.strokeText(character, this.centerX + lineOffset, yPos); + if (strokeStyle) { + this.ctx.strokeText(character, centerX + lineOffset, yPos); + } yPos -= yInc; } - } - else if (alignment == 'inner') - { + } else if (alignment == 'inner') { // In reversed mode inner text is drawn from top of segment at 6 o'clock position to bottom of the wheel. - for (var c = 0; c < lines[i].length; c++) - { - character = lines[i].charAt(c); + for (let c = 0; c < lines[i].length; c++) { + let character = lines[i].charAt(c); - if (fillStyle) - this.ctx.fillText(character, this.centerX + lineOffset, yPos); + if (fillStyle) { + this.ctx.fillText(character, centerX + lineOffset, yPos); + } - if (strokeStyle) - this.ctx.strokeText(character, this.centerX + lineOffset, yPos); + if (strokeStyle) { + this.ctx.strokeText(character, centerX + lineOffset, yPos); + } yPos += yInc; } - } - else if (alignment == 'center') - { + } else if (alignment == 'center') { // Again for reversed this is the opposite of before. // If there is more than one character in the text then an adjustment to the position needs to be done. // What we are aiming for is to position the center of the text at the center point between the inner and outer radius. - var centerAdjustment = 0; + let centerAdjustment = 0; - if (lines[i].length > 1) - { + if (lines[i].length > 1) { centerAdjustment = (yInc * (lines[i].length -1) / 2); } - var yPos = (this.centerY + this.innerRadius + ((this.outerRadius - this.innerRadius) / 2)) + centerAdjustment + margin; + let yPos = (centerY + innerRadius + ((outerRadius - innerRadius) / 2)) + centerAdjustment + margin; - for (var c = (lines[i].length -1); c >= 0; c--) - { - character = lines[i].charAt(c); + for (let c = (lines[i].length -1); c >= 0; c--) { + let character = lines[i].charAt(c); - if (fillStyle) - this.ctx.fillText(character, this.centerX + lineOffset, yPos); + if (fillStyle) { + this.ctx.fillText(character, centerX + lineOffset, yPos); + } - if (strokeStyle) - this.ctx.strokeText(character, this.centerX + lineOffset, yPos); + if (strokeStyle) { + this.ctx.strokeText(character, centerX + lineOffset, yPos); + } yPos -= yInc; } } this.ctx.restore(); - } - else if (orientation == 'curved') - { + + } else if (orientation == 'curved') { // There is no built in canvas function to draw text around an arc, // so we need to do this ourselves. - var radius = 0; + let radius = 0; // Set the alignment of the text - inner, outer, or center by calculating // how far out from the center point of the wheel the text is drawn. - if (alignment == 'inner') - { + if (alignment == 'inner') { // When alignment is inner the radius is the innerRadius plus any margin. - radius = this.innerRadius + margin; + radius = innerRadius + margin; this.ctx.textBaseline = 'top'; - } - else if (alignment == 'outer') - { + } else if (alignment == 'outer') { // Outer it is the outerRadius minus any margin. - radius = this.outerRadius - margin; + radius = outerRadius - margin; this.ctx.textBaseline = 'bottom'; // We need to adjust the radius in this case to take in to multiline text. // In this case the radius needs to be further out, not at the inner radius. radius -= (fontSize * (lines.length - 1)); - } - else if (alignment == 'center') - { + } else if (alignment == 'center') { // When center we want the text halfway between the inner and outer radius. - radius = this.innerRadius + margin + ((this.outerRadius - this.innerRadius) / 2); + radius = innerRadius + margin + ((outerRadius - innerRadius) / 2); this.ctx.textBaseline = 'middle'; } // Set the angle to increment by when looping though and outputting the characters in the text // as we do this by rotating the wheel small amounts adding each character. - var anglePerChar = 0; - var drawAngle = 0; + let anglePerChar = 0; + let drawAngle = 0; // If more than one character in the text then... - if (lines[i].length > 1) - { + if (lines[i].length > 1) { // Text is drawn from the left. this.ctx.textAlign = 'left'; @@ -1012,7 +986,7 @@ Winwheel.prototype.drawSegmentText = function() anglePerChar = (4 * (fontSize / 10)); // Work out what percentage the radius the text will be drawn at is of 100px. - radiusPercent = (100 / radius); + let radiusPercent = (100 / radius); // Then use this to scale up or down the anglePerChar value. // When the radius is less than 100px we need more angle between the letters, when radius is greater (so the text is further @@ -1022,13 +996,11 @@ Winwheel.prototype.drawSegmentText = function() // Next we want the text to be drawn in the middle of the segment, without this it would start at the beginning of the segment. // To do this we need to work out how much arc the text will take up in total then subtract half of this from the center // of the segment so that it sits centred. - totalArc = (anglePerChar * lines[i].length); + let totalArc = (anglePerChar * lines[i].length); // Now set initial draw angle to half way between the start and end of the segment. drawAngle = seg.startAngle + (((seg.endAngle - seg.startAngle) / 2) - (totalArc / 2)); - } - else - { + } else { // The initial draw angle is the center of the segment when only one character. drawAngle = (seg.startAngle + ((seg.endAngle - seg.startAngle) / 2)); @@ -1047,24 +1019,25 @@ Winwheel.prototype.drawSegmentText = function() // ---------------------- // Now the drawing itself. // In reversed direction mode we loop through the characters in the text backwards in order for them to appear on screen correctly - for (c = lines[i].length; c >= 0; c--) - { + for (let c = lines[i].length; c >= 0; c--) { this.ctx.save(); - character = lines[i].charAt(c); + let character = lines[i].charAt(c); // Rotate the wheel to the draw angle as we need to add the character at this location. - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); this.ctx.rotate(this.degToRad(drawAngle)); - this.ctx.translate(-this.centerX, -this.centerY); + this.ctx.translate(-centerX, -centerY); // Now draw the character directly below the center point of the wheel at the appropriate radius. // Note in the reversed mode we add the radius to the this.centerY instead of subtract. - if (strokeStyle) - this.ctx.strokeText(character, this.centerX, this.centerY + radius + lineOffset); + if (strokeStyle) { + this.ctx.strokeText(character, centerX, centerY + radius + lineOffset); + } - if (fillStyle) - this.ctx.fillText(character, this.centerX, this.centerY + radius + lineOffset); + if (fillStyle) { + this.ctx.fillText(character, centerX, centerY + radius + lineOffset); + } // Increment the drawAngle by the angle per character so next loop we rotate // to the next angle required to draw the character at. @@ -1073,39 +1046,36 @@ Winwheel.prototype.drawSegmentText = function() this.ctx.restore(); } } - } - else - { + } else { // Normal direction so do things normally. // Check text orientation, of horizontal then reasonably straight forward, if vertical then a bit more work to do. - if (orientation == 'horizontal') - { + if (orientation == 'horizontal') { // Based on the text alignment, set the correct value in the context. - if (alignment == 'inner') + if (alignment == 'inner') { this.ctx.textAlign = 'left'; - else if (alignment == 'outer') + } else if (alignment == 'outer') { this.ctx.textAlign = 'right'; - else + } else { this.ctx.textAlign = 'center'; + } // Set this too. this.ctx.textBaseline = 'middle'; // Work out the angle around the wheel to draw the text at, which is simply in the middle of the segment the text is for. // The rotation angle is added in to correct the annoyance with the canvas arc drawing functions which put the 0 degrees at the 3 oclock - var textAngle = this.degToRad(seg.endAngle - ((seg.endAngle - seg.startAngle) / 2) + this.rotationAngle - 90); + let textAngle = this.degToRad(seg.endAngle - ((seg.endAngle - seg.startAngle) / 2) + this.rotationAngle - 90); // We need to rotate in order to draw the text because it is output horizontally, so to // place correctly around the wheel for all but a segment at 3 o'clock we need to rotate. this.ctx.save(); - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); this.ctx.rotate(textAngle); - this.ctx.translate(-this.centerX, -this.centerY); + this.ctx.translate(-centerX, -centerY); // -------------------------- // Draw the text based on its alignment adding margin if inner or outer. - if (alignment == 'inner') - { + if (alignment == 'inner') { // Inner means that the text is aligned with the inner of the wheel. If looking at a segment in in the 3 o'clock position // it would look like the text is left aligned within the segment. @@ -1115,49 +1085,50 @@ Winwheel.prototype.drawSegmentText = function() // The inner radius also needs to be taken in to account as when inner aligned. // If fillstyle is set the draw the text filled in. - if (fillStyle) - this.ctx.fillText(lines[i], this.centerX + this.innerRadius + margin, this.centerY + lineOffset); + if (fillStyle) { + this.ctx.fillText(lines[i], centerX + innerRadius + margin, centerY + lineOffset); + } // If stroke style is set draw the text outline. - if (strokeStyle) - this.ctx.strokeText(lines[i], this.centerX + this.innerRadius + margin, this.centerY + lineOffset); - } - else if (alignment == 'outer') - { + if (strokeStyle) { + this.ctx.strokeText(lines[i], centerX + innerRadius + margin, centerY + lineOffset); + } + } else if (alignment == 'outer') { // Outer means the text is aligned with the outside of the wheel, so if looking at a segment in the 3 o'clock position // it would appear the text is right aligned. To position we add the radius of the wheel in to the equation // and subtract the margin this time, rather than add it. // I don't understand why, but in order of the text to render correctly with stroke and fill, the stroke needs to // come first when drawing outer, rather than second when doing inner. - if (fillStyle) - this.ctx.fillText(lines[i], this.centerX + this.outerRadius - margin, this.centerY + lineOffset); + if (fillStyle) { + this.ctx.fillText(lines[i], centerX + outerRadius - margin, centerY + lineOffset); + } // If fillstyle the fill the text. - if (strokeStyle) - this.ctx.strokeText(lines[i], this.centerX + this.outerRadius - margin, this.centerY + lineOffset); - } - else - { + if (strokeStyle) { + this.ctx.strokeText(lines[i], centerX + outerRadius - margin, centerY + lineOffset); + } + } else { // In this case the text is to drawn centred in the segment. // Typically no margin is required, however even though centred the text can look closer to the inner of the wheel // due to the way the segments narrow in (is optical effect), so if a margin is specified it is placed on the inner // side so the text is pushed towards the outer. // If stoke style the stroke the text. - if (fillStyle) - this.ctx.fillText(lines[i], this.centerX + this.innerRadius + ((this.outerRadius - this.innerRadius) / 2) + margin, this.centerY + lineOffset); + if (fillStyle) { + this.ctx.fillText(lines[i], centerX + innerRadius + ((outerRadius - innerRadius) / 2) + margin, centerY + lineOffset); + } // If fillstyle the fill the text. - if (strokeStyle) - this.ctx.strokeText(lines[i], this.centerX + this.innerRadius + ((this.outerRadius - this.innerRadius) / 2) + margin, this.centerY + lineOffset); + if (strokeStyle) { + this.ctx.strokeText(lines[i], centerX + innerRadius + ((outerRadius - innerRadius) / 2) + margin, centerY + lineOffset); + } } // Restore the context so that wheel is returned to original position. this.ctx.restore(); - } - else if (orientation == 'vertical') - { + + } else if (orientation == 'vertical') { // If vertical then we need to do this ourselves because as far as I am aware there is no option built in to html canvas // which causes the text to draw downwards or upwards one character after another. @@ -1165,15 +1136,16 @@ Winwheel.prototype.drawSegmentText = function() // depending on if inner or outer alignment has been specified. this.ctx.textAlign = 'center'; - if (alignment == 'inner') + if (alignment == 'inner') { this.ctx.textBaseline = 'bottom'; - else if (alignment == 'outer') + } else if (alignment == 'outer') { this.ctx.textBaseline = 'top'; - else + } else { this.ctx.textBaseline = 'middle'; + } // The angle to draw the text at is halfway between the end and the starting angle of the segment. - var textAngle = seg.endAngle - ((seg.endAngle - seg.startAngle) / 2); + let textAngle = seg.endAngle - ((seg.endAngle - seg.startAngle) / 2); // Ensure the rotation angle of the wheel is added in, otherwise the test placement won't match // the segments they are supposed to be for. @@ -1181,59 +1153,60 @@ Winwheel.prototype.drawSegmentText = function() // Rotate so can begin to place the text. this.ctx.save(); - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); this.ctx.rotate(this.degToRad(textAngle)); - this.ctx.translate(-this.centerX, -this.centerY); + this.ctx.translate(-centerX, -centerY); // Work out the position to start drawing in based on the alignment. // If outer then when considering a segment at the 12 o'clock position want to start drawing down from the top of the wheel. - if (alignment == 'outer') - var yPos = (this.centerY - this.outerRadius + margin); - else if (alignment == 'inner') - var yPos = (this.centerY - this.innerRadius - margin); + //++ TODO check this as yPos did not seem to have a defualt before. + let yPos = 0; + + if (alignment == 'outer') { + yPos = (centerY - outerRadius + margin); + } else if (alignment == 'inner') { + yPos = (centerY - innerRadius - margin); + } // We need to know how much to move the y axis each time. // This is not quite simply the font size as that puts a larger gap in between the letters // than expected, especially with monospace fonts. I found that shaving a little off makes it look "right". - var yInc = (fontSize - (fontSize / 9)); + let yInc = (fontSize - (fontSize / 9)); // Loop though and output the characters. - if (alignment == 'outer') - { + if (alignment == 'outer') { // For this alignment we draw down from the top of a segment at the 12 o'clock position to simply // loop though the characters in order. - for (var c = 0; c < lines[i].length; c++) - { - character = lines[i].charAt(c); + for (let c = 0; c < lines[i].length; c++) { + let character = lines[i].charAt(c); - if (fillStyle) - this.ctx.fillText(character, this.centerX + lineOffset, yPos); + if (fillStyle) { + this.ctx.fillText(character, centerX + lineOffset, yPos); + } - if (strokeStyle) - this.ctx.strokeText(character, this.centerX + lineOffset, yPos); + if (strokeStyle) { + this.ctx.strokeText(character, centerX + lineOffset, yPos); + } yPos += yInc; } - } - else if (alignment == 'inner') - { + } else if (alignment == 'inner') { // Here we draw from the inner of the wheel up, but in order for the letters in the text text to // remain in the correct order when reading, we actually need to loop though the text characters backwards. - for (var c = (lines[i].length -1); c >= 0; c--) - { - character = lines[i].charAt(c); + for (let c = (lines[i].length -1); c >= 0; c--) { + let character = lines[i].charAt(c); - if (fillStyle) - this.ctx.fillText(character, this.centerX + lineOffset, yPos); + if (fillStyle) { + this.ctx.fillText(character, centerX + lineOffset, yPos); + } - if (strokeStyle) - this.ctx.strokeText(character, this.centerX + lineOffset, yPos); + if (strokeStyle) { + this.ctx.strokeText(character, centerX + lineOffset, yPos); + } yPos -= yInc; } - } - else if (alignment == 'center') - { + } else if (alignment == 'center') { // This is the most complex of the three as we need to draw the text top down centred between the inner and outer of the wheel. // So logically we have to put the middle character of the text in the center then put the others each side of it. // In reality that is a really bad way to do it, we can achieve the same if not better positioning using a @@ -1241,74 +1214,67 @@ Winwheel.prototype.drawSegmentText = function() // If there is more than one character in the text then an adjustment to the position needs to be done. // What we are aiming for is to position the center of the text at the center point between the inner and outer radius. - var centerAdjustment = 0; + let centerAdjustment = 0; - if (lines[i].length > 1) - { + if (lines[i].length > 1) { centerAdjustment = (yInc * (lines[i].length -1) / 2); } // Now work out where to start rendering the string. This is half way between the inner and outer of the wheel, with the // centerAdjustment included to correctly position texts with more than one character over the center. // If there is a margin it is used to push the text away from the center of the wheel. - var yPos = (this.centerY - this.innerRadius - ((this.outerRadius - this.innerRadius) / 2)) - centerAdjustment - margin; + let yPos = (centerY - innerRadius - ((outerRadius - innerRadius) / 2)) - centerAdjustment - margin; // Now loop and draw just like outer text rendering. - for (var c = 0; c < lines[i].length; c++) - { - character = lines[i].charAt(c); + for (let c = 0; c < lines[i].length; c++) { + let character = lines[i].charAt(c); - if (fillStyle) - this.ctx.fillText(character, this.centerX + lineOffset, yPos); + if (fillStyle) { + this.ctx.fillText(character, centerX + lineOffset, yPos); + } - if (strokeStyle) - this.ctx.strokeText(character, this.centerX + lineOffset, yPos); + if (strokeStyle) { + this.ctx.strokeText(character, centerX + lineOffset, yPos); + } yPos += yInc; } } this.ctx.restore(); - } - else if (orientation == 'curved') - { + + } else if (orientation == 'curved') { // There is no built in canvas function to draw text around an arc, so // we need to do this ourselves. - var radius = 0; + let radius = 0; // Set the alignment of the text - inner, outer, or center by calculating // how far out from the center point of the wheel the text is drawn. - if (alignment == 'inner') - { + if (alignment == 'inner') { // When alignment is inner the radius is the innerRadius plus any margin. - radius = this.innerRadius + margin; + radius = innerRadius + margin; this.ctx.textBaseline = 'bottom'; // We need to adjust the radius in this case to take in to multiline text. // In this case the radius needs to be further out, not at the inner radius. radius += (fontSize * (lines.length - 1)); - } - else if (alignment == 'outer') - { + } else if (alignment == 'outer') { // Outer it is the outerRadius minus any margin. - radius = this.outerRadius - margin; + radius = outerRadius - margin; this.ctx.textBaseline = 'top'; - } - else if (alignment == 'center') - { + } else if (alignment == 'center') { // When center we want the text halfway between the inner and outer radius. - radius = this.innerRadius + margin + ((this.outerRadius - this.innerRadius) / 2); + radius = innerRadius + margin + ((outerRadius - innerRadius) / 2); this.ctx.textBaseline = 'middle'; } // Set the angle to increment by when looping though and outputting the characters in the text // as we do this by rotating the wheel small amounts adding each character. - var anglePerChar = 0; - var drawAngle = 0; + let anglePerChar = 0; + let drawAngle = 0; // If more than one character in the text then... - if (lines[i].length > 1) - { + if (lines[i].length > 1) { // Text is drawn from the left. this.ctx.textAlign = 'left'; @@ -1318,7 +1284,7 @@ Winwheel.prototype.drawSegmentText = function() anglePerChar = (4 * (fontSize / 10)); // Work out what percentage the radius the text will be drawn at is of 100px. - radiusPercent = (100 / radius); + let radiusPercent = (100 / radius); // Then use this to scale up or down the anglePerChar value. // When the radius is less than 100px we need more angle between the letters, when radius is greater (so the text is further @@ -1328,13 +1294,11 @@ Winwheel.prototype.drawSegmentText = function() // Next we want the text to be drawn in the middle of the segment, without this it would start at the beginning of the segment. // To do this we need to work out how much arc the text will take up in total then subtract half of this from the center // of the segment so that it sits centred. - totalArc = (anglePerChar * lines[i].length); + let totalArc = (anglePerChar * lines[i].length); // Now set initial draw angle to half way between the start and end of the segment. drawAngle = seg.startAngle + (((seg.endAngle - seg.startAngle) / 2) - (totalArc / 2)); - } - else - { + } else { // The initial draw angle is the center of the segment when only one character. drawAngle = (seg.startAngle + ((seg.endAngle - seg.startAngle) / 2)); @@ -1349,23 +1313,24 @@ Winwheel.prototype.drawSegmentText = function() // ---------------------- // Now the drawing itself. // Loop for each character in the text. - for (c = 0; c < (lines[i].length); c++) - { + for (let c = 0; c < (lines[i].length); c++) { this.ctx.save(); - character = lines[i].charAt(c); + let character = lines[i].charAt(c); // Rotate the wheel to the draw angle as we need to add the character at this location. - this.ctx.translate(this.centerX, this.centerY); + this.ctx.translate(centerX, centerY); this.ctx.rotate(this.degToRad(drawAngle)); - this.ctx.translate(-this.centerX, -this.centerY); + this.ctx.translate(-centerX, -centerY); // Now draw the character directly above the center point of the wheel at the appropriate radius. - if (strokeStyle) - this.ctx.strokeText(character, this.centerX , this.centerY - radius + lineOffset); + if (strokeStyle) { + this.ctx.strokeText(character, centerX, centerY - radius + lineOffset); + } - if (fillStyle) - this.ctx.fillText(character, this.centerX, this.centerY - radius + lineOffset); + if (fillStyle) { + this.ctx.fillText(character, centerX, centerY - radius + lineOffset); + } // Increment the drawAngle by the angle per character so next loop we rotate // to the next angle required to draw the character at. @@ -1411,27 +1376,23 @@ Winwheel.prototype.setCenter = function(x, y) Winwheel.prototype.addSegment = function(options, position) { // Create a new segment object passing the options in. - newSegment = new Segment(options); + let newSegment = new Segment(options); // Increment the numSegments property of the class since new segment being added. this.numSegments ++; - var segmentPos; + let segmentPos; // Work out where to place the segment, the default is simply as a new segment at the end of the wheel. - if (typeof position !== 'undefined') - { + if (typeof position !== 'undefined') { // Because we need to insert the segment at this position, not overwrite it, we need to move all segments after this // location along one in the segments array, before finally adding this new segment at the specified location. - for (var x = this.numSegments; x > position; x --) - { + for (let x = this.numSegments; x > position; x --) { this.segments[x] = this.segments[x -1]; } this.segments[position] = newSegment; segmentPos = position; - } - else - { + } else { this.segments[this.numSegments] = newSegment; segmentPos = this.numSegments; } @@ -1449,18 +1410,14 @@ Winwheel.prototype.addSegment = function(options, position) // ==================================================================================================================== Winwheel.prototype.setCanvasId = function(canvasId) { - if (canvasId) - { + if (canvasId) { this.canvasId = canvasId; this.canvas = document.getElementById(this.canvasId); - if (this.canvas) - { + if (this.canvas) { this.ctx = this.canvas.getContext('2d'); } - } - else - { + } else { this.canvasId = null this.ctx = null; this.canvas = null; @@ -1477,15 +1434,12 @@ Winwheel.prototype.deleteSegment = function(position) // is more than one segment currently left in the wheel. //++ check that specifying a position that does not exist - say 10 in a 6 segment wheel does not cause issues. - if (this.numSegments > 1) - { + if (this.numSegments > 1) { // If the position of the segment to remove has been specified. - if (typeof position !== 'undefined') - { + if (typeof position !== 'undefined') { // The array is to be shortened so we need to move all segments after the one // to be removed down one so there is no gap. - for (var x = position; x < this.numSegments; x ++) - { + for (let x = position; x < this.numSegments; x ++) { this.segments[x] = this.segments[x + 1]; } } @@ -1506,7 +1460,7 @@ Winwheel.prototype.deleteSegment = function(position) // ==================================================================================================================== Winwheel.prototype.windowToCanvas = function(x, y) { - var bbox = this.canvas.getBoundingClientRect(); + let bbox = this.canvas.getBoundingClientRect(); return { x: Math.floor(x - bbox.left * (this.canvas.width / bbox.width)), @@ -1520,14 +1474,13 @@ Winwheel.prototype.windowToCanvas = function(x, y) // ==================================================================================================================== Winwheel.prototype.getSegmentAt = function(x, y) { - var foundSegment = null; + let foundSegment = null; // Call function to return segment number. - var segmentNumber = this.getSegmentNumberAt(x, y); + let segmentNumber = this.getSegmentNumberAt(x, y); // If found one then set found segment to pointer to the segment object. - if (segmentNumber !== null) - { + if (segmentNumber !== null) { foundSegment = this.segments[segmentNumber]; } @@ -1536,25 +1489,29 @@ Winwheel.prototype.getSegmentAt = function(x, y) // ==================================================================================================================== // Returns the number of the segment clicked instead of the segment object. +// This does not work correctly if the canvas width or height is altered by CSS but does work correctly with the scale factor. // ==================================================================================================================== Winwheel.prototype.getSegmentNumberAt = function(x, y) { - // KNOWN ISSUE: this does not work correct if the canvas is scaled using css, or has padding, border. - // @TODO see if can find a solution at some point, check windowToCanvas working as needed, then below. - // Call function above to convert the raw x and y from the user's browser to canvas coordinates // i.e. top and left is top and left of canvas, not top and left of the user's browser. - var loc = this.windowToCanvas(x, y); + let loc = this.windowToCanvas(x, y); // ------------------------------------------ // Now start the process of working out the segment clicked. // First we need to figure out the angle of an imaginary line between the centerX and centerY of the wheel and // the X and Y of the location (for example a mouse click). - var topBottom; - var leftRight; - var adjacentSideLength; - var oppositeSideLength; - var hypotenuseSideLength; + let topBottom; + let leftRight; + let adjacentSideLength; + let oppositeSideLength; + let hypotenuseSideLength; + + // Get the centerX and centerY scaled with the scale factor, also the same for outer and inner radius. + let centerX = (this.centerX * this.scaleFactor); + let centerY = (this.centerY * this.scaleFactor); + let outerRadius = (this.outerRadius * this.scaleFactor); + let innerRadius = (this.innerRadius * this.scaleFactor); // We will use right triangle maths with the TAN function. // The start of the triangle is the wheel center, the adjacent side is along the x axis, and the opposite side is along the y axis. @@ -1562,34 +1519,28 @@ Winwheel.prototype.getSegmentNumberAt = function(x, y) // We only ever use positive numbers to work out the triangle and the center of the wheel needs to be considered as 0 for the numbers // in the maths which is why there is the subtractions below. We also remember what quadrant of the wheel the location is in as we // need this information later to add 90, 180, 270 degrees to the angle worked out from the triangle to get the position around a 360 degree wheel. - if (loc.x > this.centerX) - { - adjacentSideLength = (loc.x - this.centerX); + if (loc.x > centerX) { + adjacentSideLength = (loc.x - centerX); leftRight = 'R'; // Location is in the right half of the wheel. - } - else - { - adjacentSideLength = (this.centerX - loc.x); + } else { + adjacentSideLength = (centerX - loc.x); leftRight = 'L'; // Location is in the left half of the wheel. } - if (loc.y > this.centerY) - { - oppositeSideLength = (loc.y - this.centerY); + if (loc.y > centerY) { + oppositeSideLength = (loc.y - centerY); topBottom = 'B'; // Bottom half of wheel. - } - else - { - oppositeSideLength = (this.centerY - loc.y); + } else { + oppositeSideLength = (centerY - loc.y); topBottom = 'T'; // Top Half of wheel. } // Now divide opposite by adjacent to get tan value. - var tanVal = oppositeSideLength / adjacentSideLength; + let tanVal = oppositeSideLength / adjacentSideLength; // Use the tan function and convert results to degrees since that is what we work with. - var result = (Math.atan(tanVal) * 180/Math.PI); - var locationAngle = 0; + let result = (Math.atan(tanVal) * 180/Math.PI); + let locationAngle = 0; // We also need the length of the hypotenuse as later on we need to compare this to the outerRadius of the segment / circle. hypotenuseSideLength = Math.sqrt((oppositeSideLength * oppositeSideLength) + (adjacentSideLength * adjacentSideLength)); @@ -1597,36 +1548,27 @@ Winwheel.prototype.getSegmentNumberAt = function(x, y) // ------------------------------------------ // Now to make sense around the wheel we need to alter the values based on if the location was in top or bottom half // and also right or left half of the wheel, by adding 90, 180, 270 etc. Also for some the initial locationAngle needs to be inverted. - if ((topBottom == 'T') && (leftRight == 'R')) - { + if ((topBottom == 'T') && (leftRight == 'R')) { locationAngle = Math.round(90 - result); - } - else if ((topBottom == 'B') && (leftRight == 'R')) - { + } else if ((topBottom == 'B') && (leftRight == 'R')) { locationAngle = Math.round(result + 90); - } - else if ((topBottom == 'B') && (leftRight == 'L')) - { + } else if ((topBottom == 'B') && (leftRight == 'L')) { locationAngle = Math.round((90 - result) + 180); - } - else if ((topBottom == 'T') && (leftRight == 'L')) - { + } else if ((topBottom == 'T') && (leftRight == 'L')) { locationAngle = Math.round(result + 270); } // ------------------------------------------ // And now we have to adjust to make sense when the wheel is rotated from the 0 degrees either // positive or negative and it can be many times past 360 degrees. - if (this.rotationAngle != 0) - { - var rotatedPosition = this.getRotationPosition(); + if (this.rotationAngle != 0) { + let rotatedPosition = this.getRotationPosition(); // So we have this, now we need to alter the locationAngle as a result of this. locationAngle = (locationAngle - rotatedPosition); // If negative then take the location away from 360. - if (locationAngle < 0) - { + if (locationAngle < 0) { locationAngle = (360 - Math.abs(locationAngle)); } } @@ -1635,13 +1577,11 @@ Winwheel.prototype.getSegmentNumberAt = function(x, y) // OK, so after all of that we have the angle of a line between the centerX and centerY of the wheel and // the X and Y of the location on the canvas where the mouse was clicked. Now time to work out the segment // this corresponds to. We can use the segment start and end angles for this. - var foundSegmentNumber = null; + let foundSegmentNumber = null; - for (var x = 1; x <= this.numSegments; x ++) - { + for (let x = 1; x <= this.numSegments; x ++) { // Due to segments sharing start and end angles, if line is clicked will pick earlier segment. - if ((locationAngle >= this.segments[x].startAngle) && (locationAngle <= this.segments[x].endAngle)) - { + if ((locationAngle >= this.segments[x].startAngle) && (locationAngle <= this.segments[x].endAngle)) { // To ensure that a click anywhere on the canvas in the segment direction will not cause a // segment to be matched, as well as the angles, we need to ensure the click was within the radius // of the segment (or circle if no segment radius). @@ -1651,8 +1591,7 @@ Winwheel.prototype.getSegmentNumberAt = function(x, y) // Have to take in to account hollow wheels (doughnuts) so check is greater than innerRadius as // well as less than or equal to the outerRadius of the wheel. - if ((hypotenuseSideLength >= this.innerRadius) && (hypotenuseSideLength <= this.outerRadius)) - { + if ((hypotenuseSideLength >= innerRadius) && (hypotenuseSideLength <= outerRadius)) { foundSegmentNumber = x; break; } @@ -1669,7 +1608,7 @@ Winwheel.prototype.getSegmentNumberAt = function(x, y) Winwheel.prototype.getIndicatedSegment = function() { // Call function below to work this out and return the prizeNumber. - var prizeNumber = this.getIndicatedSegmentNumber(); + let prizeNumber = this.getIndicatedSegmentNumber(); // Then simply return the segment in the segments array at that position. return this.segments[prizeNumber]; @@ -1681,23 +1620,20 @@ Winwheel.prototype.getIndicatedSegment = function() // ==================================================================================================================== Winwheel.prototype.getIndicatedSegmentNumber = function() { - var indicatedPrize = 0; - var rawAngle = this.getRotationPosition(); + let indicatedPrize = 0; + let rawAngle = this.getRotationPosition(); // Now we have the angle of the wheel, but we need to take in to account where the pointer is because // will not always be at the 12 o'clock 0 degrees location. - var relativeAngle = Math.floor(this.pointerAngle - rawAngle); + let relativeAngle = Math.floor(this.pointerAngle - rawAngle); - if (relativeAngle < 0) - { + if (relativeAngle < 0) { relativeAngle = 360 - Math.abs(relativeAngle); } // Now we can work out the prize won by seeing what prize segment startAngle and endAngle the relativeAngle is between. - for (x = 1; x < (this.segments.length); x ++) - { - if ((relativeAngle >= this.segments[x]['startAngle']) && (relativeAngle <= this.segments[x]['endAngle'])) - { + for (let x = 1; x < (this.segments.length); x ++) { + if ((relativeAngle >= this.segments[x]['startAngle']) && (relativeAngle <= this.segments[x]['endAngle'])) { indicatedPrize = x; break; } @@ -1712,30 +1648,26 @@ Winwheel.prototype.getIndicatedSegmentNumber = function() // ==================================================================================================================== Winwheel.prototype.getCurrentPinNumber = function() { - var currentPin = 0; + let currentPin = 0; - if (this.pins) - { - var rawAngle = this.getRotationPosition(); + if (this.pins) { + let rawAngle = this.getRotationPosition(); // Now we have the angle of the wheel, but we need to take in to account where the pointer is because // will not always be at the 12 o'clock 0 degrees location. - var relativeAngle = Math.floor(this.pointerAngle - rawAngle); + let relativeAngle = Math.floor(this.pointerAngle - rawAngle); - if (relativeAngle < 0) - { + if (relativeAngle < 0) { relativeAngle = 360 - Math.abs(relativeAngle); } // Work out the angle of the pins as this is simply 360 / the number of pins as they space evenly around. - var pinSpacing = (360 / this.pins.number); - var totalPinAngle = 0; + let pinSpacing = (360 / this.pins.number); + let totalPinAngle = 0; // Now we can work out the pin by seeing what pins relativeAngle is between. - for (x = 0; x < (this.pins.number); x ++) - { - if ((relativeAngle >= totalPinAngle) && (relativeAngle <= (totalPinAngle + pinSpacing))) - { + for (let x = 0; x < (this.pins.number); x ++) { + if ((relativeAngle >= totalPinAngle) && (relativeAngle <= (totalPinAngle + pinSpacing))) { currentPin = x; break; } @@ -1762,27 +1694,22 @@ Winwheel.prototype.getCurrentPinNumber = function() // ================================================================================================================================================== Winwheel.prototype.getRotationPosition = function() { - var rawAngle = this.rotationAngle; // Get current rotation angle of wheel. + let rawAngle = this.rotationAngle; // Get current rotation angle of wheel. // If positive work out how many times past 360 this is and then take the floor of this off the rawAngle. - if (rawAngle >= 0) - { - if (rawAngle > 360) - { + if (rawAngle >= 0) { + if (rawAngle > 360) { // Get floor of the number of times past 360 degrees. - var timesPast360 = Math.floor(rawAngle / 360); + let timesPast360 = Math.floor(rawAngle / 360); // Take all this extra off to get just the angle 0-360 degrees. rawAngle = (rawAngle - (360 * timesPast360)); } - } - else - { + } else { // Is negative, need to take off the extra then convert in to 0-360 degree value // so if, for example, was -90 then final value will be (360 - 90) = 270 degrees. - if (rawAngle < -360) - { - var timesPast360 = Math.ceil(rawAngle / 360); // Ceil when negative. + if (rawAngle < -360) { + let timesPast360 = Math.ceil(rawAngle / 360); // Ceil when negative. rawAngle = (rawAngle - (360 * timesPast360)); // Is minus because dealing with negative. } @@ -1798,8 +1725,7 @@ Winwheel.prototype.getRotationPosition = function() // ================================================================================================================================================== Winwheel.prototype.startAnimation = function() { - if (this.animation) - { + if (this.animation) { // Call function to compute the animation properties. this.computeAnimation(); @@ -1808,7 +1734,7 @@ Winwheel.prototype.startAnimation = function() winwheelToDrawDuringAnimation = this; // Put together the properties of the greesock animation. - var properties = new Array(null); + let properties = new Array(null); properties[this.animation.propertyName] = this.animation.propertyValue; // Here we set the property to be animated and its value. properties['yoyo'] = this.animation.yoyo; // Set others. properties['repeat'] = this.animation.repeat; @@ -1823,16 +1749,18 @@ Winwheel.prototype.startAnimation = function() } // ================================================================================================================================================== -// Use same function function which needs to be outside the class for the callback when it stops because is finished. +// Use same function which needs to be outside the class for the callback when it stops because is finished. // ================================================================================================================================================== Winwheel.prototype.stopAnimation = function(canCallback) { // @TODO as part of multiwheel, need to work out how to stop the tween for a single wheel but allow others to continue. // We can kill the animation using our tween object. - if (winwheelToDrawDuringAnimation) - { - winwheelToDrawDuringAnimation.tween.kill(); + if (winwheelToDrawDuringAnimation) { + // If the wheel has a tween animation then kill it. + if (winwheelToDrawDuringAnimation.tween) { + winwheelToDrawDuringAnimation.tween.kill(); + } // Call the callback function. winwheelStopAnimation(canCallback); @@ -1847,8 +1775,7 @@ Winwheel.prototype.stopAnimation = function(canCallback) // ================================================================================================================================================== Winwheel.prototype.pauseAnimation = function() { - if (this.tween) - { + if (this.tween) { this.tween.pause(); } } @@ -1858,8 +1785,7 @@ Winwheel.prototype.pauseAnimation = function() // ================================================================================================================================================== Winwheel.prototype.resumeAnimation = function() { - if (this.tween) - { + if (this.tween) { this.tween.play(); } } @@ -1871,31 +1797,25 @@ Winwheel.prototype.resumeAnimation = function() // ==================================================================================================================== Winwheel.prototype.computeAnimation = function() { - if (this.animation) - { + if (this.animation) { // Set the animation parameters for the specified animation type including some sensible defaults if values have not been specified. - if (this.animation.type == 'spinOngoing') - { + if (this.animation.type == 'spinOngoing') { // When spinning the rotationAngle is the wheel property which is animated. this.animation.propertyName = 'rotationAngle'; - if (this.animation.spins == null) - { + if (this.animation.spins == null) { this.animation.spins = 5; } - if (this.animation.repeat == null) - { + if (this.animation.repeat == null) { this.animation.repeat = -1; // -1 means it will repeat forever. } - if (this.animation.easing == null) - { + if (this.animation.easing == null) { this.animation.easing = 'Linear.easeNone'; } - if (this.animation.yoyo == null) - { + if (this.animation.yoyo == null) { this.animation.yoyo = false; } @@ -1903,100 +1823,78 @@ Winwheel.prototype.computeAnimation = function() this.animation.propertyValue = (this.animation.spins * 360); // If the direction is anti-clockwise then make the property value negative. - if (this.animation.direction == 'anti-clockwise') - { + if (this.animation.direction == 'anti-clockwise') { this.animation.propertyValue = (0 - this.animation.propertyValue); } - } - else if (this.animation.type == 'spinToStop') - { + } else if (this.animation.type == 'spinToStop') { // Spin to stop the rotation angle is affected. this.animation.propertyName = 'rotationAngle'; - if (this.animation.spins == null) - { + if (this.animation.spins == null) { this.animation.spins = 5; } - if (this.animation.repeat == null) - { + if (this.animation.repeat == null) { this.animation.repeat = 0; // As this is spin to stop we don't normally want it repeated. } - if (this.animation.easing == null) - { + if (this.animation.easing == null) { this.animation.easing = 'Power3.easeOut'; // This easing is fast start and slows over time. } - if (this.animation.stopAngle == null) - { + if (this.animation.stopAngle == null) { // If the stop angle has not been specified then pick random between 0 and 359. this.animation._stopAngle = Math.floor((Math.random() * 359)); - } - else - { + } else { // We need to set the internal to 360 minus what the user entered because the wheel spins past 0 without // this it would indicate the prize on the opposite side of the wheel. We aslo need to take in to account // the pointerAngle as the stop angle needs to be relative to that. this.animation._stopAngle = (360 - this.animation.stopAngle + this.pointerAngle); } - if (this.animation.yoyo == null) - { + if (this.animation.yoyo == null) { this.animation.yoyo = false; } // The property value is the spins * 360 then plus or minus the stopAngle depending on if the rotation is clockwise or anti-clockwise. this.animation.propertyValue = (this.animation.spins * 360); - if (this.animation.direction == 'anti-clockwise') - { + if (this.animation.direction == 'anti-clockwise') { this.animation.propertyValue = (0 - this.animation.propertyValue); // Also if the value is anti-clockwise we need subtract the stopAngle (but to get the wheel to stop in the correct // place this is 360 minus the stop angle as the wheel is rotating backwards). this.animation.propertyValue -= (360 - this.animation._stopAngle); - } - else - { + } else { // Add the stopAngle to the propertyValue as the wheel must rotate around to this place and stop there. this.animation.propertyValue += this.animation._stopAngle; } - } - else if (this.animation.type == 'spinAndBack') - { + } else if (this.animation.type == 'spinAndBack') { // This is basically is a spin for a number of times then the animation reverses and goes back to start. // If a repeat is specified then this can be used to make the wheel "rock" left and right. // Again this is a spin so the rotationAngle the property which is animated. this.animation.propertyName = 'rotationAngle'; - if (this.animation.spins == null) - { + if (this.animation.spins == null) { this.animation.spins = 5; } - if (this.animation.repeat == null) - { + if (this.animation.repeat == null) { this.animation.repeat = 1; // This needs to be set to at least 1 in order for the animation to reverse. } - if (this.animation.easing == null) - { + if (this.animation.easing == null) { this.animation.easing = 'Power2.easeInOut'; // This is slow at the start and end and fast in the middle. } - if (this.animation.yoyo == null) - { + if (this.animation.yoyo == null) { this.animation.yoyo = true; // This needs to be set to true to have the animation reverse back like a yo-yo. } - if (this.animation.stopAngle == null) - { + if (this.animation.stopAngle == null) { this.animation._stopAngle = 0; - } - else - { + } else { // We need to set the internal to 360 minus what the user entered // because the wheel spins past 0 without this it would indicate the // prize on the opposite side of the wheel. @@ -2006,22 +1904,17 @@ Winwheel.prototype.computeAnimation = function() // The property value is the spins * 360 then plus or minus the stopAngle depending on if the rotation is clockwise or anti-clockwise. this.animation.propertyValue = (this.animation.spins * 360); - if (this.animation.direction == 'anti-clockwise') - { + if (this.animation.direction == 'anti-clockwise') { this.animation.propertyValue = (0 - this.animation.propertyValue); // Also if the value is anti-clockwise we need subtract the stopAngle (but to get the wheel to stop in the correct // place this is 360 minus the stop angle as the wheel is rotating backwards). this.animation.propertyValue -= (360 - this.animation._stopAngle); - } - else - { + } else { // Add the stopAngle to the propertyValue as the wheel must rotate around to this place and stop there. this.animation.propertyValue += this.animation._stopAngle; } - } - else if (this.animation.type == 'custom') - { + } else if (this.animation.type == 'custom') { // Do nothing as all values must be set by the developer in the parameters // especially the propertyName and propertyValue. } @@ -2034,32 +1927,23 @@ Winwheel.prototype.computeAnimation = function() // ==================================================================================================================== Winwheel.prototype.getRandomForSegment = function(segmentNumber) { - var stopAngle = 0; - - if (segmentNumber) - { - if (typeof this.segments[segmentNumber] !== 'undefined') - { - var startAngle = this.segments[segmentNumber].startAngle; - var endAngle = this.segments[segmentNumber].endAngle; - var range = (endAngle - startAngle) - 2; - - if (range > 0) - { + let stopAngle = 0; + + if (segmentNumber) { + if (typeof this.segments[segmentNumber] !== 'undefined') { + let startAngle = this.segments[segmentNumber].startAngle; + let endAngle = this.segments[segmentNumber].endAngle; + let range = (endAngle - startAngle) - 2; + + if (range > 0) { stopAngle = (startAngle + 1 + Math.floor((Math.random() * range))); - } - else - { + } else { console.log('Segment size is too small to safely get random angle inside it'); } - } - else - { + } else { console.log('Segment ' + segmentNumber + ' undefined'); } - } - else - { + } else { console.log('Segment number not specified'); } @@ -2071,7 +1955,7 @@ Winwheel.prototype.getRandomForSegment = function(segmentNumber) // ==================================================================================================================== function Pin(options) { - defaultOptions = { + let defaultOptions = { 'visible' : true, // In future there might be some functionality related to the pins even if they are not displayed. 'number' : 36, // The number of pins. These are evenly distributed around the wheel. 'outerRadius' : 3, // Radius of the pins which determines their size. @@ -2079,25 +1963,23 @@ function Pin(options) 'strokeStyle' : 'black', // Line colour of the pins. 'lineWidth' : 1, // Line width of the pins. 'margin' : 3, // The space between outside edge of the wheel and the pins. + 'responsive' : false, // If set to true the diameter of the pin will resize when the wheel is responsive. }; // Now loop through the default options and create properties of this class set to the value for // the option passed in if a value was, or if not then set the value of the default. - for (var key in defaultOptions) - { - if ((options != null) && (typeof(options[key]) !== 'undefined')) + for (let key in defaultOptions) { + if ((options != null) && (typeof(options[key]) !== 'undefined')) { this[key] = options[key]; - else + } else { this[key] = defaultOptions[key]; + } } // Also loop though the passed in options and add anything specified not part of the class in to it as a property. - if (options != null) - { - for (var key in options) - { - if (typeof(this[key]) === 'undefined') - { + if (options != null) { + for (let key in options) { + if (typeof(this[key]) === 'undefined') { this[key] = options[key]; } } @@ -2110,7 +1992,7 @@ function Pin(options) function Animation(options) { // Most of these options are null because the defaults are different depending on the type of animation. - defaultOptions = { + let defaultOptions = { 'type' : 'spinOngoing', // For now there are only supported types are spinOngoing (continuous), spinToStop, spinAndBack, custom. 'direction' : 'clockwise', // clockwise or anti-clockwise. 'propertyName' : null, // The name of the winning wheel property to be affected by the animation. @@ -2131,21 +2013,18 @@ function Animation(options) // Now loop through the default options and create properties of this class set to the value for // the option passed in if a value was, or if not then set the value of the default. - for (var key in defaultOptions) - { - if ((options != null) && (typeof(options[key]) !== 'undefined')) + for (let key in defaultOptions) { + if ((options != null) && (typeof(options[key]) !== 'undefined')) { this[key] = options[key]; - else + } else { this[key] = defaultOptions[key]; + } } // Also loop though the passed in options and add anything specified not part of the class in to it as a property. - if (options != null) - { - for (var key in options) - { - if (typeof(this[key]) === 'undefined') - { + if (options != null) { + for (let key in options) { + if (typeof(this[key]) === 'undefined') { this[key] = options[key]; } } @@ -2159,7 +2038,7 @@ function Segment(options) { // Define default options for segments, most are null so that the global defaults for the wheel // are used if the values for a particular segment are not specifically set. - defaultOptions = { + let defaultOptions = { 'size' : null, // Leave null for automatic. Valid values are degrees 0-360. Use percentToDegrees function if needed to convert. 'text' : '', // Default is blank. 'fillStyle' : null, // If null for the rest the global default will be used. @@ -2182,22 +2061,19 @@ function Segment(options) // Now loop through the default options and create properties of this class set to the value for // the option passed in if a value was, or if not then set the value of the default. - for (var key in defaultOptions) - { - if ((options != null) && (typeof(options[key]) !== 'undefined')) + for (let key in defaultOptions) { + if ((options != null) && (typeof(options[key]) !== 'undefined')) { this[key] = options[key]; - else + } else { this[key] = defaultOptions[key]; + } } // Also loop though the passed in options and add anything specified not part of the class in to it as a property. // This allows the developer to easily add properties to segments at construction time. - if (options != null) - { - for (var key in options) - { - if (typeof(this[key]) === 'undefined') - { + if (options != null) { + for (let key in options) { + if (typeof(this[key]) === 'undefined') { this[key] = options[key]; } } @@ -2220,8 +2096,7 @@ Segment.prototype.changeImage = function(image, imageDirection) this.imgData = null; // Set direction. - if (imageDirection) - { + if (imageDirection) { this.imageDirection = imageDirection; } @@ -2238,7 +2113,7 @@ Segment.prototype.changeImage = function(image, imageDirection) // ==================================================================================================================== function PointerGuide(options) { - defaultOptions = { + let defaultOptions = { 'display' : false, 'strokeStyle' : 'red', 'lineWidth' : 3 @@ -2246,14 +2121,10 @@ function PointerGuide(options) // Now loop through the default options and create properties of this class set to the value for // the option passed in if a value was, or if not then set the value of the default. - for (var key in defaultOptions) - { - if ((options != null) && (typeof(options[key]) !== 'undefined')) - { + for (let key in defaultOptions) { + if ((options != null) && (typeof(options[key]) !== 'undefined')) { this[key] = options[key]; - } - else - { + } else { this[key] = defaultOptions[key]; } } @@ -2264,11 +2135,10 @@ function PointerGuide(options) // ==================================================================================================================== function winwheelPercentToDegrees(percentValue) { - var degrees = 0; + let degrees = 0; - if ((percentValue > 0) && (percentValue <= 100)) - { - var divider = (percentValue / 100); + if ((percentValue > 0) && (percentValue <= 100)) { + let divider = (percentValue / 100); degrees = (360 * divider); } @@ -2281,27 +2151,21 @@ function winwheelPercentToDegrees(percentValue) // ==================================================================================================================== function winwheelAnimationLoop() { - if (winwheelToDrawDuringAnimation) - { + if (winwheelToDrawDuringAnimation) { // Check if the clearTheCanvas is specified for this animation, if not or it is not false then clear the canvas. - if (winwheelToDrawDuringAnimation.animation.clearTheCanvas != false) - { + if (winwheelToDrawDuringAnimation.animation.clearTheCanvas != false) { winwheelToDrawDuringAnimation.ctx.clearRect(0, 0, winwheelToDrawDuringAnimation.canvas.width, winwheelToDrawDuringAnimation.canvas.height); } - var callbackBefore = winwheelToDrawDuringAnimation.animation.callbackBefore; - var callbackAfter = winwheelToDrawDuringAnimation.animation.callbackAfter; + let callbackBefore = winwheelToDrawDuringAnimation.animation.callbackBefore; + let callbackAfter = winwheelToDrawDuringAnimation.animation.callbackAfter; // If there is a callback function which is supposed to be called before the wheel is drawn then do that. - if (callbackBefore != null) - { + if (callbackBefore != null) { // If the property is a function then call it, otherwise eval the proptery as javascript code. - if (typeof callbackBefore === 'function') - { + if (typeof callbackBefore === 'function') { callbackBefore(); - } - else - { + } else { eval(callbackBefore); } } @@ -2310,23 +2174,18 @@ function winwheelAnimationLoop() winwheelToDrawDuringAnimation.draw(false); // If there is a callback function which is supposed to be called after the wheel has been drawn then do that. - if (callbackAfter != null) - { + if (callbackAfter != null) { // If the property is a function then call it, otherwise eval the proptery as javascript code. - if (typeof callbackAfter === 'function') - { + if (typeof callbackAfter === 'function') { callbackAfter(); - } - else - { + } else { eval(callbackAfter); } } // If there is a sound callback then call a function which figures out if the sound should be triggered // and if so then call the function specified by the developer. - if (winwheelToDrawDuringAnimation.animation.callbackSound) - { + if (winwheelToDrawDuringAnimation.animation.callbackSound) { winwheelTriggerSound(); } } @@ -2339,37 +2198,29 @@ function winwheelAnimationLoop() function winwheelTriggerSound() { // If this property does not exist then add it as a property of the winwheel. - if (winwheelToDrawDuringAnimation.hasOwnProperty('_lastSoundTriggerNumber') == false) - { + if (winwheelToDrawDuringAnimation.hasOwnProperty('_lastSoundTriggerNumber') == false) { winwheelToDrawDuringAnimation._lastSoundTriggerNumber = 0; } - var callbackSound = winwheelToDrawDuringAnimation.animation.callbackSound; - var currentTriggerNumber = 0; + let callbackSound = winwheelToDrawDuringAnimation.animation.callbackSound; + let currentTriggerNumber = 0; // Now figure out if the sound callback should be called depending on the sound trigger type. - if (winwheelToDrawDuringAnimation.animation.soundTrigger == 'pin') - { + if (winwheelToDrawDuringAnimation.animation.soundTrigger == 'pin') { // So for the pin type we need to work out which pin we are between. currentTriggerNumber = winwheelToDrawDuringAnimation.getCurrentPinNumber(); - } - else - { + } else { // Check on the change of segment by working out which segment we are in. // We can utilise the existing getIndiatedSegmentNumber function. currentTriggerNumber = winwheelToDrawDuringAnimation.getIndicatedSegmentNumber(); } // If the current number is not the same as last time then call the sound callback. - if (currentTriggerNumber != winwheelToDrawDuringAnimation._lastSoundTriggerNumber) - { + if (currentTriggerNumber != winwheelToDrawDuringAnimation._lastSoundTriggerNumber) { // If the property is a function then call it, otherwise eval the proptery as javascript code. - if (typeof callbackSound === 'function') - { + if (typeof callbackSound === 'function') { callbackSound(); - } - else - { + } else { eval(callbackSound); } @@ -2381,26 +2232,21 @@ function winwheelTriggerSound() // ==================================================================================================================== // This function is called-back when the greensock animation has finished. // ==================================================================================================================== -var winwheelToDrawDuringAnimation = null; // This global is set by the winwheel class to the wheel object to be re-drawn. +let winwheelToDrawDuringAnimation = null; // This global is set by the winwheel class to the wheel object to be re-drawn. function winwheelStopAnimation(canCallback) { // When the animation is stopped if canCallback is not false then try to call the callback. // false can be passed in to stop the after happening if the animation has been stopped before it ended normally. - if (canCallback != false) - { - var callback = winwheelToDrawDuringAnimation.animation.callbackFinished; + if (canCallback != false) { + let callback = winwheelToDrawDuringAnimation.animation.callbackFinished; - if (callback != null) - { + if (callback != null) { // If the callback is a function then call it, otherwise evaluate the property as javascript code. - if (typeof callback === 'function') - { + if (typeof callback === 'function') { // Pass back the indicated segment as 99% of the time you will want to know this to inform the user of their prize. callback(winwheelToDrawDuringAnimation.getIndicatedSegment()); - } - else - { + } else { eval(callback); } } @@ -2411,33 +2257,84 @@ function winwheelStopAnimation(canCallback) // Called after the image has loaded for each segment. Once all the images are loaded it then calls the draw function // on the wheel to render it. Used in constructor and also when a segment image is changed. // ==================================================================================================================== -var winhweelAlreadyDrawn = false; +let winhweelAlreadyDrawn = false; function winwheelLoadedImage() { // Prevent multiple drawings of the wheel which ocurrs without this check due to timing of function calls. - if (winhweelAlreadyDrawn == false) - { + if (winhweelAlreadyDrawn == false) { // Set to 0. - var winwheelImageLoadCount = 0; + let winwheelImageLoadCount = 0; // Loop though all the segments of the wheel and check if image data loaded, if so increment counter. - for (i = 1; i <= winwheelToDrawDuringAnimation.numSegments; i ++) - { + for (let i = 1; i <= winwheelToDrawDuringAnimation.numSegments; i ++) { // Check the image data object is not null and also that the image has completed loading by checking // that a property of it such as the height has some sort of true value. - if ((winwheelToDrawDuringAnimation.segments[i].imgData != null) && (winwheelToDrawDuringAnimation.segments[i].imgData.height)) - { + if ((winwheelToDrawDuringAnimation.segments[i].imgData != null) && (winwheelToDrawDuringAnimation.segments[i].imgData.height)) { winwheelImageLoadCount ++; } } // If number of images loaded matches the segments then all the images for the wheel are loaded. - if (winwheelImageLoadCount == winwheelToDrawDuringAnimation.numSegments) - { + if (winwheelImageLoadCount == winwheelToDrawDuringAnimation.numSegments) { // Call draw function to render the wheel. winhweelAlreadyDrawn = true; winwheelToDrawDuringAnimation.draw(); } } } + +// ==================================================================================================================== +// Called when the wheel is to resize. This is normally called from a onresize of the window, also called from onload +// so the initial size is correct. Here we must re-size the canvas and work out the scaleFactor for the wheel. +// ==================================================================================================================== +function winwheelResize() +{ + // By default set the margin to 40px, this can be overridden if needed. + // This is to stop the canvas going right to the right edge of the screen and being overlayed by a scrollbar though + // if the canvas is center aligned, half the magin will be applied to each side since the margin actually reduces the width of the canvas. + let margin = 40; + + // If a value has been specified for this then update the margin to it. + if (typeof(winwheelToDrawDuringAnimation._responsiveMargin) !== 'undefined') { + margin = winwheelToDrawDuringAnimation._responsiveMargin; + } + + // Get the current width and also optional min and max width properties. + let width = window.innerWidth - margin; + let minWidth = winwheelToDrawDuringAnimation._responsiveMinWidth; + let minHeight = winwheelToDrawDuringAnimation._responsiveMinHeight; + + // Adjust the width as it cannot be larger than the original size of the wheel and we don't want + // the canvas and wheel inside it to be too small so check the min width. + if (width < minWidth) { + width = minWidth; + } else if (width > winwheelToDrawDuringAnimation._originalCanvasWidth) { + width = winwheelToDrawDuringAnimation._originalCanvasWidth; + } + + // Work out the percent the new width is smaller than the original width. + let percent = (width / winwheelToDrawDuringAnimation._originalCanvasWidth); + + // Set the canvas width to the width to a percentage of the original width. + winwheelToDrawDuringAnimation.canvas.width = (winwheelToDrawDuringAnimation._originalCanvasWidth * percent); + + // Scale the height if we are supposed to but ensure it does not go below the minHeight. + if (winwheelToDrawDuringAnimation._responsiveScaleHeight) { + let height = (winwheelToDrawDuringAnimation._originalCanvasHeight * percent); + + if (height < minHeight) { + height = minHeight; + } else if (height > winwheelToDrawDuringAnimation._originalCanvasHeight) { + height = winwheelToDrawDuringAnimation._originalCanvasHeight; + } + + winwheelToDrawDuringAnimation.canvas.height = height; + } + + // OK so now we have the percent, set the scaleFactor of the wheel to this. + winwheelToDrawDuringAnimation.scaleFactor = percent; + + // Now re-draw the wheel to ensure the changes in size are rendered. + winwheelToDrawDuringAnimation.draw(); +} diff --git a/Winwheel.min.js b/Winwheel.min.js index a59a31e..6db3016 100644 --- a/Winwheel.min.js +++ b/Winwheel.min.js @@ -1,54 +1,54 @@ -function Winwheel(a,b){defaultOptions={canvasId:"canvas",centerX:null,centerY:null,outerRadius:null,innerRadius:0,numSegments:1,drawMode:"code",rotationAngle:0,textFontFamily:"Arial",textFontSize:20,textFontWeight:"bold",textOrientation:"horizontal",textAlignment:"center",textDirection:"normal",textMargin:null,textFillStyle:"black",textStrokeStyle:null,textLineWidth:1,fillStyle:"silver",strokeStyle:"black",lineWidth:1,clearTheCanvas:!0,imageOverlay:!1,drawText:!0,pointerAngle:0,wheelImage:null,imageDirection:"N"}; -for(var c in defaultOptions)this[c]=null!=a&&"undefined"!==typeof a[c]?a[c]:defaultOptions[c];if(null!=a)for(c in a)"undefined"===typeof this[c]&&(this[c]=a[c]);this.canvasId?(this.canvas=document.getElementById(this.canvasId))?(null==this.centerX&&(this.centerX=this.canvas.width/2),null==this.centerY&&(this.centerY=this.canvas.height/2),null==this.outerRadius&&(this.outerRadius=this.canvas.widthb;c--)this.segments[c]=this.segments[c-1];this.segments[b]=newSegment;c=b}else this.segments[this.numSegments]=newSegment,c=this.numSegments;this.updateSegmentSizes();return this.segments[c]}; +Winwheel.prototype.drawPins=function(){if(this.pins&&this.pins.number){var a=this.centerX*this.scaleFactor,c=this.centerY*this.scaleFactor,b=this.outerRadius*this.scaleFactor,d=this.pins.outerRadius,e=this.pins.margin;this.pins.responsive&&(d=this.pins.outerRadius*this.scaleFactor,e=this.pins.margin*this.scaleFactor);for(var t=360/this.pins.number,h=1;h<=this.pins.number;h++)this.ctx.save(),this.ctx.strokeStyle=this.pins.strokeStyle,this.ctx.lineWidth=this.pins.lineWidth,this.ctx.fillStyle=this.pins.fillStyle, +this.ctx.translate(a,c),this.ctx.rotate(this.degToRad(h*t+this.rotationAngle)),this.ctx.translate(-a,-c),this.ctx.beginPath(),this.ctx.arc(a,c-b+d+e,d,0,2*Math.PI),this.pins.fillStyle&&this.ctx.fill(),this.pins.strokeStyle&&this.ctx.stroke(),this.ctx.restore()}}; +Winwheel.prototype.drawPointerGuide=function(){if(this.ctx){var a=this.centerX*this.scaleFactor,c=this.centerY*this.scaleFactor,b=this.outerRadius*this.scaleFactor;this.ctx.save();this.ctx.translate(a,c);this.ctx.rotate(this.degToRad(this.pointerAngle));this.ctx.translate(-a,-c);this.ctx.strokeStyle=this.pointerGuide.strokeStyle;this.ctx.lineWidth=this.pointerGuide.lineWidth;this.ctx.beginPath();this.ctx.moveTo(a,c);this.ctx.lineTo(a,-(b/4));this.ctx.stroke();this.ctx.restore()}}; +Winwheel.prototype.drawWheelImage=function(){if(null!=this.wheelImage){var a=this.centerX*this.scaleFactor,c=this.centerY*this.scaleFactor,b=this.wheelImage.width*this.scaleFactor,d=this.wheelImage.height*this.scaleFactor,e=a-b/2,t=c-d/2;this.ctx.save();this.ctx.translate(a,c);this.ctx.rotate(this.degToRad(this.rotationAngle));this.ctx.translate(-a,-c);this.ctx.drawImage(this.wheelImage,e,t,b,d);this.ctx.restore()}}; +Winwheel.prototype.drawSegmentImages=function(){if(this.ctx){var a=this.centerX*this.scaleFactor,c=this.centerY*this.scaleFactor;if(this.segments)for(var b=1;b<=this.numSegments;b++){var d=this.segments[b];if(d.imgData.height){var e=d.imgData.width*this.scaleFactor,t=d.imgData.height*this.scaleFactor;var h=null!==d.imageDirection?d.imageDirection:this.imageDirection;if("S"==h){h=a-e/2;var n=c;var r=d.startAngle+180+(d.endAngle-d.startAngle)/2}else"E"==h?(h=a,n=c-t/2,r=d.startAngle+270+(d.endAngle- +d.startAngle)/2):"W"==h?(h=a-e,n=c-t/2,r=d.startAngle+90+(d.endAngle-d.startAngle)/2):(h=a-e/2,n=c-t,r=d.startAngle+(d.endAngle-d.startAngle)/2);this.ctx.save();this.ctx.translate(a,c);this.ctx.rotate(this.degToRad(this.rotationAngle+r));this.ctx.translate(-a,-c);this.ctx.drawImage(d.imgData,h,n,e,t);this.ctx.restore()}else console.log("Segment "+b+" imgData is not loaded")}}}; +Winwheel.prototype.drawSegments=function(){if(this.ctx&&this.segments)for(var a=this.centerX*this.scaleFactor,c=this.centerY*this.scaleFactor,b=this.innerRadius*this.scaleFactor,d=this.outerRadius*this.scaleFactor,e=1;e<=this.numSegments;e++){var t=this.segments[e];var h=null!==t.fillStyle?t.fillStyle:this.fillStyle;this.ctx.fillStyle=h;var n=null!==t.lineWidth?t.lineWidth:this.lineWidth;this.ctx.lineWidth=n;var r=null!==t.strokeStyle?t.strokeStyle:this.strokeStyle;if((this.ctx.strokeStyle=r)||h){this.ctx.beginPath(); +if(this.innerRadius){var f=Math.cos(this.degToRad(t.startAngle+this.rotationAngle-90))*(b-n/2);n=Math.sin(this.degToRad(t.startAngle+this.rotationAngle-90))*(b-n/2);this.ctx.moveTo(a+f,c+n)}else this.ctx.moveTo(a,c);this.ctx.arc(a,c,d,this.degToRad(t.startAngle+this.rotationAngle-90),this.degToRad(t.endAngle+this.rotationAngle-90),!1);this.innerRadius?this.ctx.arc(a,c,b,this.degToRad(t.endAngle+this.rotationAngle-90),this.degToRad(t.startAngle+this.rotationAngle-90),!0):this.ctx.lineTo(a,c);h&&this.ctx.fill(); +r&&this.ctx.stroke()}}}; +Winwheel.prototype.drawSegmentText=function(){if(this.ctx)for(var a,c,b,d,e,t,h,n,r,f,l=this.centerX*this.scaleFactor,p=this.centerY*this.scaleFactor,w=this.outerRadius*this.scaleFactor,v=this.innerRadius*this.scaleFactor,y=1;y<=this.numSegments;y++){this.ctx.save();var g=this.segments[y];if(g.text){a=null!==g.textFontFamily?g.textFontFamily:this.textFontFamily;c=null!==g.textFontSize?g.textFontSize:this.textFontSize;b=null!==g.textFontWeight?g.textFontWeight:this.textFontWeight;d=null!==g.textOrientation? +g.textOrientation:this.textOrientation;e=null!==g.textAlignment?g.textAlignment:this.textAlignment;t=null!==g.textDirection?g.textDirection:this.textDirection;h=null!==g.textMargin?g.textMargin:this.textMargin;n=null!==g.textFillStyle?g.textFillStyle:this.textFillStyle;r=null!==g.textStrokeStyle?g.textStrokeStyle:this.textStrokeStyle;f=null!==g.textLineWidth?g.textLineWidth:this.textLineWidth;c*=this.scaleFactor;h*=this.scaleFactor;var m="";null!=b&&(m+=b+" ");null!=c&&(m+=c+"px ");null!=a&&(m+=a); +this.ctx.font=m;this.ctx.fillStyle=n;this.ctx.strokeStyle=r;this.ctx.lineWidth=f;a=g.text.split("\n");b=-(a.length/2*c)+c/2;"curved"!=d||"inner"!=e&&"outer"!=e||(b=0);for(f=0;fc;d--)this.segments[d]=this.segments[d-1];this.segments[c]=b;b=c}else this.segments[this.numSegments]=b,b=this.numSegments;this.updateSegmentSizes();return this.segments[b]}; Winwheel.prototype.setCanvasId=function(a){if(a){if(this.canvasId=a,this.canvas=document.getElementById(this.canvasId))this.ctx=this.canvas.getContext("2d")}else this.canvas=this.ctx=this.canvasId=null};Winwheel.prototype.deleteSegment=function(a){if(1this.centerX){var k=c.x-this.centerX;var e="R"}else k=this.centerX-c.x,e="L";if(c.y>this.centerY){var q=c.y-this.centerY;var f="B"}else q=this.centerY-c.y,f="T";var l=180*Math.atan(q/k)/Math.PI;c=0;k=Math.sqrt(q*q+k*k);"T"==f&&"R"==e?c=Math.round(90-l):"B"==f&&"R"==e?c=Math.round(l+90):"B"==f&&"L"==e?c=Math.round(90-l+180):"T"==f&&"L"==e&&(c=Math.round(l+270));0!=this.rotationAngle&&(e=this.getRotationPosition(), -c-=e,0>c&&(c=360-Math.abs(c)));e=null;for(a=1;a<=this.numSegments;a++)if(c>=this.segments[a].startAngle&&c<=this.segments[a].endAngle&&k>=this.innerRadius&&k<=this.outerRadius){e=a;break}return e};Winwheel.prototype.getIndicatedSegment=function(){var a=this.getIndicatedSegmentNumber();return this.segments[a]}; -Winwheel.prototype.getIndicatedSegmentNumber=function(){var a=0,b=this.getRotationPosition();b=Math.floor(this.pointerAngle-b);0>b&&(b=360-Math.abs(b));for(x=1;x=this.segments[x].startAngle&&b<=this.segments[x].endAngle){a=x;break}return a}; -Winwheel.prototype.getCurrentPinNumber=function(){var a=0;if(this.pins){var b=this.getRotationPosition();b=Math.floor(this.pointerAngle-b);0>b&&(b=360-Math.abs(b));var c=360/this.pins.number,k=0;for(x=0;x=k&&b<=k+c){a=x;break}k+=c}"clockwise"==this.animation.direction&&(a++,a>this.pins.number&&(a=0))}return a}; -Winwheel.prototype.getRotationPosition=function(){var a=this.rotationAngle;if(0<=a){if(360a&&(b=Math.ceil(a/360),a-=360*b),a=360+a;return a}; +Winwheel.prototype.windowToCanvas=function(a,c){var b=this.canvas.getBoundingClientRect();return{x:Math.floor(a-this.canvas.width/b.width*b.left),y:Math.floor(c-this.canvas.height/b.height*b.top)}};Winwheel.prototype.getSegmentAt=function(a,c){var b=null,d=this.getSegmentNumberAt(a,c);null!==d&&(b=this.segments[d]);return b}; +Winwheel.prototype.getSegmentNumberAt=function(a,c){var b=this.windowToCanvas(a,c);var d=this.centerX*this.scaleFactor;var e=this.centerY*this.scaleFactor;var t=this.outerRadius*this.scaleFactor,h=this.innerRadius*this.scaleFactor;if(b.x>d){var n=b.x-d;d="R"}else n=d-b.x,d="L";if(b.y>e){var r=b.y-e;e="B"}else r=e-b.y,e="T";var f=180*Math.atan(r/n)/Math.PI;b=0;n=Math.sqrt(r*r+n*n);"T"==e&&"R"==d?b=Math.round(90-f):"B"==e&&"R"==d?b=Math.round(f+90):"B"==e&&"L"==d?b=Math.round(90-f+180):"T"==e&&"L"== +d&&(b=Math.round(f+270));0!=this.rotationAngle&&(d=this.getRotationPosition(),b-=d,0>b&&(b=360-Math.abs(b)));d=null;for(e=1;e<=this.numSegments;e++)if(b>=this.segments[e].startAngle&&b<=this.segments[e].endAngle&&n>=h&&n<=t){d=e;break}return d};Winwheel.prototype.getIndicatedSegment=function(){var a=this.getIndicatedSegmentNumber();return this.segments[a]}; +Winwheel.prototype.getIndicatedSegmentNumber=function(){var a=0,c=this.getRotationPosition();c=Math.floor(this.pointerAngle-c);0>c&&(c=360-Math.abs(c));for(var b=1;b=this.segments[b].startAngle&&c<=this.segments[b].endAngle){a=b;break}return a}; +Winwheel.prototype.getCurrentPinNumber=function(){var a=0;if(this.pins){var c=this.getRotationPosition();c=Math.floor(this.pointerAngle-c);0>c&&(c=360-Math.abs(c));for(var b=360/this.pins.number,d=0,e=0;e=d&&c<=d+b){a=e;break}d+=b}"clockwise"==this.animation.direction&&(a++,a>this.pins.number&&(a=0))}return a};Winwheel.prototype.getRotationPosition=function(){var a=this.rotationAngle;0<=a?360a&&(a-=360*Math.ceil(a/360)),a=360+a);return a}; Winwheel.prototype.startAnimation=function(){if(this.animation){this.computeAnimation();winwheelToDrawDuringAnimation=this;var a=Array(null);a[this.animation.propertyName]=this.animation.propertyValue;a.yoyo=this.animation.yoyo;a.repeat=this.animation.repeat;a.ease=this.animation.easing;a.onUpdate=winwheelAnimationLoop;a.onComplete=winwheelStopAnimation;this.tween=TweenMax.to(this,this.animation.duration,a)}}; -Winwheel.prototype.stopAnimation=function(a){winwheelToDrawDuringAnimation&&(winwheelToDrawDuringAnimation.tween.kill(),winwheelStopAnimation(a));winwheelToDrawDuringAnimation=this};Winwheel.prototype.pauseAnimation=function(){this.tween&&this.tween.pause()};Winwheel.prototype.resumeAnimation=function(){this.tween&&this.tween.play()}; +Winwheel.prototype.stopAnimation=function(a){winwheelToDrawDuringAnimation&&(winwheelToDrawDuringAnimation.tween&&winwheelToDrawDuringAnimation.tween.kill(),winwheelStopAnimation(a));winwheelToDrawDuringAnimation=this};Winwheel.prototype.pauseAnimation=function(){this.tween&&this.tween.pause()};Winwheel.prototype.resumeAnimation=function(){this.tween&&this.tween.play()}; Winwheel.prototype.computeAnimation=function(){this.animation&&("spinOngoing"==this.animation.type?(this.animation.propertyName="rotationAngle",null==this.animation.spins&&(this.animation.spins=5),null==this.animation.repeat&&(this.animation.repeat=-1),null==this.animation.easing&&(this.animation.easing="Linear.easeNone"),null==this.animation.yoyo&&(this.animation.yoyo=!1),this.animation.propertyValue=360*this.animation.spins,"anti-clockwise"==this.animation.direction&&(this.animation.propertyValue= 0-this.animation.propertyValue)):"spinToStop"==this.animation.type?(this.animation.propertyName="rotationAngle",null==this.animation.spins&&(this.animation.spins=5),null==this.animation.repeat&&(this.animation.repeat=0),null==this.animation.easing&&(this.animation.easing="Power3.easeOut"),this.animation._stopAngle=null==this.animation.stopAngle?Math.floor(359*Math.random()):360-this.animation.stopAngle+this.pointerAngle,null==this.animation.yoyo&&(this.animation.yoyo=!1),this.animation.propertyValue= 360*this.animation.spins,"anti-clockwise"==this.animation.direction?(this.animation.propertyValue=0-this.animation.propertyValue,this.animation.propertyValue-=360-this.animation._stopAngle):this.animation.propertyValue+=this.animation._stopAngle):"spinAndBack"==this.animation.type&&(this.animation.propertyName="rotationAngle",null==this.animation.spins&&(this.animation.spins=5),null==this.animation.repeat&&(this.animation.repeat=1),null==this.animation.easing&&(this.animation.easing="Power2.easeInOut"), null==this.animation.yoyo&&(this.animation.yoyo=!0),this.animation._stopAngle=null==this.animation.stopAngle?0:360-this.animation.stopAngle,this.animation.propertyValue=360*this.animation.spins,"anti-clockwise"==this.animation.direction?(this.animation.propertyValue=0-this.animation.propertyValue,this.animation.propertyValue-=360-this.animation._stopAngle):this.animation.propertyValue+=this.animation._stopAngle))}; -Winwheel.prototype.getRandomForSegment=function(a){var b=0;if(a)if("undefined"!==typeof this.segments[a]){var c=this.segments[a].startAngle;a=this.segments[a].endAngle-c-2;0=a&&(b=a/100*360);return b} -function winwheelAnimationLoop(){if(winwheelToDrawDuringAnimation){0!=winwheelToDrawDuringAnimation.animation.clearTheCanvas&&winwheelToDrawDuringAnimation.ctx.clearRect(0,0,winwheelToDrawDuringAnimation.canvas.width,winwheelToDrawDuringAnimation.canvas.height);var a=winwheelToDrawDuringAnimation.animation.callbackBefore,b=winwheelToDrawDuringAnimation.animation.callbackAfter;null!=a&&("function"===typeof a?a():eval(a));winwheelToDrawDuringAnimation.draw(!1);null!=b&&("function"===typeof b?b():eval(b)); +Winwheel.prototype.getRandomForSegment=function(a){var c=0;if(a)if("undefined"!==typeof this.segments[a]){var b=this.segments[a].startAngle;a=this.segments[a].endAngle-b-2;0=a&&(c=a/100*360);return c} +function winwheelAnimationLoop(){if(winwheelToDrawDuringAnimation){0!=winwheelToDrawDuringAnimation.animation.clearTheCanvas&&winwheelToDrawDuringAnimation.ctx.clearRect(0,0,winwheelToDrawDuringAnimation.canvas.width,winwheelToDrawDuringAnimation.canvas.height);var a=winwheelToDrawDuringAnimation.animation.callbackBefore,c=winwheelToDrawDuringAnimation.animation.callbackAfter;null!=a&&("function"===typeof a?a():eval(a));winwheelToDrawDuringAnimation.draw(!1);null!=c&&("function"===typeof c?c():eval(c)); winwheelToDrawDuringAnimation.animation.callbackSound&&winwheelTriggerSound()}} -function winwheelTriggerSound(){0==winwheelToDrawDuringAnimation.hasOwnProperty("_lastSoundTriggerNumber")&&(winwheelToDrawDuringAnimation._lastSoundTriggerNumber=0);var a=winwheelToDrawDuringAnimation.animation.callbackSound;var b="pin"==winwheelToDrawDuringAnimation.animation.soundTrigger?winwheelToDrawDuringAnimation.getCurrentPinNumber():winwheelToDrawDuringAnimation.getIndicatedSegmentNumber();b!=winwheelToDrawDuringAnimation._lastSoundTriggerNumber&&("function"===typeof a?a():eval(a),winwheelToDrawDuringAnimation._lastSoundTriggerNumber= -b)}var winwheelToDrawDuringAnimation=null;function winwheelStopAnimation(a){0!=a&&(a=winwheelToDrawDuringAnimation.animation.callbackFinished,null!=a&&("function"===typeof a?a(winwheelToDrawDuringAnimation.getIndicatedSegment()):eval(a)))}var winhweelAlreadyDrawn=!1; -function winwheelLoadedImage(){if(0==winhweelAlreadyDrawn){var a=0;for(i=1;i<=winwheelToDrawDuringAnimation.numSegments;i++)null!=winwheelToDrawDuringAnimation.segments[i].imgData&&winwheelToDrawDuringAnimation.segments[i].imgData.height&&a++;a==winwheelToDrawDuringAnimation.numSegments&&(winhweelAlreadyDrawn=!0,winwheelToDrawDuringAnimation.draw())}}; +function winwheelTriggerSound(){0==winwheelToDrawDuringAnimation.hasOwnProperty("_lastSoundTriggerNumber")&&(winwheelToDrawDuringAnimation._lastSoundTriggerNumber=0);var a=winwheelToDrawDuringAnimation.animation.callbackSound;var c="pin"==winwheelToDrawDuringAnimation.animation.soundTrigger?winwheelToDrawDuringAnimation.getCurrentPinNumber():winwheelToDrawDuringAnimation.getIndicatedSegmentNumber();c!=winwheelToDrawDuringAnimation._lastSoundTriggerNumber&&("function"===typeof a?a():eval(a),winwheelToDrawDuringAnimation._lastSoundTriggerNumber= +c)}var winwheelToDrawDuringAnimation=null;function winwheelStopAnimation(a){0!=a&&(a=winwheelToDrawDuringAnimation.animation.callbackFinished,null!=a&&("function"===typeof a?a(winwheelToDrawDuringAnimation.getIndicatedSegment()):eval(a)))}var winhweelAlreadyDrawn=!1; +function winwheelLoadedImage(){if(0==winhweelAlreadyDrawn){for(var a=0,c=1;c<=winwheelToDrawDuringAnimation.numSegments;c++)null!=winwheelToDrawDuringAnimation.segments[c].imgData&&winwheelToDrawDuringAnimation.segments[c].imgData.height&&a++;a==winwheelToDrawDuringAnimation.numSegments&&(winhweelAlreadyDrawn=!0,winwheelToDrawDuringAnimation.draw())}} +function winwheelResize(){var a=40;"undefined"!==typeof winwheelToDrawDuringAnimation._responsiveMargin&&(a=winwheelToDrawDuringAnimation._responsiveMargin);var c=window.innerWidth-a,b=winwheelToDrawDuringAnimation._responsiveMinWidth;a=winwheelToDrawDuringAnimation._responsiveMinHeight;cwinwheelToDrawDuringAnimation._originalCanvasWidth&&(c=winwheelToDrawDuringAnimation._originalCanvasWidth);c/=winwheelToDrawDuringAnimation._originalCanvasWidth;winwheelToDrawDuringAnimation.canvas.width= +winwheelToDrawDuringAnimation._originalCanvasWidth*c;winwheelToDrawDuringAnimation._responsiveScaleHeight&&(b=winwheelToDrawDuringAnimation._originalCanvasHeight*c,bwinwheelToDrawDuringAnimation._originalCanvasHeight&&(b=winwheelToDrawDuringAnimation._originalCanvasHeight),winwheelToDrawDuringAnimation.canvas.height=b);winwheelToDrawDuringAnimation.scaleFactor=c;winwheelToDrawDuringAnimation.draw()}; diff --git a/examples/basic_code_wheel/index.html b/examples/basic_code_wheel/index.html index 5c2a1ad..bf51f05 100644 --- a/examples/basic_code_wheel/index.html +++ b/examples/basic_code_wheel/index.html @@ -74,7 +74,7 @@

Winwheel.js example wheel - basic code wheel

+ + + +
+

Winwheel.js - responsive wheel example

+
+

Here is an example of a responsive Winwheel.js which resizes the canvas and wheel to fit the window / device viewed on.

+
+

+ This can be achieved by setting the responsive parameter on the wheel and optionally a several data-attributes + on the canvas. +

+
+ + +

Sorry, your browser doesn't support canvas. Please try another.

+
+

+

Tap the wheel to spin.

+
+ + + diff --git a/examples/responsive_wheel/main.css b/examples/responsive_wheel/main.css new file mode 100644 index 0000000..56049a1 --- /dev/null +++ b/examples/responsive_wheel/main.css @@ -0,0 +1,18 @@ +body +{ + font-family: arial; +} + +/* Do some css reset on selected elements */ +h1, p +{ + margin: 0; +} + +h1 { + font-size: 1.5em; +} + +p { + font-size: 0.8em; +} diff --git a/examples/two_part_wheel/index.html b/examples/two_part_wheel/index.html index b485f13..6c76bb7 100644 --- a/examples/two_part_wheel/index.html +++ b/examples/two_part_wheel/index.html @@ -41,6 +41,13 @@

Winwheel.js example wheel - 2 part wheel

the same rotation angle as the primary wheel.


+

+ 2019-01-01 Note: Currently Winwheel.js does not support animating multiple independent wheels on the same page, + nor do I really recommend trying to create games made up of multiple wheels. While I do know how to add multiwheel support to Winwheel.js, + experience in helping people to do this over the last year or so has shown that, depending on the options used, the framerate is so bad with + as little as three wheels that any game made does not look good and is almost unusable. Best to stick to single wheel uses of Winwheel.js for now. +

+

Choose a power setting then press the Spin button. You will be alerted to the prize won when the spinning stops.


@@ -78,7 +85,7 @@

Winwheel.js example wheel - 2 part wheel