For a project, I need to take these two blocks of code and modify them to create a little animation. The Javascript document creates a background color and creates like white rectangles fall down, like snow. The HTML document creates the canvas.
I have worked with the HTML canvas and done some super basic animations, but I'm hitting a roadblock here. When I try to add stuff to this, it does not show up. All I want to do is add some text and then animate it going across the canvas and some other very basic stuff.
I tried adding the text to the script portion of the html document like I have done several times before, but it just didn't seem to do anything.
Any idea what I'm doing wrong?
(I'm an animation major, so I apologize in advance for my ignorance; scripting is not my forte)
html document:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>A Basic Animation</title>
<script type="text/javascript" src="buffer-script2.js"></script>
<style type="text/css">
body,td,th {
font-family: Arial, "Franklin Gothic Book", sans-serif;
}
</style>
</head>
<body>
<h2>An Animation with Double Buffer</h2>
<canvas id="Sky" width="600" height="300">
Canvas not supported
</canvas>
<script>
var canvas = document.getElementById("Sky");
var ctx = canvas.getContext("2d");
ctx.font = "30px Arial";
ctx.strokeText("Hello World",10,50);
</script>
</body>
</html>
Javascript file:
window.onload = init;
var canvas = null;
var context = null;
var bufferCanvas = null;
var bufferCanvasCtx = null;
var flakeArray = [];
var flakeTimer = null;
var maxFlakes = 200;
function Flake() {
this.x = Math.round(Math.random() * context.canvas.width);
this.y = -10;
this.drift = Math.random();
this.speed = Math.round(Math.random() * 5) + 1;
this.width = (Math.random() * 5) + 2;
this.height = this.width;
}
function init() {
canvas = document.getElementById('Sky');
context = canvas.getContext("2d");
bufferCanvas = document.createElement("canvas");
bufferCanvasCtx = bufferCanvas.getContext("2d");
bufferCanvasCtx.canvas.width = context.canvas.width;
bufferCanvasCtx.canvas.height = context.canvas.height;
// initialize the rects
flakeTimer = setInterval(addFlake, 200);
Draw();
setInterval(animate, 30);
}
function addFlake() {
flakeArray[flakeArray.length] = new Flake();
if (flakeArray.length == maxFlakes)
clearInterval(flakeTimer);
}
function blank() {
bufferCanvasCtx.fillStyle = "#330033";
bufferCanvasCtx.fillRect(0,0,bufferCanvasCtx.canvas.width, bufferCanvasCtx.canvas.height);
}
function animate() {
Update();
Draw();
}
function Update() {
for (var i = 0; i < flakeArray.length; i++) {
if (flakeArray[i].y < context.canvas.height) {
flakeArray[i].y += flakeArray[i].speed;
if (flakeArray[i].y > context.canvas.height)
flakeArray[i].y = -5;
flakeArray[i].x += flakeArray[i].drift;
if (flakeArray[i].x > context.canvas.width)
flakeArray[i].x = 0;
}
}
}
function Draw(){
context.save();
/*
// create a clipping region
bufferCanvasCtx.beginPath();
bufferCanvasCtx.fillStyle="black";
bufferCanvasCtx.fillRect(0,0,bufferCanvas.width,bufferCanvas.height);
bufferCanvasCtx.arc(bufferCanvas.width/2,bufferCanvas.height/2,bufferCanvas.height/3,0,2*Math.PI);
bufferCanvasCtx.clip();
*/
blank();
for (var i = 0; i < flakeArray.length; i++) {
bufferCanvasCtx.fillStyle = "white";
bufferCanvasCtx.fillRect(flakeArray[i].x,flakeArray[i].y,flakeArray[i].width,flakeArray[i].height);
}
// copy the entire rendered image from the buffer canvas to the visible one
context.drawImage(bufferCanvas, 0,0,bufferCanvas.width, bufferCanvas.height);
context.restore();
}
I suggest...
Use requestAnimationFrame instead of an interval.
Get rid of the flakeTimer by initializing flakes at negative heights.
Get rid of the double buffer and render everything on one canvas.
Update flake positions relative to the time passed between updates.
Encapsulate updating and drawing of flakes, e.g. by introducing a Snow class.
Simplify iterations such as for (var i = 0; i < flakeArray.length; i++) by using e.g. for (let flake of flakes).
Less reliance on globals, pass canvas context and dimensions as parameters.
function Flake(x, y, drift, speed, size) {
this.x = x;
this.y = y;
this.drift = drift;
this.speed = speed;
this.size = size;
}
class Snow {
constructor() {
this.flakes = [];
}
update(dt, width, height) {
for (let flake of this.flakes) {
flake.y += flake.speed * dt;
if (flake.y > height) {
flake.y = -flake.size;
}
flake.x += flake.drift * dt;
if (flake.x > width) {
flake.x = -flake.size;
}
}
}
draw(ctx) {
ctx.fillStyle = "white";
for (let flake of this.flakes) {
ctx.fillRect(flake.x, flake.y, flake.size, flake.size);
}
}
}
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
let snow = new Snow();
// Add flakes:
for (let i = 0; i < 200; ++i) {
snow.flakes.push(new Flake(
Math.round(Math.random() * canvas.width),
-i * 200,
Math.random() * 0.05,
Math.random() * 0.25 + 0.05,
Math.random() * 5 + 2)
);
}
// Animate:
let last = performance.now();
function frame(time) {
// Clear background:
ctx.fillStyle = "#330033";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Update & render snow:
snow.update(time - last, canvas.width, canvas.height);
snow.draw(ctx);
// Render text:
ctx.strokeStyle = "white";
ctx.font = "30px Arial";
ctx.strokeText("Hello World", 10, 50);
requestAnimationFrame(frame);
last = time;
}
requestAnimationFrame(frame);
<canvas id="canvas" width="600" height="300"></canvas>
Related
I am making a project where I need to make an html canvas with raining bowtie images. Here is my js:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
let img = new Image("images.png")
var ok2animate = true;
img.onload = function(){
function Drop() {
this.x = Math.random() * (canvas.width - 20);
this.y = -Math.random() * 20;
this.fallRate = Math.random()*.5+.5;
}
//
Drop.prototype.draw = function () {
// this.x;
// this.y;
ctx.drawImage(img, this.x, this.y)
return (this);
}
//
Drop.prototype.fall = function () {
this.y += this.fallRate;
return (this);
}
function animate() {
// request another animation frame
if (ok2animate) {
requestAnimationFrame(animate);
}
ctx.fillRect(0,0,canvas.width,canvas.height)
//ctx.clearRect(0, 0, canvas.width, canvas.height);
// make all drops fall and then redraw them
for (var i = 0; i < drops.length; i++) {
drops[i].fall().draw();
}
}
// an array of objects each representing 1 drop
var drops = [];
// add some test drops
for (var i = 0; i < 10; i++) {
drops.push(new Drop());
}
setInterval(function(){requestAnimationFrame(animate)}, 1000)
requestAnimationFrame(animate)
}
<canvas id="canvas" width=300 height=300></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
How do I make it so the images rain down at 1 second intervals?
(I have included the image file in the same folder)
I have tried using an image file from the web, using a bigger canvas, uwing other versions of jquery, but none of those worked.
Image creation
You can create a new image with the Image constructor. You already did that, however, the arguments that the constructor accepts are the width and the height of the image, not the url. You need the src property of the image to reference the image file.
And since you only use 1 single image, that isn't changing, you only have to create and load it once. You can reuse the same image for as many times as you want.
Starting the loop
Now, you need to start the loop after all the images (which in this case is one) has been loaded. Listen for the onload event on the image and call the setInterval function whenever the image is loaded.
setInterval will start of the rendering here, so there is no need to call requestAnimationFrame(animate) anywhere else.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// Create the image like this.
const image = new Image(100, 100);
image.src = "https://www.fillmurray.com/100/100";
function Drop() {
this.x = Math.random() * (canvas.width - 20);
this.y = -Math.random() * 20;
this.fallRate = Math.random() * 10 + 0.5;
}
Drop.prototype.draw = function() {
ctx.drawImage(image, this.x, this.y)
return this;
}
Drop.prototype.fall = function() {
this.y += this.fallRate;
return this;
}
const drops = [];
for (let i = 0; i < 10; i++) {
drops.push(new Drop());
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, canvas.width, canvas.height)
for (const drop of drops) {
drop.fall().draw();
}
}
image.onload = () => {
setInterval(function() {
requestAnimationFrame(animate)
}, 1000)
};
<canvas id="canvas" width=300 height=300></canvas>
What I'm trying to accomplish is simply to redraw some stuff on my canvas after it's resized. My problem is that after the canvas resizes, nothing seems to appearing on it. I tried to put some simple code for drawing a rectangle inside the resizing handler as well, and what I noticed is that the rectangle appears during the window resizing and afterwards the canvas is left blank. Below is my code.
var canvass = document.getElementById('zipper-canvas');
var ctx = canvass.getContext('2d');
var repetitions;
var firstRowX = 0; var firstRowY ;
var secondRowX = 5; var secondRowY ;
$(function() {
canvass.width = window.innerWidth;
repetitions = canvass.width / 10;
canvass.height = 30;
ctx = canvass.getContext('2d');
firstRowY = canvass.height / 2 - 3;
secondRowY = canvass.height / 2 + 1;
redraw();
//resize the canvass to fill browser window dynamically
window.addEventListener('resize', resizecanvass, false);
//for some reason i cannot draw on the canvas after it was resized
function resizecanvass() {
canvass.width = window.innerWidth;
repetitions = canvass.width / 10;
canvass.height = 30;
ctx = canvass.getContext('2d');
firstRowY = canvass.height / 2 - 3;
secondRowY = canvass.height / 2 + 1;
/**
* Your drawings need to be inside this function otherwise they will be reset when
* you resize the browser window and the canvass goes will be cleared.
*/
redraw();
}
function redraw() {
canvass = document.getElementById("zipper-canvas");
ctx = canvass.getContext('2d');
ctx.clearRect(0, 0, canvass.width, canvass.height);
console.log(repetitions);
for (var r = 0; r < repetitions; r++) {
ctx.beginPath();
ctx.rect(firstRowX, firstRowY, 5, 5);
ctx.fillStyle = "#edeeef";
ctx.fill();
ctx.closePath();
firstRowX = firstRowX + 10;
}
for (var r2 = 0; r2 < repetitions; r2++) {
ctx.beginPath();
ctx.rect(secondRowX, secondRowY, 5, 5);
ctx.fillStyle = "#a2a3a5";
ctx.fill();
ctx.closePath();
secondRowX = secondRowX + 10;
}
}
});
The problem is that you don't reset your firstRowX and secondRowX variables, so you end up drawing outside the canvas. I fixed that and also moved variable definitions where they belong.
var canvass = document.getElementById('zipper-canvas');
// Could be const
var firstRowX = 0;
var secondRowX = 5;
// Set the correct size on load
resizecanvass();
//resize the canvass to fill browser window dynamically
window.addEventListener('resize', resizecanvass, false);
//for some reason i cannot draw on the canvas after it was resized
function resizecanvass() {
canvass.width = window.innerWidth;
canvass.height = 30;
/**
* Your drawings need to be inside this function otherwise they will be reset when
* you resize the browser window and the canvass goes will be cleared.
*/
redraw();
}
function redraw() {
var ctx = canvass.getContext('2d');
ctx.clearRect(0, 0, canvass.width, canvass.height);
var repetitions = canvass.width / 10;
var firstRowY = canvass.height / 2 - 3;
var secondRowY = canvass.height / 2 + 1;
for (var r = 0; r < repetitions; r++) {
ctx.beginPath();
ctx.rect(firstRowX + r*10, firstRowY, 5, 5);
ctx.fillStyle = "#edeeef";
ctx.fill();
ctx.closePath();
}
for (var r2 = 0; r2 < repetitions; r2++) {
ctx.beginPath();
ctx.rect(secondRowX + r2*10, secondRowY, 5, 5);
ctx.fillStyle = "#a2a3a5";
ctx.fill();
ctx.closePath();
}
}
canvas {
outline:1px solid black;
}
body, html, canvas {
margin: 0px;padding:0px;
}
<canvas id='zipper-canvas'></canvas>
Here's a JSFiddle where you can test the resizing behavior:
https://jsfiddle.net/Darker/noxd4x64/1/
Note that the same effect could be achieved using CSS background without any programming.
I am trying to create multiple animated rectangles using Html Canvas with requestAnimationFrame. As for now, I managed to do exactly what I wanted with only one animated rectangle, but I can't find out how to create more rectangles that would simply be in line and follow each other with an equal distance.
Also, there's a random data (I, II or III) inside each rectangle.
Here's my actual code:
//Referencing canvas
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
//Make Canvas fullscreen and responsive
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize, false); resize();
//FPS
var framesPerSecond = 60;
//Default Y pos to center;
var yPos = canvas.height / 2;
//Default X pos offset
var xPos = -150;
//Speed (increment)
var speed = 2;
//Our array to store rectangles objects
var rectangles = [] ;
//Dynamic Number from database
var quote = ["I", "II", "III"];
//Random number for testing purpose
var rand = quote[Math.floor(Math.random() * quote.length)];
//Draw Rectangle
function drawRectangle () {
setTimeout(function() {
requestAnimationFrame(drawRectangle);
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Background color
ctx.fillStyle = "yellow";
//Position, size.
var rectWidth = 70;
var rectHeigth = 55;
ctx.fillRect(xPos,yPos,rectWidth,rectHeigth);
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "black";
//Data Layer
var dataLayer = ctx.fillText(rand,xPos+(rectWidth/2),yPos+(rectHeigth/2));
xPos += speed;
//Infinite loop for test
if (xPos > 1080) {
xPos = -150;
}
}, 1000 / framesPerSecond);
}
drawRectangle ();
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Moving Blocks</title>
<style>
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<canvas id="my-canvas"></canvas>
</body>
</html>
Animating arrays of objects.
For animations you are best of using a single render function that renders all the objects once a frame, rather than create a separate render frame per object.
As for the squares there are many ways that you can get them to do what you want. It is a little difficult to answer as what you want is not completely clear.
This answer will use a rectangle object that has everything needed to be rendered and move. The rectangles will be kept in an array and the main render function will update and render each rectangle in turn.
There will be a spawn function that creates rectangles untill the limit has been reached.
// constants up the top
const quote = ["I", "II", "III"];
// function selects a random Item from an array
const randItem = (array) => array[(Math.random() * array.length) | 0];
// array to hold all rectangles
const rectangles = [];
var maxRectangles = 20;
const spawnRate = 50; // number of frames between spawns
var spawnCountdown = spawnRate;
//Referencing canvas
const ctx = canvas.getContext("2d");
var w, h; // global canvas width and height.
resizeCanvas(); // size the canvas to fit the page
requestAnimationFrame(mainLoop); // this will start when all code below has been run
function mainLoop() {
// resize in the rendering frame as using the resize
// event has some issuse and this is more efficient.
if (w !== innerWidth || h !== innerHeight) {
resizeCanvas();
}
ctx.clearRect(0, 0, w, h);
spawnRectangle(); // spawns rectangles
updateAllRectangles(); // moves all active rectangles
drawAllRectangles(); // I will let you gues what this does... :P
requestAnimationFrame(mainLoop);
}
function resizeCanvas() {
w = canvas.width = innerWidth;
h = canvas.height = innerHeight;
// and reset any canvas constants
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
}
// function to spawn a rectangle
function spawnRectangle() {
if (rectangles.length < maxRectangles) {
if (spawnCountdown) {
spawnCountdown -= 1;
} else {
rectangles.push(
createRectangle({
y: canvas.height / 2, // set at center
text: randItem(quote),
dx: 2, // set the x speed
})
);
spawnCountdown = spawnRate;
}
}
}
// define the default rectangle
const rectangle = {
x: -40, // this is the center of the rectangle
y: 0,
dx: 0, // delta x and y are the movement per frame
dy: 0,
w: 70, // size
h: 55,
color: "yellow",
text: null,
textColor: "black",
draw() { // function to draw this rectangle
ctx.fillStyle = this.color;
ctx.fillRect(this.x - this.w / 2, this.y - this.h / 2, this.w, this.h);
ctx.fillStyle = this.textColor;
ctx.fillText(this.text, this.x, this.y);
},
update() { // moves the rectangle
this.x += this.dx;
this.y += this.dy;
if (this.x > canvas.width + this.w / 2) {
this.x = -this.w / 2;
// if the rectangle makes it to the other
// side befor all rectangles are spawnd
// then reduce the number so you dont get any
// overlap
if (rectangles.length < maxRectangles) {
maxRectangles = rectangles.length;
}
}
}
}
// creats a new rectangle. Setting can hold any unique
// data for the rectangle
function createRectangle(settings) {
return Object.assign({}, rectangle, settings);
}
function updateAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].update();
}
}
function drawAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].draw();
}
}
canvas {
position: absolute;
top: 0px;
left: 0px;
background-color: #131217;
}
body {
margin: 0;
overflow: hidden;
}
<canvas id="canvas"></canvas>
I'm trying to make water waves in Javascript on a canvas but there is something wrong.
My idea was making 3 waves with different colors but they overdraw each other.
I was not able figure out where the problem is.
<!DOCTYPE HTML>
<html>
<style>
<!-- 100% area -->
body, html {
height: 100%;
width: 100%;
}
</style>
<body>
<canvas id="myCanvas" ></canvas>
<script>
//get window size
var canvas = document.getElementById( "myCanvas" );
canvas.width = window.innerWidth; /// equal to window dimension
canvas.height = window.innerHeight;
// get the context
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext('2d');
// VARIABLES
var frameCount=0;
var N = 30;
var positionXTeiler= Math.floor(canvas.width/(N-N/2));
var size = 50;
var xOffset = 200;
var colors = [];
var amplitude = 200;
var wavesCount = 3;
var init = function()
{
colors.push("rgba(0,0,128,1)");
colors.push("rgba(0,0,255,1)");
colors.push("rgba(47,86,233,1)");
}
var draw = function() {
context.clearRect (0, 0, canvas.width, canvas.height);
for (i=0; i<N; i++) {
for (n=0; n<wavesCount; n++) {
var x = amplitude*Math.sin (frameCount*0.02+n*Math.PI/2);
context.save();
context.fillStyle = colors[n];
context.beginPath();
context.arc(positionXTeiler*i+x-xOffset,canvas.height-n*20,size,0,Math.PI*2,true);
context.closePath();
context.fill();
context.restore();
}
}
// count the frame and loop the animation
frameCount = frameCount+1;
requestAnimationFrame(draw);
};
// start the loop
init();
draw();
</script>
</body>
</html>
My result should look like that + with moving
Loop the waves and, inside, loop the circles (i.e. invert the two loops).
The goal is to draw all circles of a wave before moving to the next. This way you are making sure that the circles of a wave are drawn on top of the circles of the previous one.
Also, you may want to consider using a time-based increment instead of a frame count. Animation frames are not guaranteed to be regular and their rate depends of the user's system.
//get window size
var canvas = document.getElementById( "myCanvas" );
canvas.width = window.innerWidth; /// equal to window dimension
canvas.height = window.innerHeight;
// get the context
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext('2d');
// VARIABLES
var frameCount=0;
var N = 30;
var positionXTeiler= Math.floor(canvas.width/(N-N/2));
var size = 50;
var xOffset = 200;
var colors = [];
var amplitude = 200;
var wavesCount = 3;
var init = function()
{
colors.push("rgba(0,0,128,1)");
colors.push("rgba(0,0,255,1)");
colors.push("rgba(47,86,233,1)");
}
var draw = function() {
context.clearRect (0, 0, canvas.width, canvas.height);
for (n=wavesCount-1; n>=0; n--) {
for (i=0; i<N; i++) {
var x = amplitude*Math.sin (frameCount*0.02+n*Math.PI/2);
context.save();
context.fillStyle = colors[n];
context.beginPath();
context.arc(positionXTeiler*i+x-xOffset,canvas.height-n*20,size,0,Math.PI*2,true);
context.closePath();
context.fill();
context.restore();
}
}
// count the frame and loop the animation
frameCount = frameCount+1;
requestAnimationFrame(draw);
};
// start the loop
init();
draw();
<!-- 100% area -->
body, html {
height: 100%;
width: 100%;
}
<!DOCTYPE HTML>
<html>
<body>
<canvas id="myCanvas" ></canvas>
</body>
</html>
the code below animates a marquee line. the animation starts pretty fast but slows down noticeably over time. Please help me figure out why. The same code is here: http://jsbin.com/aleqef/
EDIT: I do not create any new objects during my animation loop, everything seems to be cached, that is, the patterns, the context, etc. I just do not see anything suspicious. Looks like a memory problem but I am not sure why.
var data = [
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAMAAADXEh96AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAGUExURQAAAP///6XZn90AAAAcSURBVHjaYmBgYGBkZAQSYAjlgUXgYhAeQIABAAGkABPpfLrFAAAAAElFTkSuQmCC",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAMAAADXEh96AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAGUExURQAAAP///6XZn90AAAAeSURBVHjaYmBkAEEgYGRkBBEgFgMDRBTGA8sBBBgAAaQAE03fiAgAAAAASUVORK5CYII=",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAMAAADXEh96AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAGUExURQAAAP///6XZn90AAAAdSURBVHjaYmBgZGRkYGAAY0YGCITxoHIQUYAAAwABpAATvSsONQAAAABJRU5ErkJggg=="
];
var context = document.getElementById("canvas").getContext("2d");
var count = data.length;
var patterns = [];
var pattern = 0;
function onload(e) {
patterns.push(context.createPattern(e.target, "repeat"));
}
function draw() {
for (var angle = 0; angle < 360; angle += 5) {
var rad = (angle * Math.PI) / 180;
var x = 200 * Math.cos(rad);
var y = 200 * Math.sin(rad);
context.moveTo(0, 0);
context.lineTo(x, y);
}
}
function animate() {
window.setTimeout(animate, 1000 / 60);
if (patterns.length > 0) {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
draw();
context.lineWidth = 1;
context.strokeStyle = patterns[pattern];
context.stroke();
pattern = (pattern + 1) % patterns.length;
}
}
for (var i = 0; i < count; i++) {
var image = new Image();
image.onload = onload;
image.src = data[i];
}
animate();
Try this change (beginPath())
if (patterns.length > 0) {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.beginPath();
draw();
Link http://jsbin.com/aleqef/4/edit#preview