ken
ken

Reputation: 179

snap.svg making a perfect "circle pointer"

First off, sorry for the topic name. I can't really explain what I want to make precisely in one sentence.

Then! What I want to make is a circle with a square on its stroke, within which there will be 6 "hoverable" squares.

Here is the test I've made :

var s = Snap(500, 500);

var thecircle = s.circle(250,250,100).attr({fill:'none',stroke:'red','stroke-width':'2'});
var pointer = s.rect(240,340,20,20);
var william = s.g(thecircle, pointer);

var hover1 = s.rect(240,290,20,20).attr({'value':'0'}).addClass('hovering');
var hover2 = hover1.clone().transform('r60,250,250').attr({'value':'60'}).addClass('hovering');
var hover3 = hover1.clone().transform('r120,250,250').attr({'value':'120'}).addClass('hovering');
var hover4 = hover1.clone().transform('r180,250,250').attr({'value':'180'}).addClass('hovering');
var hover5 = hover1.clone().transform('r240,250,250').attr({'value':'240'}).addClass('hovering');
var hover6 = hover1.clone().transform('r300,250,250').attr({'value':'300'}).addClass('hovering');

var $ = jQuery;

$('.hovering').mouseenter(function(){
  var rotate = $(this).attr('value');
  william.animate({transform:'r'+rotate+',250,250'},300,mina.ease);
});

And here is a fiddle so that you can check how the animation looks : JSfiddle

So now, see what happens when you hover the 6th square (300 degrees) and then hover the 2nd one (60 degrees). The pointer will travel all the way in front of the 5th, 4th and 3rd before reaching the 2nd (a travel of 240 degrees).

I would like for my pointer to go the fastest route to its destination, in this example, it would be 420 degrees. But I have no idea on how I could make it behave that way as I'm not really good at maths...

Upvotes: 2

Views: 498

Answers (2)

Paul LeBeau
Paul LeBeau

Reputation: 101820

You just need to work out which of the two possible directions has the shortest distance. Then you add the diff that's smallest to an accumulated rotation value.

Demo

var s = Snap(500, 500);

var thecircle = s.circle(250,250,100).attr({fill:'none',stroke:'red','stroke-width':'2'});
var pointer = s.rect(240,340,20,20);
var william = s.g(thecircle, pointer);

var hover1 = s.rect(240,290,20,20).attr({'value':'0'}).addClass('hovering');
var hover2 = hover1.clone().transform('r60,250,250').attr({'value':'60'}).addClass('hovering');
var hover3 = hover1.clone().transform('r120,250,250').attr({'value':'120'}).addClass('hovering');
var hover4 = hover1.clone().transform('r180,250,250').attr({'value':'180'}).addClass('hovering');
var hover5 = hover1.clone().transform('r240,250,250').attr({'value':'240'}).addClass('hovering');
var hover6 = hover1.clone().transform('r300,250,250').attr({'value':'300'}).addClass('hovering');

var $ = jQuery;
// What rotate currently is
var lastRotate = 0;
// Actual transform rotate accumulates up or down depending
// on which direction we have been going in.
var accumulatedRotation = 0;

$('.hovering').mouseenter(function(){
  // Make sure 'rotate' is a number not a string
  var rotate = parseInt($(this).attr('value'), 10);
  // rotateAlt is the alternative version of rotate (either >360 or <0)
  var rotateAlt = (lastRotate < 180) ? (rotate - 360) : (360 + rotate); 
  // Work out the diff value for each alt
  var diffA = rotate - lastRotate;
  var diffB = rotateAlt - lastRotate;
  // Add the smaller diff to the accumulated rotation
  if (Math.abs(diffA) < Math.abs(diffB))
    accumulatedRotation += diffA;
  else
    accumulatedRotation += diffB;
  william.animate({transform:'r'+accumulatedRotation+',250,250'},300,mina.ease);
  // Remember the last value of 'rotate'
  lastRotate = rotate;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.4.1/snap.svg.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Upvotes: 3

Mi-Creativity
Mi-Creativity

Reputation: 9654

UPDATED

To fix this, First we need to create a variable prevRotate -initially set to 0- to record the value of the pointer's previous location, then we need to take care of four special cases(1) as well as the normal case like below:

  1. When the pointer was previously on the bottom square ( rotate value = 0), and the mouse hovers a square with rotation value more than 180, we need to move the pointer counter-clockwise by -1 * (360 - rotate). then using a callback function we set the rotation degree immediately to original rotate value using the .animate() function with animation time of 0.

  2. When the pointer was originally on a square with rotate value higher than 180, and the hover over the bottom square which has rotate value of 0, animate the pointer to 360 CW direction and then with the use of our callback function we reset it to 0.

  3. When pointer was previously on the square that has rotate value of 300, and the mouses hovers the square with rotate value of 60, we need to draw the short route by rotating from 300 to 420 (300 + (360-300) + 60), then using the callback function to reset the rotate value to 60.

  4. When the pointer was previously on square with rotate value of 60 and we need to animate it to square with rotate of 300, we need to animate from 60 to -60 and then immediately set the rotation to 300 using the callback function again.

Finally we need to update the value of prevRotate for further comparisons.

JS Fiddle 1 - updated 2

var $ = jQuery,
    prevRotate = 0; // The variable used to store the previous location of the pointer.

$('.hovering').mouseenter(function(){
    var rotate = $(this).attr('value');

    // Special case 1, moving the pointer CCW following the short route.
    if( prevRotate == 0){
        var tempR = rotate;
        rotate = rotate > 180 ? -1 * (360 - rotate) : rotate;
        william.animate({transform:'r' +rotate+ ',250,250'},300,mina.ease, function(){
            myCB(tempR);
        });   
    }else{

        // Normal case.
        william.animate({transform:'r'+ rotate +',250,250'},300,mina.ease);
    }

    if(rotate == 0){

        // Special Case 2, animating to 360 instead of 0, then to 0 immediately.
        if(prevRotate > 180){
            william.animate({transform:'r360,250,250'},300,mina.ease,function(){
                myCB(0);
            }); 
        }

    }else if(rotate == 60 && prevRotate == 300){

        // Special Case 3, animating from 300 to 60 following the short route.
        william.animate({transform:'r420,250,250'},300,mina.ease, function(){
            myCB(60);
        });          
    }else if(rotate == 300 && prevRotate == 60){

        // Special Case 4, animating from 60 to 30 following short route.
        william.animate({transform:'r-60,250,250'},300,mina.ease, function(){
            myCB(300);
        });
    }

    // Update the value of the pointer's previous location
    prevRotate = $(this).attr('value');        
});

// The Callback function to reset rotate values.
function myCB(theAngle){
    william.animate({transform:'r' +theAngle+ ',250,250'},0);
}

(1) Note that you can merge special cases 3 and 4 code blocks into one like this:

JS Fiddle 2 - Updated

if((rotate == 60 || rotate == 300) && prevRotate == 360 - rotate){

    // Define the destination angle depending on value of rotate
    var newR = (rotate == 60) ? 420 : -60;
    william.animate({transform:'r' +newR+ ',250,250'},300,mina.ease,function(){
        myCB(prevRotate);
    }); 
}

Upvotes: 2

Related Questions