动态

详情 返回 返回

使用 fabric.js 開發移動端 H5 圖片編輯器 - 动态 详情

大家好,我是開源圖片編輯器的 https://github.com/ikuaitu/vue-fabric-editor 的作者,它是一款基於 PC 版本的開源圖片編輯器。

最近很多開發者諮詢,是否可以將開源圖片編輯器改造為一款適用於移動端的 H5 版本圖片編輯器,最近 H5 版本的圖片編輯器剛剛上線,就將實現思路和產品細節整理成筆記分享出來,供大家參考。
0.png

基礎

開源的圖片編輯器的基本功能都有了,例如切換模板、添加元素、自定義字體等,不過相較於移動端的交互會有很大的差異,做了很多改造,這次筆記主要分享一下移動端圖片編輯器實現思路和細節
1.png
2.png
3.png

大綱

  1. 切換模板
  2. 添加圖片
  3. 添加組合元素
  4. 設置背景色
  5. 修改畫布尺寸
  6. 快捷菜單
  7. 屬性工具條
  8. 特效字體
  9. 切換字體
  10. 輸入文字
  11. 文字排版
  12. 邊框
  13. 陰影
  14. 下載圖片
注:部分代碼示例為封裝後的代碼,非 fabric.js 原生方法。

1. 切換模板

切換模板.gif
編輯器基於 fabric.js 開發,所有的模板都是以 json 的格式存儲,切換模板只需要請求詳情接口,將 json 格式的數據調添加到畫布當中即可,需要注意的點是需要將模板中使用的字體名稱,並加載字體文件後再進行渲染,否則字體樣式沒辦法正常渲染。

const loadInfo = async (res: any) => {
  const info = res.data
  templName.value = info.name;
  await canvasEditor.getFontList(JSON.stringify(info.json));
  canvasEditor.loadJSON(JSON.stringify(info.json), () => LoadingPlugin(false));
};

2. 添加圖片

添加圖片.gif
fabric.js 中添加圖片提供了很多種方法,我們使用通過最簡單的fabric.Image.fromURL即可,另外,經常有圖片尺寸大於畫布的情況,還需要將圖片按畫布寬度的一般進行縮放,更方便用户操作。

const toEditor = async (e: MouseEvent) => {
  visible.value = false
  LoadingPlugin(true)
  const item = await canvasEditor.createImgByElement(e.target as HTMLImageElement)
  await canvasEditor.addBaseType(item, { scale: true })
  LoadingPlugin(false)
}

3. 添加組合元素

添加組合元素.gif

fabric.js 支持將單個元素按照 JSON 格式導出/導入,我們將導出的數據存儲在數據庫中的,導入時按元素類型導入即可,需要獲取 JSON 中元素的類型,並作為方法名調用,同樣需要在導入前做字體加載,倒入後做縮放。

