生成 CSS 个 3D 模型
Generating CSS 3D Mockups
我正在尝试制作一些笔记本电脑 mockups from scratch with CSS 3D transforms (pen),但我发现这比我想象的要难。这是我弄乱的代码:
transform: scaleX(1) scaleY(1) scaleZ(1)
rotateX(48deg) rotateY(-35deg) rotateZ(37deg)
translateX(360px) translateY(103px) translateZ(0px)
skewX(0) skewY(0);
我还没有弄清楚该怎么做,但我相信如果我们可以将图像的角放在笔记本电脑图像相应角的顶部,那么它看起来会很逼真。我错了吗?
我认为这可以通过一个函数来完成,该函数接受 5 个输入(源图像的矩形与目标(笔记本电脑)图像四个角的坐标的比率)和 returns 一个包含有效对象的对象CSS 图像分层属性。
您对如何实现有任何见解吗?
这道数学题有门道:
它导致了这些很好的实例:
- https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/
- https://tympanus.net/codrops/2014/11/21/perspective-mockup-slideshow/
这是从 CoffeeScript 移植到这个答案的文章中的笔 JavaScript:
// Original was CoffeeScript at https://codepen.io/fta/pen/ifnqH?editors=0010.
// This is ported back to plain JavaScript. It could be cleaned up!
{
const $ = jQuery;
function getTransform (from, to) {
console.assert(from.length === to.length && to.length === 4);
const A = []; // 8x8
for (let i = 0; i < 4; i++) {
A.push([from[i].x, from[i].y, 1, 0, 0, 0, -from[i].x * to[i].x, -from[i].y * to[i].x]);
A.push([0, 0, 0, from[i].x, from[i].y, 1, -from[i].x * to[i].y, -from[i].y * to[i].y]);
}
const b = []; // 8x1
for (let i = 0; i < 4; i++) {
b.push(to[i].x);
b.push(to[i].y);
}
// Solve A * h = b for h
const h = numeric.solve(A, b);
const H = [[h[0], h[1], 0, h[2]], [h[3], h[4], 0, h[5]], [0, 0, 1, 0], [h[6], h[7], 0, 1]];
// Sanity check that H actually maps `from` to `to`
for (let i = 0; i < 4; i++) {
const lhs = numeric.dot(H, [from[i].x, from[i].y, 0, 1]);
const k_i = lhs[3];
const rhs = numeric.dot(k_i, [to[i].x, to[i].y, 0, 1]);
console.assert(numeric.norm2(numeric.sub(lhs, rhs)) < 1e-9, "Not equal:", lhs, rhs);
}
return H;
};
function applyTransform(element, originalPos, targetPos, callback) {
// All offsets were calculated relative to the document
// Make them relative to (0, 0) of the element instead
const from = (function() {
const results = [];
for (let k = 0, len = originalPos.length; k < len; k++) {
const p = originalPos[k];
results.push({
x: p[0] - originalPos[0][0],
y: p[1] - originalPos[0][1]
});
}
return results;
})();
const to = (function() {
const results = [];
for (let k = 0, len = targetPos.length; k < len; k++) {
const p = targetPos[k];
results.push({
x: p[0] - originalPos[0][0],
y: p[1] - originalPos[0][1]
});
}
return results;
})();
// Solve for the transform
const H = getTransform(from, to);
// Apply the matrix3d as H transposed because matrix3d is column major order
// Also need use toFixed because css doesn't allow scientific notation
$(element).css({
'transform': `matrix3d(${((function() {
const results = [];
for (let i = 0; i < 4; i++) {
results.push((function() {
const results1 = [];
for (let j = 0; j < 4; j++) {
results1.push(H[j][i].toFixed(20));
}
return results1;
})());
}
return results;
})()).join(',')})`,
'transform-origin': '0 0'
});
return typeof callback === "function" ? callback(element, H) : void 0;
};
function makeTransformable(selector, callback) {
return $(selector).each(function(i, element) {
$(element).css('transform', '');
// Add four dots to corners of `element` as control points
const controlPoints = (function() {
const ref = ['left top', 'left bottom', 'right top', 'right bottom'];
const results = [];
for (let k = 0, len = ref.length; k < len; k++) {
const position = ref[k];
results.push($('<div>').css({
border: '10px solid black',
borderRadius: '10px',
cursor: 'move',
position: 'absolute',
zIndex: 100000
}).appendTo('body').position({
at: position,
of: element,
collision: 'none'
}));
}
return results;
})();
// Record the original positions of the dots
const originalPos = (function() {
const results = [];
for (let k = 0, len = controlPoints.length; k < len; k++) {
const p = controlPoints[k];
results.push([p.offset().left, p.offset().top]);
}
return results;
})();
// Transform `element` to match the new positions of the dots whenever dragged
$(controlPoints).draggable({
start: () => {
return $(element).css('pointer-events', 'none'); // makes dragging around iframes easier
},
drag: () => {
return applyTransform(element, originalPos, (function() {
const results = [];
for (let k = 0, len = controlPoints.length; k < len; k++) {
const p = controlPoints[k];
results.push([p.offset().left, p.offset().top]);
}
return results;
})(), callback);
},
stop: () => {
applyTransform(element, originalPos, (function() {
const results = [];
for (let k = 0, len = controlPoints.length; k < len; k++) {
const p = controlPoints[k];
results.push([p.offset().left, p.offset().top]);
}
return results;
})(), callback);
return $(element).css('pointer-events', 'auto');
}
});
return element;
});
};
makeTransformable('.box', function(element, H) {
console.log($(element).css('transform'));
return $(element).html($('<table>').append($('<tr>').html($('<td>').text('matrix3d('))).append((function() {
const results = [];
for (let i = 0; i < 4; i++) {
results.push($('<tr>').append((function() {
const results1 = [];
for (let j = 0; j < 4; j++) {
results1.push($('<td>').text(H[j][i] + ((i === j && j === 3) ? '' : ',')));
}
return results1;
})()));
}
return results;
})()).append($('<tr>').html($('<td>').text(')'))));
});
}
.box {
margin: 20px;
padding: 10px;
height: 150px;
width: 500px;
border: 1px solid black;
}
<div class="box">
Drag the points to transform the box!
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/numeric/1.2.6/numeric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js"></script>
我正在尝试制作一些笔记本电脑 mockups from scratch with CSS 3D transforms (pen),但我发现这比我想象的要难。这是我弄乱的代码:
transform: scaleX(1) scaleY(1) scaleZ(1)
rotateX(48deg) rotateY(-35deg) rotateZ(37deg)
translateX(360px) translateY(103px) translateZ(0px)
skewX(0) skewY(0);
我还没有弄清楚该怎么做,但我相信如果我们可以将图像的角放在笔记本电脑图像相应角的顶部,那么它看起来会很逼真。我错了吗?
我认为这可以通过一个函数来完成,该函数接受 5 个输入(源图像的矩形与目标(笔记本电脑)图像四个角的坐标的比率)和 returns 一个包含有效对象的对象CSS 图像分层属性。
您对如何实现有任何见解吗?
这道数学题有门道:
它导致了这些很好的实例:
- https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/
- https://tympanus.net/codrops/2014/11/21/perspective-mockup-slideshow/
这是从 CoffeeScript 移植到这个答案的文章中的笔 JavaScript:
// Original was CoffeeScript at https://codepen.io/fta/pen/ifnqH?editors=0010.
// This is ported back to plain JavaScript. It could be cleaned up!
{
const $ = jQuery;
function getTransform (from, to) {
console.assert(from.length === to.length && to.length === 4);
const A = []; // 8x8
for (let i = 0; i < 4; i++) {
A.push([from[i].x, from[i].y, 1, 0, 0, 0, -from[i].x * to[i].x, -from[i].y * to[i].x]);
A.push([0, 0, 0, from[i].x, from[i].y, 1, -from[i].x * to[i].y, -from[i].y * to[i].y]);
}
const b = []; // 8x1
for (let i = 0; i < 4; i++) {
b.push(to[i].x);
b.push(to[i].y);
}
// Solve A * h = b for h
const h = numeric.solve(A, b);
const H = [[h[0], h[1], 0, h[2]], [h[3], h[4], 0, h[5]], [0, 0, 1, 0], [h[6], h[7], 0, 1]];
// Sanity check that H actually maps `from` to `to`
for (let i = 0; i < 4; i++) {
const lhs = numeric.dot(H, [from[i].x, from[i].y, 0, 1]);
const k_i = lhs[3];
const rhs = numeric.dot(k_i, [to[i].x, to[i].y, 0, 1]);
console.assert(numeric.norm2(numeric.sub(lhs, rhs)) < 1e-9, "Not equal:", lhs, rhs);
}
return H;
};
function applyTransform(element, originalPos, targetPos, callback) {
// All offsets were calculated relative to the document
// Make them relative to (0, 0) of the element instead
const from = (function() {
const results = [];
for (let k = 0, len = originalPos.length; k < len; k++) {
const p = originalPos[k];
results.push({
x: p[0] - originalPos[0][0],
y: p[1] - originalPos[0][1]
});
}
return results;
})();
const to = (function() {
const results = [];
for (let k = 0, len = targetPos.length; k < len; k++) {
const p = targetPos[k];
results.push({
x: p[0] - originalPos[0][0],
y: p[1] - originalPos[0][1]
});
}
return results;
})();
// Solve for the transform
const H = getTransform(from, to);
// Apply the matrix3d as H transposed because matrix3d is column major order
// Also need use toFixed because css doesn't allow scientific notation
$(element).css({
'transform': `matrix3d(${((function() {
const results = [];
for (let i = 0; i < 4; i++) {
results.push((function() {
const results1 = [];
for (let j = 0; j < 4; j++) {
results1.push(H[j][i].toFixed(20));
}
return results1;
})());
}
return results;
})()).join(',')})`,
'transform-origin': '0 0'
});
return typeof callback === "function" ? callback(element, H) : void 0;
};
function makeTransformable(selector, callback) {
return $(selector).each(function(i, element) {
$(element).css('transform', '');
// Add four dots to corners of `element` as control points
const controlPoints = (function() {
const ref = ['left top', 'left bottom', 'right top', 'right bottom'];
const results = [];
for (let k = 0, len = ref.length; k < len; k++) {
const position = ref[k];
results.push($('<div>').css({
border: '10px solid black',
borderRadius: '10px',
cursor: 'move',
position: 'absolute',
zIndex: 100000
}).appendTo('body').position({
at: position,
of: element,
collision: 'none'
}));
}
return results;
})();
// Record the original positions of the dots
const originalPos = (function() {
const results = [];
for (let k = 0, len = controlPoints.length; k < len; k++) {
const p = controlPoints[k];
results.push([p.offset().left, p.offset().top]);
}
return results;
})();
// Transform `element` to match the new positions of the dots whenever dragged
$(controlPoints).draggable({
start: () => {
return $(element).css('pointer-events', 'none'); // makes dragging around iframes easier
},
drag: () => {
return applyTransform(element, originalPos, (function() {
const results = [];
for (let k = 0, len = controlPoints.length; k < len; k++) {
const p = controlPoints[k];
results.push([p.offset().left, p.offset().top]);
}
return results;
})(), callback);
},
stop: () => {
applyTransform(element, originalPos, (function() {
const results = [];
for (let k = 0, len = controlPoints.length; k < len; k++) {
const p = controlPoints[k];
results.push([p.offset().left, p.offset().top]);
}
return results;
})(), callback);
return $(element).css('pointer-events', 'auto');
}
});
return element;
});
};
makeTransformable('.box', function(element, H) {
console.log($(element).css('transform'));
return $(element).html($('<table>').append($('<tr>').html($('<td>').text('matrix3d('))).append((function() {
const results = [];
for (let i = 0; i < 4; i++) {
results.push($('<tr>').append((function() {
const results1 = [];
for (let j = 0; j < 4; j++) {
results1.push($('<td>').text(H[j][i] + ((i === j && j === 3) ? '' : ',')));
}
return results1;
})()));
}
return results;
})()).append($('<tr>').html($('<td>').text(')'))));
});
}
.box {
margin: 20px;
padding: 10px;
height: 150px;
width: 500px;
border: 1px solid black;
}
<div class="box">
Drag the points to transform the box!
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/numeric/1.2.6/numeric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js"></script>