What is wrong with my approach?
I would like to allow a user to enter a value of 0 to 180 degree to rotate a picture by this value.
If I use the code below it works perfect.
document.getElementById('myCanvas').setAttribute("style", "position:relative;height:1024px;width:100%; transform: rotate(45deg)" );
But, if I use variables like in the following code, no rotation is performed.
var deg = 45;
var rotation = "transform: rotate(" + deg + "deg)";
document.getElementById('myCanvas').setAttribute("style", "position:relative;height:1024px;width:100%; rotation" );
How can I manage my requirement?
Thank you in advance.
Looking at your code, I think your problem is because you have missed some concatenation. Try this:
var deg = 45;
var rotation = "transform: rotate(" + deg + "deg);"; // Note: Extra semicolon in string
var element = document.getElementById('myCanvas')
element.setAttribute("style", "position:relative;height:1024px;width:100%;" + rotation);
Going further
If you want degree to change based on user input, then I suggest adding an input element to the page, waiting for click (or change) events and then updating your picture's styles:
var number_input = document.querySelector("input[type=number]#rotation");
var element // = ...
var myExtraStyles // = ...
// Pass in extra styles, like you width and such as above.
function rotateElement(degrees, extraStyles) {
var rotation = "transform: rotate(" + deg + "deg);";
element.setAttribute("style", extraStyles + rotation);
}
number_input.addEventListener("click", function(){
rotateElement(element.value, myExtraStyles);
})
Related
So, I've built a quick function to change hue values of a target element in JavaScript, and it works mostly fine now, but I do have some more questions that go beyond the initial post's scope. So I'll open a new question and post them here.
Here's the code:
document.getElementById('left').style.filter = "hue-rotate(" + 20 + "deg)";
document.getElementById('right').style.filter = "hue-rotate(" + 20 + "deg)";
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
const filter = e.target.style.filter;
var deg = parseInt(filter.replace(/[^0-9.]/g, ""));
deg += 40;
e.target.style.filter = "hue-rotate(" + deg + "deg)";
if (deg >= 360) {deg -= 360}
console.log(e.target.id + " is " + deg + "deg");
});
});
My main question (1) is that I've coded an if statement to log the current hue value within 360º (the hue-rotate works anyway with values over +360º, but I find it to be clearer this way). However, while the check works perfectly the first time around, it stops working after the function loops once through the 360º (on subsequent loops, it goes beyond 360º).
For clarification, the statement has been positioned after the degree value is already set (and animated) so as to sidestep the quick loop animation that happens when it goes from, say, 340 to 20º (instead of going there directly, it seems to loop back through the whole hue wheel).
Also, (2) the initial hue-rotate states are defined (at the top) within the script because the function does not work otherwise, although both DIVs do have defined CSS values.
That's it! Thanks in advance!
Since the degree value on the element is always set before limiting the degrees to 360, the 2nd time it loops subtracting 360 wont be enough.
style logged value
0 0
360 (-360) 0
720 (-360) 360
etc
To limit the logged value between [0, 360], use the % operator instead
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
const filter = e.target.style.filter;
var deg = parseInt(filter.replace(/[^0-9.]/g, ""));
deg += 40;
e.target.style.filter = "hue-rotate(" + deg + "deg)";
deg %= 360; // deg = deg % 360
console.log(e.target.id + " is " + deg + "deg");
});
});
For (2):
To get the style of the element from css, use getComputedStyle
occurence.addEventListener('click', (e) => {
const filter = getComputedStyle(e.target).filter;
...
});
e.target.style.filter = "hue-rotate(" + deg + "deg)";
above line should be below if condition if (deg >= 360) {deg -= 360}
document.getElementById('left').style.filter = "hue-rotate(" + 20 + "deg)";
document.getElementById('right').style.filter = "hue-rotate(" + 20 + "deg)";
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
const filter = e.target.style.filter;
console.log(filter);
let deg = parseInt(filter.replace(/[^0-9.]/g, ""));
deg += 40;
if (deg >= 360) {
deg -= 360
}
e.target.style.filter = "hue-rotate(" + deg + "deg)";
console.log(e.target.id + " is " + deg + "deg");
});
});
<div id="left">left</div>
<div id="right">right</div>
I have a task to make an SVG rotate in IE9.
I found the FakeSmile library which makes it rotate, but after the whole DOM is ready, which is not the behavior I want. I made an attempt to do it manually with JavaScript and ended with this code:
//init an array with values from 0 to 360 for degrees
var degrees = [];
for(var i = 0; i <= 360; i++) {
degress.push(i);
}
// function to rotate it, after it's fetched from the DOM
var rotate = function() {
var deg = degrees.shift();
element.style.msTransform = "rotate(" + deg + "deg)";
degrees.push(deg);
}
setInterval(rotate, 7);
Though it is working, I am worried if any performance hit will occur. Also if there is a better solution. Any suggestions are welcomed.
A creator function and organized objects would be a good start. Remember you shouldn't pollute the global namespace if you can avoid it.
Also de-bounce request and animation. A request every 7 millisecond is two request per frame on a 60fps screen (the most common) and there is no need to calculate and throw away frames the user never sees.
In my example i use requestAnimationFrame because that will synchronize with the screens refreshrate. On every request i check if the handle is already drawing a frame and if it isn't i schedule a frame drawing.
Notice that you can still set JavaScript variables every 7 millisecond. It's just the DOM that's slows.
EDIT 1 - No requestAnimationFrame in IE9
My mistake about requestAnimationFrame, but de-bounce is still a good idea. With de-bounce, several factors can request a change and it will still only render when relevant.
I have replaced requestAnimationFrame with setTimeout(.... 1000/60) for close to 60 fps animation.
function createRotator(element) {
var rotator;
rotator = {
degrees: 0,
element: element,
eventHandle: false,
rotate: function rotate() {
rotator.degrees = (rotator.degrees + 1) % 360;
if (rotator.eventHandle === false)
rotator.eventHandle = setTimeout(function() {
rotator.element.style.transform = "rotate(" + rotator.degrees + "deg)";
rotator.element.style.msTransform = "rotate(" + rotator.degrees + "deg)";
rotator.eventHandle = false;
}, 1000 / 60);
}
};
return rotator;
}
//TEST
var nodes = 0;
var handle;
handle = setInterval(function() {
nodes++;
if (nodes > 10) {
clearInterval(handle);
}
var testNode = document.body.appendChild(document.createElement("p"));
testNode.innerHTML = "Hello dear World!";
testNode.style.width = "115px";
testNode.style.cssFloat = "left";
testNode.style.marginTop = "100px";
var rotator = createRotator(testNode);
setInterval(rotator.rotate, 3);
}, 1000 / 4);
Yeah, with IE9, you're out of luck on CSS animations. My only suggestion would be a memory optimization
//init a variable to store the current angle
let angle = 0;
// function to rotate it
function rotate() {
angle = (++angle)%360;
element.style.msTransform = "rotate(" + angle+ "deg)";
}
setInterval(rotate, 7);
This design change also lets you change the speed of the rotation on the fly without changing the interval length. All you would change is ++angle to angle + w where w is the angular velocity.
What is also unfortunate is that you can't use requestAnimationFrame instead of an interval. Oh well. It's not the end of the world.
EDIT:
It was bugging me that the function was relying so heavily on global variables. So, here is a slightly "better", though heavier, way of doing it.
/** Takes in an element, an angular velocity, and an interval, and makes the element spin in IE9
PARAMS:
element : Element - The element we are spinning
da : Number - The angular velocity in degrees per interval
interval : Number - The number of milliseconds per interval
RETURNS:
Number - The ID of the interval that is created
**/
function makeRotate(element, da, interval){
// Variable to store angle
let a = 0;
// If da isn't provided, make it 1
da = da || 1;
// If interval isn't provided, make it 7
interval = interval || 7;
// Get the ID and make the interval
let id = window.setInterval(() => {
// Increment the angle by the angular velocity, but wrap around 360
a = (a + da)%360;
// Apply the transform to the element
element.style.msTransform = "rotate(" + a + "deg)";
}, interval);
// Return the ID of the interval
return id;
}
const intervalId = makeRotate(element, 1, 7);
Also, I made sure to return the interval id because it is always handy to be able to cancel those suckers! window.clearInterval(intervalId);
I have an svg country map and I'm trying to achieve the effect when you click on a district and then see an animated scaling relative to selected district, i.e. scaling with transform-origin point in the center of a clicked district. And I would prefer not to use any libraries.
First of all, why here, when we have svg viewBox attribute setted, polygons drawn with getBoundingClientRect() points don't match their actual elements svg, map, e.target?
If we add some coefficients like here, then what viewBoxMapCoefX1 and viewBoxMapCoefX1 we need so the mapPolygon drawn with getBoundingClientRect() points matches actual map element?
When I try to scale map by adding:
// ANIMATION HERE
map.style.transformOrigin = transformOriginX + "px " + transformOriginY + "px";
map.style.transform = "scale(" + scale + ")";
it looks like transformOrigin value is wrong. If I try to change viewBox value by adding:
// ANIMATION HERE
svg.viewBox.baseVal.x = bounding.left;
svg.viewBox.baseVal.y = bounding.top;
svg.viewBox.baseVal.width = bounding.width;
svg.viewBox.baseVal.height = bounding.height;
then how can I make scaling animation with CSS (no SMIL)?
Any help or advice on this is greatly appreciated.
1) You should be using .getBBox() instead of BoundingClientRect to get the coordinates, as this will be based on the SVG itself and not the screen.
3) You are applying the transforms to the group tag, whereas it makes sense for these to be applied to the SVG element itself instead. You may have tried this already, but the transform origin value will all be based on the co-ordinates you're getting previously? So long as the animation works as intended, there is no issue with the animation front from what I can see.
// ANIMATION HERE
var svg = document.querySelector("svg");
svg.style.transformOrigin = '50% 50%';
svg.style.transform = 'scale(4)';
Thanks to #OwenAyres's answer 1 part of question has been solved.
Instead of scaling map straight relative to the selected district center we can find district's center in % insted of px and then move district's center to map's center and then make scale:
// ANIMATION HERE
var transformOriginXPercent = (50 - transformOriginX * 100 / mapBounding.width) * scale;
var transformOriginYPercent = (50 - transformOriginY * 100 / mapBounding.height) * scale;
var scaleText = "scale(" + scale + ")";
var translateText = "translate(" + transformOriginXPercent + "%," + transformOriginYPercent + "%)";
map.style.transformOrigin = "50% 50%";
map.style.transform = translateText + " " + scaleText;
and get expected result.
But there is still a question what coefficients for transformOriginX and transformOriginY are correct to get the result in the following form:
map.style.transformOrigin = (transformOriginX) + "px " + (transformOriginY) + "px";
map.style.transform = "scale(" + scale + ")";
I have recently made a countdown timer for a website like this! It's pretty much a combination of 2 scripts I found online.
The countdown counts down to February 1st and is pretty static. But the 'clockpicture' is supposed to rotate 6 degrees every second (please see the page source). But it turns out the clock picture will start counting/turning from the moment the page is loaded, so if you arrive there between two seconds the picture will we half a second off, compared to the countdown.
Is there any way I can 'connect' the turning of the picture to the changing of the countdown?
Any help is much appreciated!
EDIT
Based on discussion with OP, finally I found the solution. The problem was, how the browsers handle the rotations.
Ok, so, remove that anim gif, and do some animation there. Create a global variable, called var degrees = 0 at the top of your script. You need to incrase that degrees in every tick with 6. If it reach the 360, then reset it to 0.
For some reason, it not works on jsfiddle, but you can check it on my site. Live demo
Then in your tick function:
if (amount < 0) {
expired.push(idx);
}
// date is still good
else {
this.display(cnt, this.format(this.math(amount)));
if (degrees === 360) {
degrees = 0;
}
degrees += 6;
obj = document.getElementById('clock');
if (navigator.userAgent.match("Chrome")) {
obj.style.WebkitTransform = "rotate(" + degrees + "deg)";
} else if (navigator.userAgent.match("Firefox")) {
obj.style.MozTransform = "rotate(" + degrees + "deg)";
} else if (navigator.userAgent.match("MSIE")) {
obj.style.msTransform = "rotate(" + degrees + "deg)";
} else if (navigator.userAgent.match("Opera")) {
obj.style.OTransform = "rotate(" + degrees + "deg)";
} else {
obj.style.transform = "rotate(" + degrees + "deg)";
}
}
I have this function set up
if (window.innerWidth && window.innerHeight) {
var winW = window.innerWidth;
}
var xM = winW/180;
var axis = 0;
$(window).bind('mousemove',function(e){
var xCoord = Math.floor(e.pageX/xM);
axis = 0.6 * Math.sin(xCoord);
var pageCoords = "( " + e.pageX + ", " + e.pageY + ", " + xCoord + " )";
$("span#showme").text(pageCoords);
});
setInterval(function() {
$("#welcome-background").fadeTo(0, 0.4 + axis);
}, 100);
(for additional reference and working visual- http://jsfiddle.net/ySjqh/2/ )
The code works in theory to divide the page evenly into segments from 0-180, then calculates which segment the mouse appears in. Then uses the Math.sin() function to derive how much opacity to apply, based on a padded starting point of 0.4 opacity (jQuery style), and should use the mouse position to determine how much of the remaining 0.6 to apply based on its distance from center, where mouse at center-page should yield full opacity.
What I don't get is why the script behaves this way, rolling through an entire sine wave when I've limited the input to the Math.sin(x) function to 1 < x < 180. If you replace xCoord with axis in the place where I build the jQuery text for #showme, you'll see that it throws negative numbers- which shouldn't be happening! ... so I don't get what the problem/behavior results from!!! Frustrating!!!
Just use:
xCoord = (xCoord * Math.PI) / 180; // Convert value to Radians
and it works..
Sample
http://jsfiddle.net/ySjqh/4/
axis = 0.6 * (1 - Math.abs(e.pageX - winW/2)/(winW/2));
Using the X distance from the center instead of sin.