Do the rotational controller.

This topic, I continue the series of articles about writing all sorts of Goodies for MooTools. Today we are using pure JavaScript will do the rotational knob — control, which is often used in working with sound programs to adjust the volume or balance. Here is something like this:

Sample

As JavaScript is not allowed to rotate the image, we do not like would have done in flash. For the task we need two images:

Elements

The idea is to place the indicator on a substrate and rotate it according to mouse position. To calculate the indicator we will use math. Don't be afraid of the wave equation to solve we don't have, Vspomni a bit of school trigonometry and polar coordinate system.

So, the polar coordinate system defines the position of a point by two quantities: rotation angle of the radius vector relative to the polar axis φ and the length of the radius vector r. For better understanding take a look at the diagram below.

Schema 1

Sorry to visualise a point in this coordinate system the browser is not under power — it works with the Cartesian system. But it's not a problem, because we can easily go from polar to Cartesian system using the formula:

x = r⋅cos(φ)
y = r⋅sin(φ)

Coordinate x and y we substitute in the. CSS left and top properties of the indicator. It should be noted that in the diagram, the y axis pointing upwards. In the browser it the opposite direction. We will take into account, when we change the coordinates of the indicator.

OK, it's settled. But if r is known initially (we know the size of the image), how do we determine the angle φ from the position of the mouse? Again we come to the aid of school trigonometry. If you look at the following diagram, we can see that the vector from the pole to the point of the cursor position with its projection forms a right triangle.

Schema 2

The legs of this triangle are the coordinates of the cursor. From here we can find the tangent of the angle φ as the ratio of opposite over adjacent:

tg(φ) = y / x

The angle φ hence, we find by taking the arctangent of

φ = arctg(tg(φ)) = arctg(y / x)

Of mathematics is, perhaps, everything. Now for the coding. In HTML everything is simple:
<div id="Container">
<div id="Indicator"></div>
</div>

CSS:
#Container
{
position: relative;
background-image: url('./images/rheostat.png');
width: 64px sized;
height: 64px sized;
}

#Indicator
{
position: absolute;
background-image: url('./images/indicator.png');
width: 4px;
height: 4px;
visibility: hidden;
}


JavaScript code is written using the framework MooTools:
var Rheostat = new Class({
Implements: [Events, Options],

// Set optional default.
options: {
radius: 27,
minValue: 0,
maxValue: 100
},

// Constant to convert degrees to radians and Vice versa.
deg2rad: Math.PI / 180
rad2deg: 180 / Math.PI

// Flag indicating that we clamped the left mouse button.
captured: false

// Constructor
initialize: function(container, indicator, options){
this.setOptions(options);
this.indicator = $(indicator);
this.container = $(container);

// Show hidden default indicator.
this.indicator.fade('show');

// Add the mouse events.
this.container.addEvent('mousedown' this.captureMouse.bind(this));
document.addEvents({
'mouseup': this.releaseMouse.bind(this)
});

// the size of the container we need in order to shift the coordinate system indicator
// upper left corner of the container in its center.
var containerSize = this.container.getSize();
var indicatorSize = this.indicator.getSize();
this.offset = {
x: Math.floor(containerSize.x / 2) - Math.floor(indicatorSize.x / 2)
y: Math.floor(containerSize.y / 2) - Math.floor(indicatorSize.y / 2)
};

this.angle = 0;
this.updateIndicatorPosition();
},

// Remember that the control captured the mouse.
captureMouse: function(){
this.captured = true;
},

// Erase flag capture.
releaseMouse: function(){
this.captured = false;
},

// this method is considered to be the angle of the mouse position.
updateAngle: function(e){
if (this.captured)
{
var containerPosition = this.container.getPosition();

// the Sides of our triangle.
// mouseLeft I added 0.1 in order to avoid a possible divide by zero later.
var mouseLeft = e.client.x - this.offset.x - containerPosition.x + 0.1;
var mouseTop = this.offset.y - e.client.y + containerPosition.y;

// Calculation of angle (because of Math.atan() returns the value in radians
// for easier handling with it will make it a degree).
this.angle = Math.atan(mouseTop / mouseLeft) * this.rad2deg;

// because the arc tangent function we can return only values between -90 and +90
// if the cursor is in the left half corner to add 180. Otherwise in the left half of
// the indicator we will never see.
if (mouseLeft < 0)
this.angle += 180;

// Another check to a continuous sequence of values from 0 to 360 degrees.
if (this.angle < 0)
this.angle += 360;

// Counting value in accordance with the specified minimum and maximum values adjustable values.
var value = Math.floor((this.options.maxValue - this.options.minValue) * this.angle / 360 + this.options.minValue);
this.fireEvent('valueChanged', value)
this.updateIndicatorPosition();
}
},

updateIndicatorPosition: function(){
// Converting the angle to radians to transfer it to the Math.cos() and Math.sin().
var radAngle = this.angle * this.deg2rad
var left = this.options.radius * Math.cos(radAngle) + this.offset.x;

// note the " - " sign. We are launching the y-axis Vice-versa.
var top = -this.options.radius * Math.sin(radAngle) + this.offset.y;

// Positioning indicator.
this.indicator.setStyle('left', left);
this.indicator.setStyle('top', top);
}
});

Construct an instance of this controller as:
var rheostat = new Rheostat('Container' 'Indicator');