const capitalizeFirstLetter = (str: string) => {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

const toEditor = async (item: ItemProps) => {
  visible.value = false
  LoadingPlugin(true)
  await canvasEditor.downFontByJSON(JSON.stringify(item.json));
  const el = JSON.parse(JSON.stringify(item.json));
  const elType = capitalizeFirstLetter(el.type);
  new fabric[elType].fromObject(el, (fabricEl: fabric.Object) => {
    canvasEditor.dragAddItem(fabricEl);
    LoadingPlugin(false)
  });
}

4. 設置背景色

背景色設置.gif
設置背景色較為簡單,按照 fabric.js 的 API 設置顏色即可,需要注意的是大部分 PC 端的顏色組件並不適配移動端 H5 的場景,不支持 touch 事件,我們使用了 @jaames/iro這個組件,它在移動端表現出色,完全適配我們的場景,而且它的 API 很靈活,我們將它封裝成一個通用的顏色組件,在多處調用。

<template>
  <div ref="pickerContainer">
  </div>
</template>

<script setup lang="ts">
import iro from '@jaames/iro';
const emit = defineEmits(['update:modelValue', 'change']);

const props = defineProps({
  modelValue: {
    type: String,
    default: '#000000'
  },
  width: {
    type: Number,
    default: 200
  }
});

const pickerContainer = ref<HTMLButtonElement | string>('');
let colorPicker: any = null;

onMounted(() => {
  // 創建iro.js顏色選擇器
  colorPicker = iro.ColorPicker(pickerContainer.value, {
    width: props.width,
    color: props.modelValue,
    borderWidth: 1,
    borderColor: "#fff",
    layoutDirection: 'horizontal',
    layout: [
      {
        component: iro.ui.Slider,
        options: {
          id: 'hue-slider',
          sliderType: 'hue'
        }
      },
      {
        component: iro.ui.Box,
      },
      {
        component: iro.ui.Slider,
        options: {
          sliderType: 'alpha'
        }
      }
    ]
  });

  // 監聽顏色變化事件併發射自定義事件
  colorPicker.on('color:change', (color: any) => {
    const rgbaString = color.rgbaString;
    emit('update:modelValue', rgbaString);
    emit('change', rgbaString);
  });
});

</script>

5. 修改畫布尺寸

尺寸修改.gif
日常使用圖片編輯器都有修改畫布尺寸的需要,在開源項目中已經封裝好了相應的方法,直接調用即可,需要注意的是,當修改尺寸彈框彈出時,為了達到所見即所得的效果,要避免彈框遮擋畫布,其他屬性修改同理。


const resizeEditor = async () => {
    await nextTick()
    const editorWorkspase = document.querySelector('#workspace') as HTMLElement
    const popElement = document.querySelector('.my-editor-popup') as HTMLElement
    const headerElement = document.querySelector('.t-navbar') as HTMLElement
    if (popElement) {
      editorWorkspase.style.height = `calc(100vh - ${popElement?.offsetHeight + headerElement?.offsetHeight || 0}px)`
    } else {
      editorWorkspase.style.height = ''
    }
  }

6. 快捷菜單

快捷工具條.gif

很多快捷操作需要能夠讓用户快速找的並完成操作,我們為元素添加了快捷菜單功能,避免讓一些簡單的操作讓用户在底部菜單欄點來點去,當選中元素時自動展示,取消選中時隱藏即可,需要注意的是在快捷菜單並不總是在元素上方,快捷菜單應該根據元素位置和畫布的尺寸進行定位,當菜單超出畫布區域時我們要及時調整菜單位置;另外 當屬性彈框出現,畫布尺寸變化時,需要同步修改菜單位置。

// 更新位置信息
const upDatePosition = async () => {
  const activeObject = canvasEditor.canvas.getActiveObject();
  if (activeObject) {
    canvasEditor.canvas.renderAll();
    fixLeft.value = 10;
    fixTop.value = 10;
    await nextTick();
    isIncluded(activeObject);
    await nextTick();
  }
}

// 監聽選中對象變化更新位置信息
getObjectAttr(upDatePosition)
canvasEditor.canvas.on('selection:updated', upDatePosition)
canvasEditor.canvas.on('mouse:move', upDatePosition)
canvasEditor.on('workspaceAutoEvent', upDatePosition)

7. 屬性工具條

屬性工具條.gif
參考了其他圖片編輯器,部分屬性在點擊元素後才會出現可修改選項,取消選中時便隱藏選項,另外 選中的元素不同,可修改選項也不同,這是一個在移動端做複雜圖片編輯器中非常棒的一個交互。

我們封裝了通用的選中類型和方法,針對每個屬性組件單獨設置隱藏/展示。
隱藏展示代碼.png

8. 特效字體

特效字體.gif

特效字體主要是文字元素的顏色、邊框、陰影的組合,我們將來文字設置樣式後的 JSON 導出並保存在數據庫中,當選中某一個特效時,將屬性按 JSON 中的數據設置給元素即可。

const setStyle = (item: ImgItem) => {
  const activeObject = canvasEditor.canvas.getActiveObjects()[0];
  if (activeObject) {
    const values = toRaw(item.json);
    const keys = ['fill', 'stroke', 'strokeWidth', 'shadow', 'strokeLineCap'];
    activeObject.set('paintFirst', 'stroke');
    keys.forEach((key) => {
      activeObject.set(key, values[key]);
      if (key === 'fill' && typeof values[key] != 'string') {
        activeObject.set(key, new fabric.Gradient(values[key]));
      }
    });
    canvasEditor.canvas.renderAll();
  }
};

9. 切換字體

字體切換.gif

修改字體只需要調用 fabric.js 元素的fontFamily屬性即可,在修改之前要確保字體加載完成。


const changeCommon = async (key: string, value: any) => {
  const activeObject = canvasEditor.canvas.getActiveObjects()[0];
  if (activeObject) {
    LoadingPlugin(true);
    baseAttr.fontFamily = value;
    try {
      await canvasEditor.loadFont(value)
    } catch (error) {
      console.log(error)
    }
    LoadingPlugin(false);
    activeObject && activeObject.set(key, value);
    canvasEditor.canvas.renderAll();
  }
};

10. 輸入文字

文字輸入.gif
fabric.js 可直接雙擊文字元素進行修改,不過在移動端這種交互並不醒目,我們單獨為文本元素進行了修改,選中元素後,再次點擊時彈出輸入框,可以在底部菜單欄點擊按鈕進行修改。

11. 文字排版

字體排版.gif
文字排版較為簡單,我們只需要按照 fabric.js 的文字屬性對文字進行屬性設置即可,如 fontSize、lineHeight、charSpacing 等。

// 屬性值
const baseAttr = reactive({

  fontSize: 0,
  lineHeight: 0,
  charSpacing: 0,
  textAlign: '',

  fontWeight: '',
  fontStyle: '',

  underline: false,
  linethrough: false,
  overline: false,
});

12. 邊框

邊框.gif

邊框樣式和文字樣式類似,配合顏色組件可以很快捷的實現功能。

// 屬性值
const baseAttr = reactive({
  stroke: '#fff',
  strokeWidth: 0,
  strokeDashArray: [],
});

13. 陰影

陰影.gif
引用屬性主要是元素的 shadow 子屬性的修改,代碼如下:

// 屬性值
const baseAttr = reactive({
  shadow: {
    color: '#fff',
    blur: 0,
    offsetX: 1,
    offsetY: 1,
  }
});


// 通用屬性改變
const changeCommon = () => {
  const activeObject = canvasEditor.canvas.getActiveObjects()[0];
  if (activeObject) {
    activeObject.set('shadow', new fabric.Shadow(baseAttr.shadow));
    canvasEditor.canvas.renderAll();
  }
};

14. 下載圖片

fabric.js 可以導出 Png/Jpeg/Base64 格式的圖片,同時 JPEG 格式還可以指定圖片質量與尺寸倍數,詳見 fabric.js 的 API 文檔。

結尾

以上就是 fabric.js 開發移動端編輯器的實現細節了,結合我們的開源項目和插件化架構可以很方便的完成項目開發,如果你在做類似項目或者做類似的項目,歡迎與我交流。

開源項目:https://github.com/ikuaitu/vue-fabric-editor/blob/main/README-zh.md

Add a new 评论

Some HTML is okay.