I have a small test page setup to test a sprite sheet I have. Each sprite in sprites.css looks like this...
.a320_0 {
top: 0px;
left: 0px;
width: 60px;
height: 64px;
background: url("images/sprites.png") no-repeat -787px -398px;
}
My page looks like this...
var i = 0
function imageChange() {
setTimeout(function() {
var hdg = i * 15;
document.getElementById('image').className = "a320_" + hdg;
i++;
if (i < 24) {
imageChange();
}
}, 1000)
}
imageChange()
<div id='imageContainer'>
<div id='image'></div>
</div>
I'm logging the class name during the loop and can see it changing and the names correspond to classes that exist in my style sheet. sprites.css and sprites.png are both in the images folder and the images folder is in the same directory as my page.
If I just copy one of the rules from my style sheet and put it directly onto my page and replace the name with #image for testing purposes I can display that particular image so my sprite coordinates are fine but if I do the same thing on my actual css file I don't get the same result leading me to believe that my style sheet might not be loading. Even if I just put the styles directly into my document and try to use .className = , it still doesn't work. I had this working recently but it doesn't seem to be working anymore. I'm kind of lost here...
It should be document.getElementById("image").classList.add("a320_" + hdg);.
var i = 0;
function imageChange() {
setTimeout(function() {
var hdg = i * 15;
document.getElementById("image").classList.add("a320_" + hdg);
i++;
if (i < 24) {
imageChange();
}
}, 1000);
}
imageChange();
.a320_0 {
top: 0px;
left: 0px;
width: 60px;
height: 64px;
background: url("https://source.unsplash.com/random") no-repeat -787px -398px;
}
<div id='imageContainer'>
<div id='image'></div>
</div>
Codepen
While your script is correct, you seem to have made a major syntax error which is not giving you the desired results.
When adding class name to an HTML element using JavaScript, the syntax is as follows:
selector.property.action("property value");
for example your code should be:
document.getElementById('image').classList.add("a320_" + hdg);
So the correction needs to be done only in the javascript part:
var i = 0
function imageChange() {
setTimeout(function() {
var hdg = i * 15;
document.getElementById('image').classList.add("a320_" + hdg)
i++;
if (i < 24) {
imageChange();
}
}, 1000)
}
imageChange()
Hope this was helpful!
Related
I'm quite new to JS.
I want to have my html page stay the same when JS text will be appearing in one exact place without starting from blank page.
I trigger JS function via button on HTML, function in HTML:
function match () {
setTimeout(function () {
player_hp -= monster_dmg
monster_hp -= player_dmg
if (player_hp<=0) {
document.write("\nPlayer dies!")
menu();
return;
}
if (monster_hp<=0) {
document.write("\nPlayer wins!")
menu();
return;
}
if (fight=1) {
document.write("\nPlayer hp:" + player_hp)
document.write("\nMonster hp:" + monster_hp)
document.write("\n");
match()
}
}, interval)
}
One easy way to handle this is to simply create a <div> or a <span> element that has an ID attribute like this:
<div id="status"> </div>
Now you can access this element by using the Javascript method
document.querySelector("#status") and then use the innerHTML function of that element to change the internal content. You can even place the document.querySelector function into a convenient function which I have named send_status()
Here's the whole thing
/* default values */
var player_hp = 300;
var monster_dmg = 30;
var monster_hp = 200;
var interval = 500;
var player_dmg = 50;
match();
/* heres a function that will replace your document.write() functions */
function send_status(message) {
document.querySelector("#status").innerHTML = message;
}
function match() {
setTimeout(function() {
player_hp -= monster_dmg
monster_hp -= player_dmg
if (player_hp <= 0) {
send_status("\nPlayer dies!") // replaced document.write with send_status
menu();
return;
}
if (monster_hp <= 0) {
send_status("\nPlayer wins!")
menu();
return;
}
if (fight = 1) {
send_status("\nPlayer hp:" + player_hp)
send_status("\nMonster hp:" + monster_hp)
send_status("\n");
match()
}
}, interval)
}
function menu() {}
#game {
width: 100%;
height: 100px;
border: 2px solid black;
}
#status {
width: 100%;
height: 50px;
background-color: grey;
}
<div id="game">Game Goes Here</div>
<!-- here is your status area -->
<div id="status"></div>
You should create a results div, in which will be shown the match result.
Just add <div id="match_results"></div> in your HTML code.
And replace all yours document.write() for
document.getElementById('match_results').innerHTML += "<br>Player wins!"
This command is appending content in the element with ID match_results.
You should use <br> instead of \n because it is the proper way to break line in HTML code.
I am trying to set interval to a function that is only called when user scrolls below a certain height. My code return no errors and the function does not run either. However, I tried logging a random number at the end of the function and it doesn't so I think it has to do with my function. Take a look:
var firstString = ["This ", "is ", " me."];
var firstPara = document.querySelector("#firstPara");
var distanceSoFar = (document.body.scrollTop);
window.addEventListener("scroll", function() {
setInterval(slideIn, 450);
});
function slideIn() {
if (distanceSoFar > "130") {
for (var i = 0; i < firstString.length; i++) {
var stringOut = firstString.shift();
firstPara.innerHTML += stringOut;
console.log("5");
}
}
};
firstPara is just a paragraph in a div on the page. So the idea is to place some text in it on interval when a user scrolls into that view like so:
body {
height: 1000px;
}
div {
position: relative;
top: 700px;
}
div #firstPara {
border: 1px solid;
}
Part of your code is working. It handles the scroll event correctly and the slideIn function is called but the condition distanceSoFar > "130" is never met.
I'd suggest two changes to make your code work as you expect:
Use document.documentElement.scrollTop instead of document.body.scrollTop. document.body.scrollTop may return incorrect values (0) on some browsers. Look at this answer, for example.
Declare distanceSofar inside of the slideIn function. You declared the variable on the top of your code, so it stores a static value of the scrollTop property.
I'd avoid using setInterval inside a scroll event handler, you are setting a lot of intervals and not clearing them. I added some console.logs to show you how the slideIn function keeps being called even when the user is not scrolling.
A final tip: the scrollTop property is a number, so you can compare it to 130 instead of "130".
Here is the working fiddle.
I tried your code. I think it is working as you expected. I also added a clearInterval inorder to clear the timer after printing all text and also to avoid repeated calling.
<html>
<head>
<style>
body {
height: 1000px;
}
div {
position: relative;
top: 700px;
}
div #firstPara {
border: 1px solid;
}
</style>
<script>
function start(){
var s =0;
var interval = undefined;
function slideIn() {
console.log(distanceSoFar);
if (distanceSoFar > "130") {
while ( firstString.length > 0) {
var stringOut = firstString.shift();
firstPara.innerHTML += stringOut;
console.log(s++);
}
clearInterval(interval);
interval = undefined;
}
};
var firstString = ["This ", "is ", " me."];
var firstPara = document.querySelector("#firstPara");
var distanceSoFar = (document.body.scrollTop);
window.addEventListener("scroll", function() {
if(!interval)
interval = setInterval(slideIn, 450);
});
};
</script>
</head>
<body onload="start()">
<div id="firstPara"/>
</body>
<html>
I have a div on which I have a directive that binds HTML content and compile it (sort of ng-bing-html directive, but that also compile html to allow insertion of custom directives). The HTML code looks like this :
<div ng-repeat="text in texts">
<div class="content-display"
bind-html-compile="text | filterThatOutputsHTMLCodeWithCustomDirectives | nl2br">
</div>
</div>
The problem is I need to display only a restricted portion of each of the content-display divs, and have a "read more..." button that would expand the corresponding div to its full size. But I CANNOT truncate the text bound in the div, since it's not only text, but can contain HTML tags/directives.
I found this JQuery code, that accomplish what I want visually : https://stackoverflow.com/a/7590517/2459955 (JSFiddle here : http://jsfiddle.net/rlemon/g8c8A/6/ )
The problem is that it's not Angular-compliant, and is pure JQuery. And since my div in which I bind the HTML content is inside an ng-repeat... this solution wouldn't work when the texts array gets refreshed asynchronously.
Do you see a way to have the same behavior as in the answer linked earlier, but being more "Angular compliant" and applying it automatically to each of the content-display divs added by the ng-repeat ?
Consider using a CSS approach like the one described here: https://css-tricks.com/text-fade-read-more/
CSS:
.sidebar-box {
max-height: 120px;
position: relative;
overflow: hidden;
}
.sidebar-box .read-more {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
margin: 0; padding: 30px 0;
/* "transparent" only works here because == rgba(0,0,0,0) */
background-image: linear-gradient(to bottom, transparent, black);
}
Rather than use jQuery for the read more "reveal", you could create an AngularJS directive for the read more button.
Directive (untested):
angular.module('myApp')
.directive('readMore', readMoreDirective);
function readMoreDirective() {
return function(scope, iElement) {
scope.$on('click', function() {
var totalHeight = 0;
var parentElement = iElement.parent();
var grandparentElement = parentElement.parent();
var parentSiblings = grandparentElement.find("p:not('.read-more')");
// measure how tall inside should be by adding together heights
// of all inside paragraphs (except read-more paragraph)
angular.forEach(parentSiblings, function(ps) {
totalHeight += ps.outerHeight();
});
grandparentElement.css({
// Set height to prevent instant jumpdown when max height is removed
height: grandparentElement.height(),
'max-height': 9999
})
.animate({
height: totalHeight
});
});
};
}
One clean way would be using a class for truncated div, and remove it to display all the text :
Angular scope :
$scope.truncated = []; // make new array containing the state of the div (truncated or not)
for(var i; i < texts.length -1; i++){
$scope.truncated.push(0); // fill it with 0 (false by default)
}
$scope.textTruncate = function(index) {
$scope.truncated[index] = !$scope.truncated[index]; // toggle this value
}
Angular view :
<div ng-repeat="text in texts" ng-class="{truncated: truncated[$index]}">
<div class="content-display"
bind-html-compile="text | filterThatOutputsHTMLCodeWithCustomDirectives | nl2br">
</div>
<button ng-click="textTruncate($index)" >Read more</button>
</div>
CSS :
.content-display {
max-height: 1000px; /* should be your max text height */
overflow: hidden;
transition: max-height .3s ease;
}
.truncated .content-display {
max-height: 100px; /* or whatever max height you need */
}
That is what comes in my mind, not sure if it's the most efficient way.
Try using <p data-dd-collapse-text="100">{{veryLongText}}</p> inside the ng-repeat
Documentation Here
Finally, I ended up using the approach given in this answer with a slight modification : https://stackoverflow.com/a/7590517/2459955
Indeed, since I have a ng-repeat adding more divs into the DOM, the $elem.each() function wouldn't trigger for these additional divs. The solution is to use a JQuery plugin called jquery.initialize.
This plugin gives an $elem.initialize() function that has exactly the same syntax as $elem.each() but initialize() will call the callback again on new items matching the provided selector automatically when they will be added to the DOM. It uses MutationObserver.
The final code looks like this. I have some JQuery code in my module.run() entry (run once at module initialization):
var slideHeight = 400;
$(".content-collapse").initialize(function() {
var $this = $(this);
var $wrap = $this.children(".content-display");
var defHeight = $wrap.height();
if (defHeight >= slideHeight) {
var $readMore = $this.find(".read-more");
var $gradientContainer = $this.find(".gradient-container");
$gradientContainer.append('<div class="gradient"></div>');
$wrap.css("height", slideHeight + "px");
$readMore.append("<a href='#'>Read more</a>");
$readMore.children("a").bind("click", function(event) {
var curHeight = $wrap.height();
if (curHeight == slideHeight) {
$wrap.animate({
height: defHeight
}, "normal");
$(this).text("Read less");
$gradientContainer.children(".gradient").fadeOut();
} else {
$wrap.animate({
height: slideHeight
}, "normal");
$(this).text("Read more");
$gradientContainer.children(".gradient").fadeIn();
}
return false;
});
}
});
And the corresponding HTML (cleaned for demonstration purpose):
<div class="content-collapse" ng-repeat="text in texts">
<div class="content-display" bind-html-compile="::text"></div>
<div class="gradient-container"></div>
<div class="read-more"></div>
</div>
This solution allows for smooth expand/collapse animation that works fine without any CSS hack, it adds the "Read more" button only on answers that exceeds the desired size limit, and works even if the texts array is modified by asynchronous requests.
I had a similar issue. I had o implement this for a data table. I found following directive and it worked smoothly as per requirements:-
Ui Framework- Angular js
In Html
<tr data-ng-repeat="proj in errors">
<td dd-text-collapse dd-text-collapse-max-length="40"
dd-text-collapse-text="{{proj.description}}"></td>
in Javascript:-
app.directive('ddTextCollapse', ['$compile', function($compile) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
/* start collapsed */
scope.collapsed = false;
/* create the function to toggle the collapse */
scope.toggle = function() {
scope.collapsed = !scope.collapsed;
};
/* wait for changes on the text */
attrs.$observe('ddTextCollapseText', function(text) {
/* get the length from the attributes */
var maxLength = scope.$eval(attrs.ddTextCollapseMaxLength);
if (text.length > maxLength) {
/* split the text in two parts, the first always showing */
var firstPart = String(text).substring(0, maxLength);
var secondPart = String(text).substring(maxLength, text.length);
/* create some new html elements to hold the separate info */
var firstSpan = $compile('<span>' + firstPart + '</span>')(scope);
var secondSpan = $compile('<span ng-if="collapsed">' + secondPart + '</span>')(scope);
var moreIndicatorSpan = $compile('<a ng-if="!collapsed">... </a>')(scope);
var lineBreak = $compile('<br ng-if="collapsed">')(scope);
var toggleButton = $compile('<a class="collapse-text-toggle" ng-click="toggle()">{{collapsed ? "(less)" : "(more)"}}</a>')(scope);
/* remove the current contents of the element
and add the new ones we created */
element.empty();
element.append(firstSpan);
element.append(secondSpan);
element.append(moreIndicatorSpan);
element.append(lineBreak);
element.append(toggleButton);
}
else {
element.empty();
element.append(text);
}
});
}
};
}]);
I am dynamically creating divs and I want them to appear next to each other. I have the following code and after applying style to it (line 5) they keep showing one of top of the other. Please help.
rmc.onstream = function (e) {
if (e.isVideo) {
var uibox = document.createElement("div");
uibox.appendChild(document.createTextNode(e.userid));
uibox.className = "userid";
uibox.id = "uibox-" + e.userid;
uibox.style.cssText = 'display: inline-block; float: left';
document.getElementById('video-container').appendChild(e.mediaElement);
document.getElementById('video-container').appendChild(uibox);
} else if (e.isAudio) {
document.getElementById('video-container').appendChild(e.mediaElement);
}
else if (e.isScreen) {
$('#cotools-panel iframe').hide();
$('#cotools-panel video').remove();
document.getElementById('cotools-panel').appendChild(e.mediaElement);
}
};
Your styles are only being applied to uibox, and you need to apply them to emediaElement too:
if (e.isVideo) {
var uibox = document.createElement("div");
uibox.appendChild(document.createTextNode(e.userid));
uibox.className = "userid";
uibox.id = "uibox-" + e.userid;
uibox.style.cssText = 'float: left; width: 50%';
e.mediaElement.style.cssText = 'float: left; width: 50%';
document.getElementById('video-container').appendChild(e.mediaElement);
document.getElementById('video-container').appendChild(uibox);
}
Here is a working pen - I had to modify your code since I can't see where e.mediaElement is coming from, but you can get the idea: http://jsbin.com/woluneyixa/1/edit?html,css,js,output
If you're still having issues, please create a working codepen so we can see the problem you're having.
Also, using display: inline-block and float: left; is unnecessary; inline-block will have no effect whatsoever when using float.
Im trying to make a 'blanket' of divs containing child divs 150px high and 150px wide.
I want each child div to fade in 1 after the other after after a millisecond or so, opacity changing from 0, to 1.
I cant seem to figure out how this works, or how id do it though?
http://jsfiddle.net/CCawh/
JS
$(function(){
var figure = [];
w = 1500;
h = 450;
for(i = 0, i < 30, i++){
$('div').append(figure[].clone()).fadeIn();
}
});
Here is a working solution.
The problems in your code
in for(i = 0, i < 30, i++), you should use ';', not ',' . Use developer tools in your browser to catch such typos
In your code $('div').append(figure[].clone()).fadeIn(); , The fadeIn applies to $('div') as append() returns the calling object itself. You must replace it with $('<figure></figure>').appendTo('div').fadeIn('slow'); and to fadeIn items one by one you could set a timeout with incrementing delays
Add display: none; style to the figure to keep it hidden initially
Here is the full code.
$(function(){
for(i = 0; i < 30; i++){
setTimeout(function(){$('<figure></figure>').appendTo('div').fadeIn('slow');}, i*200);
}
});
Here is a fiddle to see it working http://jsfiddle.net/CCawh/12/
Try using greensock TweenLite http://www.greensock.com/get-started-js/.
It has staggerTo/staggerFrom action that does exactly what you are asking. TweenLite in conjunction with jQuery makes animation very easy.
This would be a possible solution (DEMO).
Use an immediate function and call it again n times in the fadeIn callback.
$(function(){
var figure = $('figure');
var counter = 0;
(function nextFade() {
counter++;
figure.clone().appendTo('div').hide().fadeIn(500, function() {
if(counter < 30) nextFade();
});
})();
});
You can use the following implementation as an example. Using setTimeout() will do the trick.
I've updated your jsfiddle here: http://jsfiddle.net/CCawh/5/
HTML:
<div class="container">
<div class="box"></div>
</div>
CSS:
.box {
display: none;
float: left;
margin: 10px;
width: 150px;
height: 150px;
background-color: #000;
}
JS:
$(function() {
var box = $('.box');
var delay = 100;
for (i = 0; i < 30; i++) {
setTimeout(function() {
var new_box = box.clone();
$('.container').append(new_box);
new_box.fadeIn();
}, delay);
delay += 500; // Delay the next box by an extra 500ms
}
});
Note that in order for the element to actually fade in, it must be hidden in the first place, i.e. display: none; or .hide()
Here's perhaps a more robust solution without counters:
http://jsfiddle.net/CCawh/6/
for(var i = 0; i < 30; i++){
$('div').append($('<figure>figure</figure>'));
}
(function fade(figure, duration) {
if (figure)
figure.fadeIn(duration, function() { fade(figure.next(), duration); });
})($('figure').first(), 400);
By the way, clauses in for loops are separated using semicolons, not commas.