You just need to draw the desiged background and translate the backgrounds y position by your the speed you want to have.
This is mostly called a camera. The camera handles the translation of the x and y positions while the square (or the player entity) is in the middle of the screen.
You also need to make sure that your background (image, color, pattern) is allways visible by the viewport.
I'll give you an example camera i made for a test game engine written in typescript to translate the coordinates.
/**
* calculates the position offset for camera following
*/
private static calculatePositionOffsetForCameraFollow(originalPosition: Vector2D, camera: Camera): Vector2D {
// first check if there is any following
let entity = camera.getFollowingEntity();
// if no following is active, return the original position
if (!entity) return originalPosition;
// calculate the offset. the entity should be in the center of the screen
let canvasDim = CameraOffsetCalculator.getCanvasDimension();
// calculate the center position for the entity and shift the other
// vectors.
let tmpVector = originalPosition.substract(entity.getPosition()).add(
canvasDim.divide(new Vector2D(2, 2))
);
// now check if the camera has world bounds
let worldBounds = camera.getWorldBounds();
if (!worldBounds) return tmpVector;
// check if the original vector is smaller than the shifted vector
// this will bound the left and top world bounds
if (originalPosition.x < tmpVector.x) {
// reset the x axis to fix the camera
tmpVector.x = originalPosition.x;
}
if (originalPosition.y < tmpVector.y) {
// reset the y axis to fix the camera
tmpVector.y = originalPosition.y;
}
// now the left and bottom bounds
// we need the world dimension to check if the camera reaches
// the end of the world in the visible area
let entityPosition = entity.getPosition();
let worldBoundCanvas = worldBounds.substract(canvasDim.half());
if (entityPosition.x > worldBoundCanvas.x) {
// get the original position and substract
// the distance from world bounds and canvas dim
tmpVector.x = originalPosition.x - (worldBounds.x - canvasDim.x);
}
if (entityPosition.y > worldBoundCanvas.y) {
// get the original position and substract
// the distance from world bounds and canvas dim
tmpVector.y = originalPosition.y - (worldBounds.y - canvasDim.y);
}
// return the corrected position vector
return tmpVector;
}
This function returns a new point for the background image or tile to draw at while following an entity (in your case the square).
Let me know if this has helped you!
Here is the illusion integrated in your code with a very simplified camera:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var rectWidth = 50;
var rectHeight = 50;
var rectRad = 25;
var x = (canvas.width / 2) - rectRad;
var y = (canvas.height / 2) - rectHeight;
//var dx = 2;
//var dy = 4;
var ch = canvas.height;
// should be lower than bgHeight
var scrollSpeed = .0475;
// a boolean to make the background shifts differently at
// y position to make the user think the player entity is moving
// forwards
var scroller = 0;
// the repeating background
var bg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAQCAMAAAA7+k+nAAAAKlBMVEXx8fH7+/v8/Pz5+fn29vb39/f19fXz8/P4+Pj6+vr09PTy8vL////o6OgghGlxAAAAk0lEQVR42nWPSQ7DMAwDZWujxPj/363dS1qk1YkCwQFG1p+Tdd33mXcxIppoSa4r0R6ZAHfR1kUwI9ZVLBpnu8p+nEGWY67LfXZijDyLgHmd2UEd7Hu2Czc4kE7d2MyATqk6qMgaraq984SWear99/g2uLMEaOTTRmSMgD1tRNsQ82kjnuX6w0ZazeSHjdAsE0+bFzAoDXEZIyZUAAAAAElFTkSuQmCC";
var bgBitmap = createImageBitmap(b64ToBlob(bg, "image/png"));
function rect() {
ctx.beginPath();
ctx.rect(x, y, rectWidth, rectHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// first draw the background
drawBackground();
// and now the rectangle
rect();
}
function drawBackground() {
var bgWidth = 14, bgHeight = 14;
// draw the full visible screen and on each edge, draw another tile
// to make a smooth scrolling
for (var x = -1; x < canvas.width / bgWidth; x++) {
for (var y = -1; y < canvas.height / bgHeight; y++) {
var yCorrectedCoordinate = y + (scroller * scrollSpeed);
// draw!
ctx.drawImage(
bgBitmap, x * bgWidth, yCorrectedCoordinate * bgHeight,
bgWidth, bgHeight
);
}
}
// change the scroller offset
scroller++;
if (scroller > 20)
scroller = 0;
}
/**
* converts a base64 string to a blob image capable for using in canvas environment
* @see https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
*/
function b64ToBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data.split(',')[1]);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, { type: contentType });
return blob;
}
// start the game loop after the image has beed loaded
bgBitmap.then(function (bg) {
bgBitmap = bg;
setInterval(draw, 10);
});
// i would recommend to use window.requestAnimationFrame() instead
If there are other questions about game development, here is my repository: qhun-engine at Github.com