A data frame has two indices: the first is for the rows, the second for the columns. So, in order to get the the 8 out of your data frame, you would have to type df[2,3], since it is the element in row 2 and column 3.
Now regarding your examples. Your first try (df[df$x <= 2, ,]), picks all the rows, where the value in df$x is smaller than two (which is, I assume, exactly what you want). The second comma is not necessary though and you could just as well write df[df$x <= 2,]. The reason that the second comma works, is that you could pass additional arguments to the [ function, which you could add after the second comma.
Your other attempt (df[,df$x <= 2,]) does not lead to an error, because your data frame happens to have the same number of rows and columns. So what happens is this:
df$x <= 2 actually returns the vector c(TRUE, TRUE, FALSE), because the first two values of df$x are 1 and 2, such that the condition is true, while the third element is 3 such that the condition fails. If you now type df[df$x <= 2,], you will get the first and second row of the data frame, because you are selecting in the first index, which is for rows. If, on the other hand, you type df[,df$x <= 2] you will get the first and second column of the data frame, because the second index selects for columns.
The important point is that the boolean vector returned by df$x <= 2 does not know that it has been obtained by running a condition over the rows of the data frame. It is just a vector with three elements and can be used wherever a vector with three elements is expected. Thus there is no problem in using the same vector to index for rows or columns, as long as the number of rows and columns is three.