You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用PHP+MySQL存储Fabric.js画布图片属性实现项目保存

Fabric.js 画布状态持久化实现方案

你之前想的存储元素属性的方向是对的,但不要只存width、height、left、top,Fabric中元素的大小变化本质是缩放属性控制的,额外存储缩放比例、旋转角度、翻转状态才能100%还原画布状态。整体实现分5个部分:改造数据库字段存储元素状态、前端加载时读取已存状态、监听用户操作自动同步状态到数据库、编写后端保存接口、实现画布存图功能。


第一步:改造数据库表结构

先给你的project_letters表新增以下字段,用来存储每个图片元素的状态:

  • left DECIMAL(10,2) DEFAULT 10 :元素X轴坐标
  • top DECIMAL(10,2) DEFAULT 10 :元素Y轴坐标
  • scale_x DECIMAL(5,3) DEFAULT 0.1 :元素X轴缩放比例
  • scale_y DECIMAL(5,3) DEFAULT 0.1 :元素Y轴缩放比例
  • flip_x TINYINT(1) DEFAULT 0 :是否水平翻转,1为是0为否
  • flip_y TINYINT(1) DEFAULT 0 :是否垂直翻转,1为是0为否
  • angle DECIMAL(6,2) DEFAULT 0 :元素旋转角度

第二步:改造前端图片加载逻辑

把你原来PHP循环输出addImage函数的部分替换成下面的代码,加载图片时直接读取数据库中已存的状态值,没有历史值时用默认参数:

<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/510/fabric.min.js"></script>
<canvas id="canvas"> </canvas>
<script>
// 初始化画布
var canvas = new fabric.Canvas('canvas', {
    backgroundColor: '#fff'
});

<?php
// 改用预处理查询,避免SQL注入
$stmt = $Connection->prepare("SELECT * FROM products p, project_letters l WHERE l.product_id =p.product_id and l.project_id = ? ORDER BY l.letter DESC");
$stmt->bind_param("i", $p);
$stmt->execute();
$mres = $stmt->get_result();
$NumOfimages = $mres->num_rows;

if ($NumOfimages > 0) {
    while ($row= $mres->fetch_assoc()) {
        // 读取已存状态,没有则用默认值
        $pl_id = $row['pl_id'];
        $img_path = 'images/products/'.$row['image'];
        $left = $row['left'] ?? 10;
        $top = $row['top'] ?? 10;
        $scaleX = $row['scale_x'] ?? 0.1;
        $scaleY = $row['scale_y'] ?? 0.1;
        $flipX = $row['flip_x'] ?? 0;
        $flipY = $row['flip_y'] ?? 0;
        $angle = $row['angle'] ?? 0;
?>
// 加载图片时直接绑定已存状态
fabric.Image.fromURL('<?php echo $img_path; ?>', function (img) {
    img.set({
        pl_id: <?php echo $pl_id; ?>, // 绑定自定义主键,后续更新时匹配数据库记录
        left: <?php echo $left; ?>,
        top: <?php echo $top; ?>,
        scaleX: <?php echo $scaleX; ?>,
        scaleY: <?php echo $scaleY; ?>,
        flipX: <?php echo $flipX ? 'true' : 'false'; ?>,
        flipY: <?php echo $flipY ? 'true' : 'false'; ?>,
        angle: <?php echo $angle; ?>
    });
    canvas.add(img);
}, {
    crossOrigin: 'anonymous' // 防止导出图片时跨域污染
});
<?php }}?>

canvas.setWidth(<?php echo $WIDTH ?>);
canvas.setHeight(<?php echo $HEIGHT ?>);

第三步:监听元素操作自动保存状态

在画布初始化、所有图片加载完成的代码后面,添加Fabric事件监听,用户结束拖动、缩放、旋转操作时,自动把元素最新状态发送给后端保存:

// 监听元素修改事件(拖动、缩放、旋转结束松开鼠标时触发)
canvas.on('object:modified', function(e) {
    const activeObj = e.target;
    // 跳过非图片元素
    if (!activeObj.pl_id) return;

    // 收集当前元素状态
    const formData = new FormData();
    formData.append('pl_id', activeObj.pl_id);
    formData.append('left', activeObj.left.toFixed(2));
    formData.append('top', activeObj.top.toFixed(2));
    formData.append('scale_x', activeObj.scaleX.toFixed(3));
    formData.append('scale_y', activeObj.scaleY.toFixed(3));
    formData.append('flip_x', activeObj.flipX ? 1 : 0);
    formData.append('flip_y', activeObj.flipY ? 1 : 0);
    formData.append('angle', activeObj.angle.toFixed(2));

    // 发送请求到后端
    fetch('save_element_state.php', {
        method: 'POST',
        body: formData
    }).catch(err => console.error('状态保存失败', err));
});