Use the change event values:
rheostat.addEvent('valueChanged' function(value){
// In value gets the current value of the controlled parameter.

That's kind of all for now. Later I will tell you how to use to adjust the mouse wheel and to limit rotation of the indicator from both sides. Wishes for additional functionality to be accepted.

the UPD. Improved script that resolves many of the shortcomings:
var Rheostat = new Class({
Implements: [Events, Options],

// Set optional default.
options: {
radius: 27,

// the Range of values of the controlled parameter.
minValue: 0,
maxValue: 100,

// Limit rotation angles of the indicator.
minAngle: 50
maxAngle: 310

// shift of the zero of the scale in degrees.
angleOffset: -90,

// Deploy if the scale?
reversed: true
},

// Constant to convert degrees to radians and Vice versa.
deg2rad: Math.PI / 180
rad2deg: 180 / Math.PI

// Flag indicating that we clamped the left mouse button on the control.
captured: false

angle: 0
mouseAngle: 0
oldMouseAngle: 0

// Constructor.
initialize: function(container, indicator, options){
this.setOptions(options);
this.indicator = $(indicator);
this.container = $(container);

// Add the mouse events.
this.container.addEvents({
'mousedown': this.captureMouse.bind(this)
'mousewheel': this.handleWheel.bind(this)
});

document.addEvents({
'mousemove': this.updateAngle.bind(this)
'mouseup': this.releaseMouse.bind(this)
});

// the size of the container we need in order to shift the coordinate system indicator
// upper left corner of the container in its center.
var containerSize = this.container.getSize();
var indicatorSize = this.indicator.getSize();
this.offset = {
x: Math.floor(containerSize.x / 2) - Math.floor(indicatorSize.x / 2)
y: Math.floor(containerSize.y / 2) - Math.floor(indicatorSize.y / 2)
};

// the default Angle in the beginning of the scale.
this.angle = this.options.minAngle + this.options.angleOffset;
this.updateIndicatorPosition();

// Show hidden default indicator.
this.indicator.fade('hide').fade('in');
},

// mouse wheel.
handleWheel: function(e){
// Calculate angle.
var wheelAngle = this.angle + e.wheel;
if ((wheelAngle >= this.options.minAngle) && (wheelAngle <= this.options.maxAngle)){
this.oldMouseAngle = this.mouseAngle = this.angle = wheelAngle;
this.updateIndicatorPosition();
}
},

// Remember that the control captured the mouse.
captureMouse: function(e){
this.captured = true;

// set the indicator in place of a click.
var mouseAngle = this.getMouseAngle(e);
if ((mouseAngle > = this.options.minAngle) && (mouseAngle <= this.options.maxAngle)){
this.oldMouseAngle = this.mouseAngle = this.angle = mouseAngle;
this.updateIndicatorPosition();
}
},

// Erase flag capture.
releaseMouse: function(){
this.captured = false;
},

// this method is considered to be the angle of the mouse position.
var containerPosition = this.container.getPosition();

// the Sides of our triangle.
// mouseLeft I added 0.1 in order to avoid a possible divide by zero later.
var mouseLeft = e.client.x - this.offset.x - containerPosition.x + 0.1;
var mouseTop = this.offset.y - e.client.y + containerPosition.y;

// Calculate the angle of the cursor (because Math.atan() returns the value in radians
// for easier handling with it will make it a degree).
var angle = Math.atan(mouseTop / mouseLeft) * this.rad2deg;

// because the arc tangent function we can return only values between -90 and +90
// if the cursor is in the left half corner to add 180. Otherwise in the left half of
// the indicator we will never see.
if (mouseLeft < 0)
angle += 180;

// Another test to have a continuous sequence of values from 0 to 360 degrees.
if (angle < 0)
angle += 360;

return angle this.options.angleOffset;
},

// Calculate the angle of rotation of the indicator based on direction of movement of the mouse cursor.
updateAngle: function(e){
// the Captured mouse if control?
if (this.captured){
var mouseAngle = this.getMouseAngle(e);

// Increment angle indicator.
var diffAngle = mouseAngle - this.oldMouseAngle;

// Check peresecheniya borders.
if ((this.angle + diffAngle >= this.options.minAngle) && (this.angle + diffAngle <= this.options.maxAngle))
this.angle += diffAngle;

this.oldMouseAngle = this.mouseAngle = mouseAngle;
this.updateIndicatorPosition();
}
},

// Counting value in accordance with the specified minimum and maximum values adjustable values.
updateValue: function(){
var value = Math.floor(
(this.options.maxValue - this.options.minValue + 1) *
(this.angle - this.options.minAngle) /
(this.options.maxAngle - this.options.minAngle)
);

// Generate the event about the changed value.
this.fireEvent('valueChanged' (this.options.reversed) ? this.options.maxValue - value : value);
},

// update the position indicator.
updateIndicatorPosition: function(){
// Converting the angle to radians to transfer it to the Math.cos() and Math.sin().
var radAngle = (this.angle + this.options.angleOffset) * this.deg2rad;
var left = this.options.radius * Math.cos(radAngle) + this.offset.x;

// note the " - " sign. We are launching the y-axis Vice-versa.
var top = -this.options.radius * Math.sin(radAngle) + this.offset.y;

// Positioning indicator.
this.indicator.setStyles({left: left, top: top});

// the Updated value.
this.updateValue();
}
});

an Example with font size adjustment.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Integration of PostgreSQL with MS SQL Server for those who want faster and deeper

Custom database queries in MODx Revolution

Parse URL in Zend Framework 2