When a sphere falls I would like to put images inside but it doesn't work with the fillStyle render I can only color, now with sprite I can but they don't fit the circle unless the image is rounded, how could I round the image with javascript and matter.js
the code:
return Matter.Bodies.circle(280, 40, 11, {
restitution: 0.5,
render: {
// fillStyle: '#F00',
// strokeStyle: 'black',
// lineWidth: 3,
sprite: {
texture: img,
xScale: 0.3,
yScale: 0.3,
}
}
});
img get square images from ticktok, which I don't know how to make the round
I'm not 100% sure, if this is the best way to do it (in you specific usecase), but you could just :
create a helper canvas Element:
<canvas id='helper-canvas'></canvas>
or let canvas = document.createElement('canvas');
set the width/height of the canvas (width = desired texture width)
...
canvas.width='100';
canvas.height='100';
...
draw the image onto a canvas element(using the context).
...
ctx.drawImage(img, 0, 0); // "img" is a HtmlImageElement
...
set composite-mode and draw a circle that should have the desired size
...
ctx.globalCompositeOperation='destination-in';
ctx.beginPath();
ctx.arc(img.width/2,img.width/2,img.width/2,0,Math.PI*2);
ctx.closePath();
ctx.fill();
...
generate the url of the new "create image"
...
let imgUrl = img.toDataURL('image/png');
...
And than simply create the matter-body, with that image:
Matter.Bodies.circle(280, 40, 11, {
restitution: 0.5,
render: {
sprite: {
texture: imgUrl,
xScale: 0.3,
yScale: 0.3,
}
}
});
You might be pushing the built-in renderer beyond its intent for simple use cases (debugging, prototyping). Consider using MJS headlessly along with a custom renderer that is better suited to a typical game or animation's rendering complexity.
You can use a similar technique as described in Matter.js Text inside a rectangle and make circles with, for example, HTML and CSS:
document.querySelector("img")
.addEventListener("load", () => {
const engine = Matter.Engine.create();
const circle = {
body: Matter.Bodies.circle(150, 0, 50),
elem: document.querySelector("#circle"),
render() {
const {x, y} = this.body.position;
this.elem.style.top = `${y - 50}px`;
this.elem.style.left = `${x - 50}px`;
this.elem.style.transform = `rotate(${this.body.angle}rad)`;
},
};
const ground = Matter.Bodies.rectangle(
200, 200, 400, 120, {isStatic: true}
);
const mouseConstraint = Matter.MouseConstraint.create(
engine, {element: document.body}
);
Matter.Composite.add(
engine.world, [circle.body, ground, mouseConstraint]
);
(function rerender() {
circle.render();
Matter.Engine.update(engine);
requestAnimationFrame(rerender);
})();
});
#circle {
position: absolute;
background: #111;
height: 100px;
width: 100px;
top: -100px;
left: -100px;
cursor: move;
border-radius: 50%;
}
#circle img {
border-radius: 50%;
}
#ground {
position: absolute;
background: #666;
top: 140px;
height: 120px;
width: 400px;
}
html, body {
position: relative;
height: 100%;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<div id="circle">
<img
draggable="false"
src="https://4.bp.blogspot.com/-DmPeZ5KQhnM/TvDXvxxb_WI/AAAAAAAAAKI/aRDOjVpBtfM/s1600/poptarticon.gif"
>
</div>
<div id="ground"></div>
You can use the CSS background property instead of the <img> here.
Related
So I am making an Angry-Birds game and I am using p5.js and matter.js.
I created a mouseConstraint in the game to move the bird attached to a slingshot, but I am also able to move all the bodies in the output.
How can I attach the mouseConstraint only to a single body, i.e., the bird in this case, so that I can move only that particular object and nothing else?
If this is not possible, is there an alternative which I can use for using the slingshot?
You can use collision filters:
const makeBox = (x, y, w, h, props, elem) => ({
w, h, body: Matter.Bodies.rectangle(
x, y, w, h, props
),
elem,
render() {
const {x, y} = this.body.position;
this.elem.style.top = `${y - this.h / 2}px`;
this.elem.style.left = `${x - this.w / 2}px`;
this.elem.style.transform = `rotate(${this.body.angle}rad)`;
},
});
const boxes = [...document.querySelectorAll(".box")]
.map((el, i) =>
makeBox(
// x y w h
100 * i + 100, 0, 40, 30,
{collisionFilter: i === 0 ? {category: 0b10} : {}},
el,
)
);
const ground = Matter.Bodies.rectangle(
// x y w h
200, 200, 400, 120, {
isStatic: true,
}
);
const engine = Matter.Engine.create();
const mouseConstraint = Matter.MouseConstraint.create(
engine, {
collisionFilter: {mask: 0b10},
element: document.body
}
);
Matter.Composite.add(
engine.world, [
...boxes.map(e => e.body), ground, mouseConstraint
]
);
(function rerender() {
boxes.forEach(e => e.render());
Matter.Engine.update(engine);
requestAnimationFrame(rerender);
})();
.box {
position: absolute;
background: #d00;
transition: background 0.2s;
width: 40px;
height: 30px;
cursor: move;
}
.box:not(:first-child) {
background: #111;
cursor: not-allowed;
}
.box:first-child:hover {
background: #f00;
}
#ground {
position: absolute;
background: #666;
top: 140px;
height: 120px;
width: 400px;
}
html, body {
position: relative;
height: 100%;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
<div id="ground"></div>
Here, the mouse constraint is given a mask of 0b10 and the red box which is the only body that is allowed to interact with the mouse is set to a category of 0b10.
The default mask value is 32 bits all set, or 4294967295/0xffffffff. You may wish to be more precise and disable only the first bit: 0xfffffffe. This lets the mouse constraint interact with other categories in addition to 2, disabling only interactions with category 1.
To create the opposite situation, where the mouse interacts with any bodies except for the red box, you can set the mouse constraint's mask to something that has the secondmost least significant bit off, like 0b1 or 0xfffffffd.
See also:
How can I change the collisionFilter of an object so it can no longer interact with MouseConstraint (MatterJS).
How to prevent sprites to be moved by mouse in Matter.js?
To attach a mouseConstraint to a single body, you'll need to pass in the body as the second argument:
mouseConstraint = MouseConstraint.create(engine, {body: bird});
In my KonvaJS project, I'm adding images aka "stickers" onto an uploaded image, and with KonvaJS, I'm adding "Anchors" to rotate and resize the images.
When the mouse is off the main image, (or maybe when you click on the sticker, it toggles edit mode?), I'd like to remove the anchors and lines.
How is this possible?
function centreRectShape(shape) {
shape.x((stage.getWidth() - shape.getWidth()) / 2);
shape.y((stage.getHeight() - shape.getHeight()) / 2);
}
var stage = new Konva.Stage({
container: 'canvas-container',
width: 650,
height: 300
});
var layer = new Konva.Layer();
stage.add(layer);
var bgRect = new Konva.Rect({
width: stage.getWidth(),
height: stage.getHeight(),
fill: 'gold',
opacity: 0.1
});
layer.add(bgRect);
var uploadedImage = new Konva.Image({
draggable: false
});
layer.add(uploadedImage);
imgObj = new Image();
imgObj.onload = function() {
uploadedImage.image(imgObj);
var padding = 20;
var w = imgObj.width;
var h = imgObj.height;
var targetW = stage.getWidth() - (2 * padding);
var targetH = stage.getHeight() - (2 * padding);
var widthFit = targetW / w;
var heightFit = targetH / h;
var scale = (widthFit > heightFit) ? heightFit : widthFit;
w = parseInt(w * scale, 10);
h = parseInt(h * scale, 10);
uploadedImage.size({
width: w,
height: h
});
centreRectShape(uploadedImage);
layer.draw();
}
imgObj.src = 'https://images.pexels.com/photos/787961/pexels-photo-787961.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260';
$('.sticker').on('click', function() {
addSticker($(this).attr('src'));
});
function addSticker(imgUrl) {
stickerObj = new Konva.Image({
x: 240,
y: 20,
width: 93,
height: 104,
draggable: true
});
layer.add(stickerObj);
var stickerImage = new Image();
stickerImage.onload = function() {
stickerObj.image(stickerImage);
layer.draw();
};
stickerImage.src = imgUrl;
var imgRotator = new Konva.Transformer({
node: stickerObj,
keepRatio: true,
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right']
});
layer.add(imgRotator);
}
html,
* {
margin: 0;
padding: 0;
}
body {
background: #eee;
}
#image-editor {
background: #fff;
border-radius: 3px;
border: 1px solid #d8d8d8;
width: 650px;
margin: 0 auto;
margin-top: 20px;
box-shadow: 0 3px 5px rgba(0, 0, 0, .2);
}
.stickers {
padding: 10px 5px;
background: #eee;
}
.stickers>img {
margin-right: 10px;
}
<script src="https://unpkg.com/konva#2.4.1/konva.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="image-editor">
<div id="canvas-container"></div>
<div class="stickers">
<div>
click sticer below and then rotate
</div>
<img class="sticker" src="https://craftblock.me/koa/fb-upload-clone/stickers/sticker%20(1).png" alt="Sticker" width="62px">
</div>
</div>
The most simple way is to add to the addSticker() function event handlers for both mouseover and mouseout. You can attach them, e. g., to the stage object.
stage.on('mouseover', function() {
imgRotator.attachTo(stickerObj);
layer.draw();
});
stage.on('mouseout', function() {
imgRotator.detach();
layer.draw();
});
They do two things:
attach / detach the Transformer object to / from the selected node.
refresh the layer.
From here on you can improve it. You could make the addSticker() function to return an object with the attachTo and detach methods to be able to call them from somewhere else. Something like:
function addSticker(imgUrl) {
...
return {
showTransformer: function() {
imgRotator.attachTo(stickerObj);
layer.draw();
},
hideTransformer: function() {
imgRotator.detach();
layer.draw();
}
};
}
Also, if you mean to have more than one sticker, you should implement some logic to track to which sticker should the events apply.
Edit:
You have made two comments to this answer:
"I think it's best if the mouseout is for that specific sticker."
The anchors to resize and rotate the image are actually outside the image. If you hide them after the mouseout event... well, your users will never reach them ;)
You have two options to avoid it:
Create a custom hit region which includes the anchors.
Replace the event with a click to toggle the transformer object.
I use the second approach in the updated snippet.
"What if there's multiple added stickers?"
You should use a closure to keep the references to the different objects in the scope of that functions.
Combining the click to toggle and the closure we have:
stickerObj.on('click', (function(transformer, sticker) {
return function() {
transformer.getNode() ? transformer.detach() : transformer.attachTo(sticker);
layer.draw();
};
})(imgRotator, stickerObj));
Extra tips:
Even after detaching the transformer, the user will be still able to move the image around. That's because draggable is a property of the image (inherited from node), not of the transformer.
So maybe you also want to toggle node.draggable(boolean);.
If for some reasons you don't want to detach/attach the transformer, you can achieve a similar effect toggling borderEnabled(), resizeEnabled() and rotateEnabled().
function centreRectShape(shape) {
shape.x((stage.getWidth() - shape.getWidth()) / 2);
shape.y((stage.getHeight() - shape.getHeight()) / 2);
}
var stage = new Konva.Stage({
container: 'canvas-container',
width: 650,
height: 300
});
var layer = new Konva.Layer();
stage.add(layer);
var bgRect = new Konva.Rect({
width: stage.getWidth(),
height: stage.getHeight(),
fill: 'gold',
opacity: 0.1
});
layer.add(bgRect);
var uploadedImage = new Konva.Image({
draggable: false
});
layer.add(uploadedImage);
imgObj = new Image();
imgObj.onload = function() {
uploadedImage.image(imgObj);
var padding = 20;
var w = imgObj.width;
var h = imgObj.height;
var targetW = stage.getWidth() - (2 * padding);
var targetH = stage.getHeight() - (2 * padding);
var widthFit = targetW / w;
var heightFit = targetH / h;
var scale = (widthFit > heightFit) ? heightFit : widthFit;
w = parseInt(w * scale, 10);
h = parseInt(h * scale, 10);
uploadedImage.size({
width: w,
height: h
});
centreRectShape(uploadedImage);
layer.draw();
}
imgObj.src = 'https://images.pexels.com/photos/787961/pexels-photo-787961.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260';
$('.sticker').on('click', function() {
addSticker($(this).attr('src'));
});
function addSticker(imgUrl) {
stickerObj = new Konva.Image({
x: 240,
y: 20,
width: 93,
height: 104,
draggable: true
});
layer.add(stickerObj);
var stickerImage = new Image();
stickerImage.onload = function() {
stickerObj.image(stickerImage);
layer.draw();
};
stickerImage.src = imgUrl;
var imgRotator = new Konva.Transformer({
node: stickerObj,
keepRatio: true,
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right']
});
layer.add(imgRotator);
stickerObj.on('click', (function(transformer, sticker) {
return function() {
transformer.getNode() ? transformer.detach() : transformer.attachTo(sticker);
layer.draw();
};
})(imgRotator, stickerObj));
}
html,
* {
margin: 0;
padding: 0;
}
body {
background: #eee;
}
#image-editor {
background: #fff;
border-radius: 3px;
border: 1px solid #d8d8d8;
width: 650px;
margin: 0 auto;
margin-top: 20px;
box-shadow: 0 3px 5px rgba(0, 0, 0, .2);
}
.stickers { pitón reticulada
padding: 10px 5px;
background: #eee;
}
.stickers>img {
margin-right: 10px;
}
<script src="https://unpkg.com/konva#2.4.1/konva.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="image-editor">
<div id="canvas-container"></div>
<div class="stickers">
<div>
click sticer below and then rotate
</div>
<img class="sticker" src="https://craftblock.me/koa/fb-upload-clone/stickers/sticker%20(1).png" alt="Sticker" width="62px">
</div>
</div>
I have two moving rectangles in my canvas, one is green and one is red. When they are on the same position and you can see only one of them, the red rectangle is always displayed while the green one simply is under the red one.
How can i accomplish that the green one is always displayed on top?
without any code it is hard to tell, but if you have the code for the green rectangle first it will be drawn first and the red will be drawn on top of it.
I think you should paint the green one AFTER the red one. so if your rectangles are in an array, you can iterate on it in reverse order
(function start() {
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth || $(window).width();
canvas.height = window.innerHeight || $(window).height();
const ctx = canvas.getContext('2d');
const draw = function(opts={color: 'yellow', x: 0, y: 0}) {
ctx.beginPath();
ctx.rect(opts.x, opts.y, 150, 100);
ctx.fillStyle = opts.color;
ctx.fill();
}
// COLUMN 1
draw({color: 'red',x: 20,y: 20});
draw({color: 'green',x: 40,y: 40});
// COLUMN 2
draw({color: 'green', x: 200, y: 20});
draw({color: 'red', x: 220, y: 40});
})();
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
background: #000;
}
canvas {
position: absolute;
left: 0;
top: 0;
z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id='canvas'></canvas>
Version
1.7.3
Test Case
https://jsfiddle.net/human_a/twdgya93/
In this fiddle, I have also tried creating the shape using the following code to avoid creating empty polygons, but the result is the same:
const polygon = new fabric.Polygon(calcPolygonPoints(8, Math.abs( origX - pointer.x ) / 2 ), {
objectCaching: false,
left: origX,
top: origY,
originX: 'center',
originY: 'center',
fill: 'rgba(255,255,255, 1)',
perPixelTargetFind: false,
strokeWidth: 1,
strokeDashArray: [0,0],
objType: 'shape',
stroke: 'rgba(17,17,17,1)',
hasControls: false,
hasBorders: false
})
Steps to reproduce
Click on the green button to go to drawing mode, drag the mouse inside the canvas to start creating the shape, let go of the mouse, then try selecting the newly created shape. The bounding box size matches the size of the new polygon, but it's not positioned correctly.
Expected Behavior
If instead of polygons I try to create any other type of shapes (rectangles, circles or triangles) It works perfectly fine.
Actual Behavior
Since polygons use a different method by calculating the points it calculates the width and the height, and since in this method of creating shapes the width/height are being calculated after the shape is created (with mouse:move event) the bounding rectangle will not be positioned correctly.
Also even if I try to change the size of the polygon (I don't want to scale the shapes, it's a bad idea) again the bounding rectangle is not positioned correctly, you can try it in the demo above using the number input field right next to the button.
PS
I did not write the calcPolygonPoints function, I found it somewhere on the web a while ago, unfortunately, I could not find its link again to credit the creator of this amazing function.
When you have a fiddle is very nice and fast to transform it in a working snippet, since then getting an answer is very easy.
Said so, the point is that the polygon in fabric.js do not support points updating. You were already calling the _calcDimensions() but that is not enough.
To correctly center the polygon in the bounding box you have to also populate its pathOffset property with updated values.
I added this in your snippet on the mouseUp event.
var canvas = new fabric.Canvas();
var el = document.getElementById('my-canvas');
var drawPoly = document.getElementById('draw-poly');
var changeSize = document.getElementById('change-size');
var origX, origY;
var calcPolygonPoints = (sideCount,radius) => {
var sweep=Math.PI*2/sideCount;
var cx=radius;
var cy=radius;
var points=[]
for(var i=0;i<sideCount;i++){
var x=cx+radius*Math.cos( i*sweep )
var y=cy+radius*Math.sin( i*sweep )
points.push( { x:x, y:y } )
}
return(points)
}
canvas.initialize(el, {
backgroundColor: '#fff',
width: 600,
height: 600
});
canvas.renderAll();
drawPoly.addEventListener('click', (e)=>{
canvas.defaultCursor = "crosshair";
canvas.selection = false;
canvas.discardActiveObject();
canvas.discardActiveGroup();
canvas.forEachObject(object=>{ object.selectable = false; });
canvas.renderAll();
canvas.on('mouse:down', opt => {
if (canvas.selection) return;
var pointer = canvas.getPointer(opt.e)
origX = pointer.x;
origY = pointer.y;
// I have also tried initial calculations here
// by using calcPolygonPoints(8, Math.abs( origX - pointer.x ) / 2 ) instead of []
// The result is the same
const polygon = new fabric.Polygon(calcPolygonPoints(8, Math.abs( origX - pointer.x ) / 2 ), {
objectCaching: false,
left: origX,
top: origY,
originX: 'center',
originY: 'center',
fill: 'rgba(255,255,255, 1)',
perPixelTargetFind: false,
strokeWidth: 1,
strokeDashArray: [0,0],
objType: 'shape',
stroke: 'rgba(17,17,17,1)',
hasControls: false,
hasBorders: false
})
// polygon._calcDimensions()
canvas.add(polygon).setActiveObject(polygon)
}).on('mouse:move', opt => {
if (canvas.selection || !canvas.getActiveObject()) return;
const newShape = canvas.getActiveObject()
var pointer = canvas.getPointer(opt.e)
if (newShape) {
newShape.set({
points: calcPolygonPoints(8, Math.abs( origX - pointer.x ) / 2 )
})
newShape._calcDimensions()
}
changeSize.value = Math.abs( origX - pointer.x ) / 2;
canvas.renderAll()
}).on('mouse:up', opt => {
// In my app I am using redux stores to turn off the drawing
// Here I used the following if statement to turn off the drawing
if (canvas.selection) return;
const newShape = canvas.getActiveObject()
if (newShape) {
newShape.set({
hasControls: true,
hasBorders: true
})
newShape.pathOffset = {
x: newShape.minX + newShape.width / 2,
y: newShape.minY + newShape.height / 2
};
var pointer = canvas.getPointer(opt.e);
var center = { x: (pointer.x + origX)/2, y: (pointer.y + origY)/2}
newShape.setPositionByOrigin(center, 'center', 'center')
newShape.setCoords()
canvas.renderAll()
}
canvas.renderAll()
canvas.selection = true;
canvas.off('mouse:down').off('mouse:move')
canvas.defaultCursor = "default";
canvas.discardActiveObject()
canvas.forEachObject(object=>{
if (object.evented) object.selectable = true;
})
})
})
changeSize.addEventListener('input', (e)=>{
if (!canvas.getActiveObject()) return;
canvas.getActiveObject().set({
points: calcPolygonPoints(8, parseInt(e.target.value, 10) )
})
canvas.getActiveObject()._calcDimensions()
canvas.renderAll()
})
button {
border: 0 none;
background: #2ecc70;
border-radius: 5px;
cursor: pointer;
color: #fff;
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
text-transform: uppercase;
padding: 11px 22px;
font-weight: 600;
font-size: 13px;
letter-spacing: 1px;
margin: 10px auto;
outline: 0 none;
}
input {
border: 1px solid #ddd;
box-shadow: none;
padding: 11px;
font-size: 13px;
border-radius: 5px;
margin-left: 10px;
max-width: 50px;
outline: 0 none !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.3/fabric.min.js"></script>
<div id="wrapper">
<canvas id="my-canvas"></canvas>
<button id="draw-poly">Draw Polygon</button>
<input type="number" id="change-size" value="0" />
</div>
I have never used GreenSock before. The background image changes fine, it switches and scales in and out however the issues I am facing are as follows:
The first background image on the sprite to show does not scale in and out but all the others do, how can I fix this?
It seems to change the background then scale in and out. So there is a small delay between the changing of the background image and the start of scale animation. Is there anyway to tighten this up so it scales as its changed to make it a more smoother transition?
JavaScript:
// Avatar animations
var avatarInterval;
var fadePulse = true; // true: will fade avatar images and pulse in and out once changed
// false: will slide the avatars in and out
var avatarCount = 11; // set the amount of avatars in the sprite image
var avatarSpeed = 1000; // set the avatar transition speed
var avatarHeight = 250; // set the height of a single avatar image
var avatarTotalHeight = 2750; // set the total height of the avatar sprite image
function startAvatarAnimation() {
var i = 0;
$(".avatars").show();
// Loop through avatar background images on sprite
avatarInterval = setInterval(function(){
i++;
if(i > avatarCount){
i = 0;
}
// Let's change the background
$(".avatars").css({'background-position' : '0 -' + (i*avatarHeight) + 'px' });
// avatar fading / pulse effect
if (fadePulse == true) {
// Now some scaling effects!
TweenMax.to(avatars, 0.1, {
css: {
// 'background-position': '0 -' + (i*avatarHeight) + 'px',
scaleX: 1.1,
scaleY: 1.1,
transformOrigin: "center center"
},
onComplete: scaleOut,
onCompleteParams: [avatars],
delay: 0.1,
ease: Power3.easeInOut
});
// Bring the scale back to normal
function scaleOut(el) {
TweenMax.to(el, 0.1, {
css: {
scaleX: 1.0,
scaleY: 1.0,
transformOrigin: "center center",
autoAlpha: 1,
},
ease: Power2.easeOut
});
}
} else {
// avatar sliding effect
}
}, avatarSpeed);
return false;
}
Take a look at this result.
Snippet:
var avatarCount = 6;
var avatarHeight = 250;
var avatarTotalHeight = 1500;
var avatars = $(".avatars");
var animDuration = 0.1;
var i = 0;
var timeline = new TimelineMax({ paused: true, repeat: -1 });
timeline.to(avatars, animDuration, {
scaleX: 1.1,
scaleY: 1.1,
ease: Power3.easeIn,
onComplete: onCompleteScaleIn
});
timeline.to(avatars, animDuration, {
scaleX: 1.0,
scaleY: 1.0,
ease: Power3.easeOut
});
function onCompleteScaleIn() {
i++;
i = i >= avatarCount ? 0 : i;
TweenMax.set(avatars, {
backgroundPosition: '0 -' + (i * avatarHeight) + 'px'
});
}
timeline.play();
#container {} #container,
.section {
overflow: hidden;
position: relative;
}
.section {
position: absolute;
}
#container .logo_o2 {
bottom: 10px;
right: 20px;
}
.section {
position: relative;
height: 250px;
width: 300px;
display: block;
}
.section .abs {
position: absolute;
}
.section h1,
.section h2,
.section h3,
.section h4 {
font-size: 21px;
width: 100%;
text-align: center;
letter-spacing: 0;
}
.section1,
.section2,
.section3,
.section4,
.section5,
.section6 {} .avatars {
background-image: url(http://s1.postimg.org/dwt9yu9b3/test_bg_sprite.png);
background-repeat: no-repeat;
background-size: cover;
display: block;
height: 250px;
width: 300px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>
<div class="section section3">
<div class="abs clouds"></div>
<div id="avatars" class="abs avatars"></div>
</div>
Details:
First off, this result could have been achieved in a number of ways, more efficiently. Even within TweenMax there could have been a number of possible solutions. So, this attempt is in no way supposed to be the best.
Because you are already using GSAP; I have used TimelineMax, TweenMax's powerful cousin that makes sequencing of animations very easy.
So we have a timeline variable which carries an instance of TimelineMax object with default settings of: 1. initially paused and 2. indeterminate loops.
We then populate this timeline by using the method .to() which basically will start animation from wherever the object currently is. There are a plethora of methods and properties available for the GSAP platform, you should explore.
On the first line of .to() call, we have an onComplete callback pointing to a function.
This callback adjusts the backgroundPosition as per the current iteration.
Finally, there is another .to() call which does the same i.e. start the animation from whatever attributes avatars object currently possesses (in our case, scaleX & scaleY would be at 1.1 because of the first .to() call).
Note: by default, any new .to() (or .from() or .fromTo()) call is appended at the end of a timeline. Read more about the position parameter here.
Let me know if you have any questions.
Update: Here is another version using TweenMax only. Much leaner I guess.