第四步:编写后端状态保存接口

新建save_element_state.php文件,接收前端传的元素参数,更新到数据库:

<?php
require_once '你的数据库连接文件路径.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 参数类型校验
    $pl_id = intval($_POST['pl_id']);
    $left = floatval($_POST['left']);
    $top = floatval($_POST['top']);
    $scale_x = floatval($_POST['scale_x']);
    $scale_y = floatval($_POST['scale_y']);
    $flip_x = intval($_POST['flip_x']);
    $flip_y = intval($_POST['flip_y']);
    $angle = floatval($_POST['angle']);

    // 预处理更新,防SQL注入
    $stmt = $Connection->prepare("UPDATE project_letters SET `left` = ?, `top` = ?, scale_x = ?, scale_y = ?, flip_x = ?, flip_y = ?, angle = ? WHERE pl_id = ?");
    $stmt->bind_param("dddddddi", $left, $top, $scale_x, $scale_y, $flip_x, $flip_y, $angle, $pl_id);
    $stmt->execute();
    echo 'ok';
}
?>

第五步:画布导出为图片

你原有的手动下载图片逻辑可以正常使用,如果需要把画布图片自动保存到服务器,添加对应逻辑即可:

// 原有手动下载功能保留
var link = document.createElement('a');
link.innerHTML = '导出为图片';
link.addEventListener('click', function(ev) {
    link.href = canvas.toDataURL();
    link.download = "<?php echo $project['project_title'] ?>.png";
}, false);
document.body.appendChild(link);

// 新增:保存画布到服务器
var saveServerBtn = document.createElement('a');
saveServerBtn.innerHTML = '保存画布到服务器';
saveServerBtn.addEventListener('click', function(ev) {
    ev.preventDefault();
    const base64 = canvas.toDataURL('image/png');
    const formData = new FormData();
    formData.append('img_base64', base64);
    formData.append('project_id', <?php echo $p; ?>);
    
    fetch('save_canvas_img.php', {
        method: 'POST',
        body: formData
    }).then(()=>alert('画布保存成功'));
}, false);
document.body.appendChild(saveServerBtn);
</script>

对应的save_canvas_img.php后端代码:

<?php
require_once '你的数据库连接文件路径.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $project_id = intval($_POST['project_id']);
    $base64 = $_POST['img_base64'];
    // 解析base64图片数据
    $base64 = str_replace('data:image/png;base64,', '', $base64);
    $base64 = str_replace(' ', '+', $base64);
    $imgData = base64_decode($base64);
    // 生成保存路径
    $savePath = 'uploads/canvas/'.$project_id.'_'.time().'.png';
    file_put_contents($savePath, $imgData);
    // 可把路径存到项目表的封面字段
    $stmt = $Connection->prepare("UPDATE projects SET cover_path = ? WHERE project_id = ?");
    $stmt->bind_param("si", $savePath, $project_id);
    $stmt->execute();
    echo 'ok';
}
?>

注意事项

  • 不要单独存储width/height属性:Fabric会保留图片原始宽高,所有尺寸变化通过缩放属性控制,只存宽高会丢失旋转、翻转、等比缩放的状态,还原时容易变形
  • 不要用高频触发事件做保存:object:moving/object:scaling这类事件会在用户拖动过程中几十毫秒触发一次,会产生大量无效请求,用object:modified事件只会在用户操作结束松开鼠标时触发一次,性能最优
  • 所有数据库操作用预处理语句:你原有代码中直接把$p变量拼接到SQL里存在SQL注入风险,所有用户传入的参数必须通过bind_param绑定后再执行查询
  • 注意图片跨域问题:如果后续用到外链图片,需要给图片服务器配置跨域头,同时在fromURL时加crossOrigin: 'anonymous'参数,否则导出图片时会出现画布污染报错

内容的提问来源于stack exchange,提问作者Danny Mak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 01:24:23